Use core-js v3 stable to polyfill finished ECMAScript APIs for legacy browsers
Add runtime polyfills for finished ECMAScript features with core-js v3 stable while keeping syntax transforms separate.
16 Jul 2025, 06:29 UTC

The problem: syntax transpilation is not enough
Babel, SWC, and similar tools can transform modern syntax such as optional chaining into older syntax, but they cannot add runtime APIs that older JavaScript engines never had. If your application uses Array.prototype.flat, Promise.allSettled, or Object.fromEntries, users on legacy browsers can see errors like undefined is not a function even after syntax transpilation. core-js v3 supplies polyfills for finished ECMAScript features through its /stable entry point, excluding stage proposals so production builds stay predictable.
Desired outcome
Add core-js v3 to a JavaScript application build, import the stable polyfill set, confirm the targeted APIs exist in the oldest supported browser, and keep bundle growth under review.
Prerequisites
- A Node.js project with
package.jsonand a package manager such as npm. - A bundler or build step (webpack, Vite, Rollup, or similar) that emits browser bundles.
- A list of oldest supported browser versions, such as the last two versions of Chrome or a specific Internet Explorer 11 support policy.
- core-js v3 assumed; core-js v2 is end of life and is not covered here.
1. Install core-js v3
Run this in the project root as a normal user. Do not install globally unless your workflow requires it.
npm install core-js@^3 --save
After installation, open package.json and pin the minor version you tested, for example changing ^3 to a specific 3.x range. Patch releases can change polyfill internals, so an unpinned major range can introduce unexpected behavior between builds.
Expected check:
node -e "console.log(require('core-js/package.json').version)"
This should print a v3 version string. Confirm node_modules/core-js/stable exists. Risks: network failure, version conflicts if another dependency already bundles a different core-js major, and write permission errors in node_modules.
2. Import the stable entry point
Create or edit your application entry file so the polyfills run before application code. Importing core-js/stable patches global built-ins such as Array, Promise, Object, and String.
// src/polyfills.js
import 'core-js/stable';
// Application code can now assume finished ECMAScript APIs exist.
// import 'core-js/es/array/flat';
// import 'core-js/es/promise/all-settled';
Add src/polyfills.js to your bundler entry before src/index.js:
// webpack.config.js example
module.exports = {
entry: ['./src/polyfills.js', './src/index.js'],
// ...
};
For size-sensitive applications, import only the features you use instead of the full stable set. Tree-shaking may remove unused polyfills, but verify in your built output rather than assuming it.
3. Keep syntax transforms separate
core-js adds runtime APIs only. It does not transpile syntax. Continue using Babel, SWC, or your bundler's syntax transform for optional chaining, nullish coalescing, class fields, and similar syntax. Without that step, modern syntax will still fail in legacy engines even if the APIs are polyfilled.
4. Verify the polyfills
Run a feature-detection check after the import:
// test/polyfill-check.js
import 'core-js/stable';
const checks = {
'Array.prototype.flat': typeof Array.prototype.flat === 'function',
'Promise.allSettled': typeof Promise.allSettled === 'function',
'Object.fromEntries': typeof Object.fromEntries === 'function',
};
console.table(checks);
const failed = Object.entries(checks).filter(([, ok]) => !ok);
process.exit(failed.length ? 1 : 0);
Run from the project root:
node test/polyfill-check.js
Expected result: every check prints true and the process exits with code 0. A modern Node.js installation already includes these APIs, so this command confirms the import works but does not prove legacy browser support. Open the built application in your oldest supported browser and run the same typeof checks in DevTools. If any check returns false, the polyfill did not run or was removed by the bundler.
As a diagnostic comparison, build the bundle with and without the polyfill import and inspect the output for core-js modules. The version with the import should be larger and include the polyfill code; syntax transforms should still come from your transpiler configuration.
Limitations and risks
- Bundle size:
core-js/stableincludes many polyfills. Import individual features when size matters. - Global conflicts: Patching built-ins globally can conflict with other libraries or multiple core-js versions in the same bundle. Audit your dependency tree for duplicate core-js copies.
- No syntax transpilation: core-js does not convert modern syntax to older syntax.
- Content Security Policy: If you use a strict CSP, review the built output for any runtime code generation and test in a policy-enforced environment.
Rollback
Because installing a dependency changes project state, you can roll back by removing the import, uninstalling core-js, and rebuilding:
npm uninstall core-js
Then revert package.json and package-lock.json if needed, remove the entry-point import, rebuild, and redeploy. Confirm the application returns to its previous behavior in supported browsers.
Expected result
Finished ECMAScript APIs such as Array.prototype.flat and Promise.allSettled are available in legacy browser targets without transpiling those APIs away. Syntax still requires your existing transpiler, and bundle size should be reviewed before shipping the full stable set to production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.