Cutting ClojureScript Bundle Size with :advanced Optimizations
Discover how ClojureScript’s :advanced mode can shrink JavaScript bundles by 30‑70 % through dead‑code elimination, what to configure, and how to avoid common pitfalls.
22 Jul 2026, 22:16 UTC

Problem: Big JavaScript Bundles in ClojureScript
When a ClojureScript app pulls in libraries like Reagent, Re-frame, or even the core ClojureScript stdlib, the generated JavaScript file can balloon to 200 kB or more. For mobile‑first sites and progressive web apps, that extra weight slows first‑paint and increases data usage.
Thesis: Use :advanced Mode to Shrink the Bundle
The Closure Compiler’s :advanced optimization mode performs whole‑program analysis. It renames symbols, inlines constants, and, crucially, removes any code that the compiler can prove is unreachable. When ClojureScript code follows a few simple conventions, this can cut bundle size by 30‑70 % without changing runtime behavior.
How :advanced Works
- Whole‑program analysis – The compiler looks at every function and data structure in the build.
- Dead‑code elimination – Any function, object, or constant that can never be called or accessed is stripped from the output.
- Symbol renaming – Unused symbols are renamed to short strings, further shrinking the file.
- Requirements – Code must avoid dynamic
eval, property access via strings, and reflection throughjs*unless proper externs are supplied.
Setting It Up: A Minimal Reagent Project
- Create a new project (using Leiningen or deps.edn). For example, with Leiningen:
lein new reagent myapp cd myapp - Configure the build in
project.clj:{:cljsbuild {:builds {:app {:source-paths ["src"] :compiler {:main myapp.core :output-to "resources/public/js/app.js" :optimizations :none}}}} - Build with the default (none) optimizations:
Check the size:lein cljsbuild once appls -lh resources/public/js/app.js # or wc -c resources/public/js/app.js - Switch to :advanced by editing the compiler map:
Create a minimal:optimizations :advanced :pretty-print false :externs ["externs.js"]externs.jsif you interact with any external JS APIs. - Rebuild and compare:
You should see a noticeably smaller file.lein cljsbuild once app ls -lh resources/public/js/app.js
Practical Verification
- Open the generated
app.jsin a text editor and search for a known function that you know is unused. It should be absent. - Run the app in a browser (e.g.,
http://localhost:8000) and confirm that all UI elements render and events fire. - Use browser dev tools’ network tab to verify the reduced download size.
Trade‑offs & Limitations
- Externs are mandatory – If you call
window.foo()from ClojureScript, the compiler will renamefoounless you declare it inexterns.js. Missing externs cause runtime errors. - Reflection blocks elimination – Using
js* "someProp"orget-inwith string keys prevents the compiler from knowing that a property is unused. - Eval is a no‑no – Dynamic code evaluation disables whole‑program analysis for the affected scope.
- Debugging is harder – The output is minified and symbols are renamed, making stack traces less readable. Use source maps (
:source-map true) for debugging.
Actionable Takeaway
To reap the size benefits of :advanced:
- Write code with plain maps, vectors, and avoid dynamic property access.
- Maintain a comprehensive
externs.jsfile for any external APIs. - Run a quick size comparison after each major dependency change.
- If you hit runtime errors, fallback to
:simpleor:noneand investigate which symbols were renamed.
With these steps, you can confidently enable :advanced and keep your ClojureScript bundles lean.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.