Using Externs to Safely Enable Advanced Compilation in ClojureScript
Learn how externs files keep external JavaScript names intact during ClojureScript's advanced compilation, letting you shrink bundles without breaking third‑party APIs.
19 Sept 2025, 20:33 UTC

The Disappearing API Problem
When you switch a ClojureScript project from development (:none or :simple) to production (:advanced), the Google Closure Compiler renames local symbols to shrink the bundle. If your code calls a global JavaScript function or object that the compiler does not know about, that name may be changed as well. At runtime the renamed symbol does not exist, causing a TypeError.
How Externs Protect Your Integration
An externs file is a plain JavaScript file that declares the shapes of external APIs. It contains only variable and function declarations, no implementation. The compiler treats any name found in an externs file as immutable, preserving it exactly in the output.
Practical Implementation
Example: Tracking Button Clicks with a Third‑Party Analytics Library
Suppose you use a library that exposes a global analytics object with a trackEvent method.
1. Create the externs file
// externs/analytics_externs.js
var analytics = {
trackEvent: function(eventName, data) {}
};
2. Write the ClojureScript code
(ns app.core)
(defn log-click [btn-id]
(.trackEvent js/analytics "button-click" {:id btn-id}))
3. Reference the externs in your build
If you use shadow-cljs.edn:
{:builds
{:app
{:target :browser
:optimizations :advanced
:externs ["externs/analytics_externs.js"]}}}
For plain cljs.main you can pass -a :advanced -e externs/analytics_externs.js.
Verification and Diagnostics
Because errors from missing externs appear only at runtime, check the generated JavaScript:
- Open the compiled
app.jsand search for the stringtrackEvent. It should appear unchanged. - If you see a single‑letter name like
a(), the externs are not being applied. - As a quick test, build first with
:optimizations :simple; if it works there but fails in:advanced, you likely missed an extern.
Trade‑offs and Maintenance
Externs shift the burden of API compatibility to you. Every time a third‑party library updates, you must adjust the externs file. Omitting a single name leads to a silent rename and a runtime crash that is hard to debug in a minified bundle.
| Approach | Pros | Cons |
|---|---|---|
| Manual externs | Full control, no extra dependencies. | Time‑consuming for large APIs. |
| Generated externs (e.g., gextern) | Quick setup for big libraries. | May include unused symbols, increasing output size. |
| cljsjs packages | Maintained by the community. | Can lag behind the latest library releases. |
Actionable Closing
Adopt a “strict externs” workflow: keep externs in a dedicated directory, verify the generated JS for each external call before releasing, and prefer generated or community externs when the API is large. This lets you enjoy the 60‑80 % size reduction of :advanced compilation without surprise runtime failures.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.