Configuring full externs preservation for default ES6 npm dependencies in :advanced ClojureScript builds
0 reputation · 27 Dec 2022, 01:00 UTC
0 reputation · 27 Dec 2022, 01:00 UTC
When building a ClojureScript project with :advanced optimizations and consuming an npm package that exports a default class or function (e.g., lodash-es), the compiler’s :npm-deps externs inference may omit prototype methods, causing property names to be mangled and runtime failures.
The goal is to ensure that all properties of the imported default export are treated as externs without having to enumerate each method manually. Current workarounds require writing externs files, using ^js hints, or lowering the optimization level, each of which adds maintenance overhead or sacrifices whole‑program optimization.
It is unclear whether the compiler can be directed to preserve all exports for a specific namespace, if :foreign‑libs with :provides/:requires can achieve this, or whether Shadow‑cljs offers an equivalent flag.
33275 reputation · 27 Dec 2022, 06:27 UTC
The ClojureScript compiler and Shadow-cljs do not currently provide a :preserve-all-exports flag or a configuration setting to automatically treat all properties of a specific npm namespace as externs. Neither :npm-deps nor :foreign-libs guarantees the preservation of an object's prototype methods or internal properties during :advanced optimizations.
In :advanced mode, the Google Closure Compiler performs aggressive renaming (mangling) of properties to reduce bundle size. While the compiler can infer that a default export exists, it cannot statically analyze the full prototype chain or the dynamic property list of a JavaScript class or library (like lodash-es) at compile time. Consequently, while the top-level object name may be preserved, its methods (e.g., .map, .filter) are often renamed to single characters, leading to runtime failures.
Since there is no global toggle for this behavior, you must explicitly define the properties you intend to use. The most maintainable approach is using a dedicated externs file rather than inline hints.
externs.xjs) to define the shape of the npm module:
var lodash = {
default: {
map: function(a, b) {},
filter: function(a, b) {}
}
};
:build-id configuration under :externs.:externs option in the compiler map.To confirm if properties are being mangled, perform the following checks:
.js production bundle for the literal string of the method name (e.g., ".map("). If you only find single-letter method calls (e.g., ".a("), mangling is occurring.:advanced mode but works in :dev. If so, it is almost certainly a mangling issue.Diagnostic Note: Are you using shadow-cljs or the standard cljs.main compiler? The method for attaching the externs file differs slightly between the two.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.