Shrinking ClojureScript Bundles with :advanced Optimizations – A Practical Guide
Learn how to shrink your ClojureScript bundle with Shadow CLJS’s :advanced compiler mode. A step‑by‑step guide covers config, externs, pitfalls, and verification to keep your production build fast and reliable.
31 Oct 2025, 22:05 UTC

Why Bundle Size Matters in ClojureScript
In a typical single‑page application written with Reagent, a vanilla Shadow CLJS build can produce a ~1 MB JavaScript bundle. For mobile users or low‑bandwidth environments that’s a noticeable delay. The compiler’s :advanced mode promises aggressive minification, dead‑code elimination, and property mangling that can reduce the bundle to 200 KB or less. The trade‑off? You must hand‑craft externs for every external JS API you use.
What :advanced Does Under the Hood
The Closure Compiler in :advanced mode performs:
- Dead‑code elimination – removes any function or variable that is never referenced.
- Property mangling – shortens object property names (e.g.,
foo→a), which shrinks the code further. - Advanced name mangling – renames local variables and function names to single characters.
- Aggressive inlining – replaces function calls with their bodies when safe.
Because property names are changed, any interop that accesses properties by string (e.g., (.-foo obj) or (aget obj "foo")) must be static and known to the compiler. If the compiler can’t see that a property exists, it will rename it, breaking the runtime.
Setting Up a Shadow CLJS Production Build
Below is a minimal shadow-cljs.edn that compiles a Reagent app for production using :advanced optimizations.
{:source-paths ["src"]
:dependencies [[reagent "1.2.0"]]
:builds
{:app
{:target :browser
:output-dir "public/js"
:asset-path "/js"
:modules {:main {:entries [my-app.core]}}
:devtools {:http-root "public"
:http-port 3000}
:compiler {:optimizations :advanced
:pretty-print false
:source-map false}}
}
Run the release build with:
npx shadow-cljs release app
Shadow CLJS automatically generates externs for common libraries (React, Reagent). If you import a third‑party library like lodash, add an extern file or use --externs to include its API.
Verifying the Build
- Bundle Size – After running
shadow-cljs release app, checkpublic/js/main.js. It should be < 200 KB for a typical small app, compared to ~1 MB with:whitespace. - Runtime Interop – In the browser console, import a lodash function (e.g.,
_.chunk([1,2,3,4], 2)). If it returns[ [1,2], [3,4] ], the externs are correct. - Stack Traces – Trigger a Reagent component re‑render and observe the console. With
:source-map false, stack traces will reference the minified symbols; if you seeundefinedor mangled property names, your externs are incomplete.
Common Pitfalls and How to Avoid Them
- Missing Externs – Any external API not declared in an extern file will be mangled. Always review the console for errors like
undefined is not a functionafter a build. - Dynamic Property Access – Patterns like
(.-foo obj)are static, but(aget obj "foo")or(js-define obj "foo" 42)are dynamic and will be removed. Refactor to static access or use:whitespacefor those modules. - Source Maps – Enabling
:source-map truewith:advancedproduces misleading stack traces because property names have changed. Keep source maps off for production. - Eval and js-apply – These constructs are stripped entirely. If your code relies on them, move that logic to a
:whitespacebuild or refactor.
When to Use :advanced and When to Stay Safe
If your app is a small to medium UI with minimal external interop, :advanced can deliver a dramatic size reduction with little maintenance. For larger codebases that depend heavily on dynamic JavaScript interop, consider a hybrid approach: keep the core UI in :advanced and isolate dynamic modules in :whitespace builds.
Actionable Checklist
- Run
shadow-cljs release appin a clean environment. - Verify bundle size < 200 KB.
- Test all third‑party libraries in the browser console.
- Confirm no runtime errors related to missing externs.
- Disable source maps for production; keep them for debug builds.
- Document any custom externs added for future maintainers.
Conclusion
Enabling :advanced is a powerful way to shrink ClojureScript bundles and boost runtime performance, but it comes with a cost: meticulous extern management and a stricter coding style. By following the steps above, you can safely adopt advanced optimizations in Shadow CLJS and deliver faster, leaner applications to your users.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.