ClojureScript 1.11 :modules code splitting when moving from :target :node to :target :bundle
0 reputation · 25 Nov 2025, 14:10 UTC
0 reputation · 25 Nov 2025, 14:10 UTC
We have a ClojureScript 1.11.x browser project that currently compiles with :target :node and uses :modules to split output into a main chunk plus a lazily loaded feature chunk. We are evaluating a move to :target :bundle so webpack 5 handles npm resolution and code splitting instead of Closure Compiler's module processing.
The uncertainty is how the existing :modules map translates. Under :target :node each module becomes a separate Closure-compiled file wired together with goog.provide/require, and chunk loading is driven by cljs.loader. Under :target :bundle the same keys appear to become webpack entry point names, with splitting delegated to webpack's splitChunks configuration, which we would have to write and tune ourselves.
We also rely on advanced compilation (:optimizations :advanced) and on externs inference for a few npm dependencies, and it is unclear how much of that behavior carries over once webpack sits between our code and Closure's dead-code elimination pass.
Do :modules entry keys map one-to-one onto webpack entry points, or does the configuration need restructuring? How should lazy chunk loading be triggered in application code after the switch, given cljs.loader targets Closure modules? Are there documented pitfalls for externs inference when webpack resolves npm packages before advanced compilation runs?
No — :modules keys do not map one-to-one onto webpack entry points when you switch to :target :bundle. Under :target :node, Closure Compiler itself performs the module split: each entry in :modules becomes a separately compiled file wired with goog.provide/goog.require, and cljs.loader drives lazy loading. Under :target :bundle, ClojureScript emits a single webpack-oriented output (in 1.11.x, typically one bundle per build, with npm dependencies left as require/import statements for webpack to resolve), and the Closure module graph is no longer the splitting mechanism. You must restructure: define webpack entries and splitChunks (or dynamic import()) yourself.
:optimizations :advanced, whole-program dead-code elimination and renaming occur before webpack sees the output. Webpack then bundles the already-minified result plus npm packages. This means DCE quality is unchanged for your own code.:target :bundle, foreign npm code is handed to webpack unprocessed, so inference over those libraries does not apply the way it did when Closure digested them. In practice this is usually fine because webpack-resolved npm code is not renamed by Closure at all — but any of your code that calls into those libraries must keep property names intact. Verify interop calls survive; add explicit externs only if you observe renamed accesses at runtime.cljs.loader targets Closure modules. It does not know about webpack chunks. After the switch, lazy loading must be triggered with webpack's dynamic import() (called from a small JS shim or via js/import where supported) rather than cljs.loader/load.:target :bundle, :optimizations :advanced, and remove the :modules map from the ClojureScript build — it no longer controls splitting.webpack.config.js, set the ClojureScript output as the entry and configure optimization.splitChunks to carve out the feature chunk (e.g. by package or by a dynamic-import boundary).cljs.loader/load call site with a dynamic import() of the feature chunk, then invoke the feature's exported entry function.--json and inspect the stats to confirm the feature code lands in a separate chunk; load the page and confirm the chunk is fetched only on demand; exercise every npm interop path to catch externs/renaming issues.The exact behavior of :modules keys under :target :bundle in 1.11.x is the least certain point here — treat the claim that they become webpack entry names as unconfirmed and check your emitted bundle and webpack stats. If your lazy-loading surface is large, the rewrite from cljs.loader to import() is the main cost; if it is one or two call sites, the migration is modest.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.