core-js global namespace pollution in multi-library environments
0 reputation · 21 Aug 2023, 11:09 UTC
0 reputation · 21 Aug 2023, 11:09 UTC
core-js in its global mode, the library modifies the ECMAScript prototype chain to provide missing features. This behavior is standard for application-level development, but it introduces risks in complex environments where multiple dependencies might rely on different versions of the same polyfill.
The primary concern involves the internal detection logic used by core-js to determine if a feature is natively supported or already polyfilled. If a dependency requires a specific version of a proposal-stage polyfill while the application provides a different version, the global namespace may be overwritten multiple times, leading to inconsistent runtime behavior across the bundle.
How does core-js resolve version conflicts when a global polyfill is already present but differs from the required implementation?
Are there configuration options to prevent global prototype pollution while still utilizing the useBuiltIns Babel integration?
31775 reputation · 21 Aug 2023, 22:52 UTC
core-js does not resolve version conflicts at all — there is no negotiation, comparison, or version check at runtime. In global mode, each core-js module checks whether a feature appears to exist (typically via feature detection on the prototype or global), and if its internal criteria aren't met it assigns its own implementation. When two different core-js versions (or a core-js polyfill and another library's polyfill) both land in the same bundle, the last one to execute wins, because prototype assignment silently replaces whatever was there before. If a dependency was written and tested against a different version of a proposal-stage method (whose semantics may still be changing), it can break in ways that only surface at runtime.
Confirmed, well-established behavior: global-mode core-js mutates built-in prototypes (Array.prototype, Promise, etc.), and duplicate loads overwrite each other. What is likely but version-dependent: exactly which features get overwritten. core-js's detection logic sometimes respects an existing implementation it considers spec-compliant and skips re-polyfilling, but for proposal-stage or partially compliant features it will replace them. The exact thresholds differ between core-js v2 and v3 and across minor releases, so treat any specific "it won't overwrite X" claim as unverified for your version.
Yes — this is supported, with caveats depending on your core-js major version.
@babel/plugin-transform-runtime with corejs: 3. This rewrites built-in usage to imports from core-js-pure, which never touches global prototypes, while still giving you per-feature, on-demand injection.// babel.config.json
{
"plugins": [
["@babel/plugin-transform-runtime", { "corejs": 3 }]
]
}core-js/actual/... or core-js-pure paths directly instead of the global entry points.preset-env's useBuiltIns: "usage" injects global core-js imports — there is no supported "pure" mode for useBuiltIns itself. If you need zero prototype pollution, the transform-runtime route is the standard answer.npm ls core-js core-js-pure
# expect a single deduped version; nested copies signal conflict riskAfter bundling, sanity-check in the browser console or a test:
// Should be undefined if pollution is prevented
Array.prototype.hasOwnProperty('flat') && !Array.prototype.flat.__corejs_shared__
// Or simply: load the bundle in a fresh iframe and diff Object.getOwnPropertyNames(Array.prototype)One diagnostic that changes the recommendation: which core-js major version(s) appear in npm ls output. If any dependency still pulls core-js v2, its options and behavior differ enough that deduplication or upgrading that dependency matters more than Babel configuration.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.