Selective Polyfilling with core-js/pure and Babel to Cut Bundle Size
Global polyfills bloat bundles and mutate prototypes. Learn how core-js/pure with Babel's usage mode injects only the polyfills your code actually needs, with config, example, and pitfalls.
02 Nov 2025, 19:28 UTC

Global polyfills have a hidden cost: importing core-js at your entry point pulls in every feature for your target browsers and mutates built-in prototypes like Array.prototype. That bloats the bundle and can clash with other libraries. The practical fix is core-js/pure combined with Babel's useBuiltIns: 'usage' mode, which injects only the polyfills your code actually calls, without touching globals.
How the pure build differs
Standard core-js modifies the environment: importing core-js/modules/es.array.flat adds flat to every array. The core-js-pure package (installed automatically as a dependency of core-js@3) instead exports each feature as a standalone function. Babel's usage mode rewrites your modern syntax into calls to those pure functions, so bundlers like webpack or Rollup can tree-shake everything you never used.
Configuration
Install the tooling (run in your project root; requires Node.js and npm):
npm install --save core-js@3
npm install --save-dev @babel/core @babel/preset-envThen configure babel.config.json. Pin the corejs version so Babel uses the correct feature mapping:
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": { "version": 3, "proposals": false },
"targets": "> 0.5%, not dead"
}]
]
}With usage mode you do not add manual imports; Babel analyzes each file and inserts the pure imports it needs.
Worked example
Given this source in src/index.js:
const nums = [1, [2, 3], 4];
console.log(nums.flat());
(async () => {
await Promise.allSettled([Promise.resolve(1)]);
})();Babel emits something equivalent to:
import _flat from "core-js-pure/features/array/flat";
import _Promise from "core-js-pure/features/promise";
import _allSettled from "core-js-pure/features/promise/all-settled";
const nums = [1, [2, 3], 4];
console.log(_flat(nums));The exact output varies by Babel version; treat this as illustrative, not literal. The key point: only flat, Promise, and allSettled enter the bundle, not the full polyfill suite.
Limits and common mistakes
- Never use
useBuiltIns: 'entry'with the pure strategy. Entry mode imports the entire polyfill set based on your targets, negating all size benefits. - Don't mix
core-jsandcore-js-pureimports. You can end up with both global and pure copies of the same feature, duplicating code. - Third-party dependencies are not covered. Babel typically excludes
node_modules, so a library that callsArray.prototype.flatinternally will still break in old browsers unless that library polyfills itself or you add explicit imports for it. - Match versions. A mismatch between the installed
core-jsversion and thecorejsoption in Babel can cause missing or incorrect polyfill mappings. - Instance methods become function calls. Pure polyfills can't extend prototypes, so Babel must transform the syntax; anything it can't statically detect (e.g., dynamic property access) won't be polyfilled.
Verifying the result
Three practical checks:
- Inspect the bundle. Run webpack with
--jsonand feed the output towebpack-bundle-analyzer, or grep the built file forcore-js-puremodule paths. Only the features you use should appear. - Compare sizes. Build before and after switching from entry mode to usage mode; a meaningful reduction confirms tree-shaking works.
- Test in an old browser. Run the built bundle in an environment lacking the feature (for example, an older Chrome via a browser-testing service) and confirm
flatandPromise.allSettledbehave correctly.
Note: version numbers and module paths above reflect core-js 3.x behavior; check your installed version's documentation before relying on exact paths.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.