Skip to content

Instantly share code, notes, and snippets.

@swannodette
Last active June 1, 2020 14:22
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save swannodette/ebd8b65f887318ba68579b6bea911daf to your computer and use it in GitHub Desktop.
Transitive Foreign Dependencies
NOTE: This proposal is dead. A simpler solution was arrived at via the new `:bundle` target.

ClojureScript Support for Foreign Libraries via NPM

Problem Description

The current Webpack solution works reasonable well for application oriented development, but does not address the wider issue of developing ClojureScript libraries that depend on the wider JavaScript ecosystem where the artifacts do not reside in Java-centric distrubtion like Maven (i.e. NPM). Currently a ClojureScript library cannot express these kinds of foreign library dependencies at all. Thus users must constantly redefine basic dependencies from NPM such as React, boilerplate Webpack configuration, boilerplate import/export files, and thus cannot create and share reusable ClojureScript components that depend on this vast ecosystem.

ClojureScript actually has nearly all of the pieces to solve this problem today due to prior work around :npm-deps, as well as :foreign-libs changes to support community distribution efforts like CLJSJS.

Proposal

Allow users to create deps.cljs file in their artifact that expresses which Node dependencies they need. Previously :npm-deps was only for describing which NPM artifacts to install and which libraries to index ultimately for Closure compiler. We can extend this to allow :npm-deps to be combined with :foreign-libs. This can be done simply by extending the way we handle the :foreign-libs :file key. Instead of supplying a file, users can specify a special keyword - :bundle. This can communicate that this file doesn't yet exist and will be constructed by some other tool.

We will also extend :global-exports to support a more expressive syntax.

{cljs-react "react"}
{cljs-foo {* "foo"}}
{cljs-bar {[Foo Baz] "bar"}}

This extension allows us to generate the required index.js files for Webpack and provides enough flexiblity for users to reshape existing libraries into a more idiomatic shape for ClojureScript consumption.

Finally we will supply a new compiler option :bundle-fn a symbol that will take the the deep merged :file :bundle foreign lib entries. This deep merged foreign lib entry is exactly what users are doing manually by hand today.

We will supply a default :bundle-fn, cljs.webpack/bundle which will take this foreign lib entry and automatically generate the index.js files as well as the webpack.config.js file and then invoke webpack. We could supply :bundle-args to supply other useful flags (i.e. development, production).

Note this design isn't actually tied in anyway to webpack. If some other popular tool comes along, users can easily accomodate that by supplying their own :bundle-fn. At the same this proposal makes it easy to handle other kinds of assets like image and stylesheets via Webpack in a reusable way.

@swannodette
Copy link
Author

  1. Nothing is changing about :npm-deps.
  2. We're not going to change the ns form. However we need the ability to reshape JS libs to be more idiomatic namespaces. JS patterns around default, modules as functions etc. are not idiomatic. Note that reshaping is optional. There are cases where you need to both bundle a build and get libraries via require via npm. Being explicit is just simpler from a compiler effort perspective and it's really not much of a burden for the primary beneficiaries of the proposal - downstream users.
  3. This is trivial to add to the current proposal simply by adding some custom keys to the foreign entry that :bundle-fn will understand. You could imagine :webpack-assets and :webpack-plugins.
  4. Tree-shaking is still an unsolved problem in JavaScript, there's really nothing to talk about here and shadow doesn't offer any improvement to the problem as far as I know, so there's no inspiration to take there. There is no need for Webpack to interact with GCC. That was true before and still true under this proposal. Far as I know Thomas did not look at the current state of Webpack so we cannot draw any conclusions from his previous efforts. Looking around it seems to work reasonably well and was changed to address previous shortcomings.

Hope this answers your questions.

@thheller
Copy link

I created an example repo showing a scenario that provided the most difficulty when trying to combine CLJS code-splitting with webpack. Note that this example is deliberately simple with simple npm packages that don't involve webpack-style tree shaking. If someone wants to figure out a webpack config that achieves an acceptable split this might serve as a good example starter project. Can always make it more complex later.

@didibus
Copy link

didibus commented Jun 1, 2020

So has this been implemented? I'm kind of confused at what is today's best practice for packaging a ClojureScript library that depends on some NPM dependencies which are not compatible with Google Closure.

@swannodette
Copy link
Author

No, this problem was solved in a simpler way with the :bundle target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment