NOTE: This proposal is dead. A simpler solution was arrived at via the new `:bundle` target.
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.
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.
I know you’re looking for direct feedback on the idea itself but I am also a little confused so I hope I can ask some questions about it first.
I understand this is to solve the problem: As a developer, when I install a CLJS library that depends on a node module, I want that node module to be installed automatically and bundled for me for use in my CLJS application.
Under the proposed solution, as a library developer I would specify a
deps.cljs
like so:And use it like so in my library code:
As an application developer, I would put
my-lib
in mydeps.edn
file and write some code that uses it:And a build.edn that looks like:
The ClojureScript compiler would then analyze this, detect that
my-lib.core
has adeps.cljs
with the foreign-libs configuration, and then execute the default:bundle-fn
cljs.webpack/bundle
that would create 3 files:index.js
with the correct imports of libraries formy-lib.core
as specified in thedeps.cljs
webpack.config.js
with the appropriate configuration for processing the libraries specified bymy-lib.core
Assuming I understand that correctly, then that leads me to ask:
I'm not sure if the
:npm-deps
thing is a total distraction. It seems like it would still be quite useful to install dependencies for consumers by having it in their classpath / including it in their app. You also mentioned how sometimes it might be good to specify both GCC to process the npm-libs and to bundle them 😵. Willing to ignore it to talk about the rest of this.Could we remove the need for explicitly specifying
foreign-libs
completely by analyzing the ns requires? I think this may have been what Thomas was talking about in Slack. It sounds like there might be some complicated cases that the simple case(:require ["react" :as react])
doesn't cover, and might mean inventing new syntax to handle them. Is that why we want to be more explicit by specifying the import pattern and the other foreign-deps information in config?Would there be a way for libraries to specify other bundling-related configuration options e.g. assets and additional loaders? I think that would go a long way in taking a load off of library users to have to create their own custom webpack stuff for each project and maintaining encapsulation of each library. webpack's config might not be composable in that way :(.
Currently it's pretty difficult to get code splitting and tree-shaking to work correctly in webpack and then have it interop with GCC. I trust that Thomas went to a lot of effort trying to get it to work before he went to the current solution in shadow-cljs where the processing and wrapping in IIFEs is done in-house. It sounds like it would be a good idea to explore this again with the latest webpack versions, but if it's not possible, are there other alternatives that would enable better tree shaking foreign code (at least at the module level)?
I hope these questions are in line with what's being discussed and I'm not totally off track and I'm not speaking out of turn by asking them. Honestly, just writing out the above (before my questions) helped clarify a lot so I hope I captured it accurately. Let me know if I can correct or update anything.
Thanks!