Selective core-js Polyfills with browserslist: A Practical Engineering Guide
Learn how to configure browserslist and import only the core-js polyfills your target browsers actually need, reducing bundle size without sacrificing compatibility.
12 Mar 2026, 04:52 UTC

Desired outcome
Ship only the ECMAScript polyfills required by the browsers you actually support, keeping the production bundle as small as possible while guaranteeing that the targeted language features work in the lowest‑supported environment.
Prerequisites
- Node.js ≥ 18 and npm ≥ 9 (or yarn/pnpm equivalent).
- A bundler that respects
sideEffectsinpackage.json(webpack 5, Rollup 3+, Vite 4+). - A
browserslistconfiguration either inpackage.jsonor a dedicated.browserslistrcfile. - core-js 3.x installed as a production dependency.
Procedure
1. Declare your target browsers
Edit package.json (or create .browserslistrc) with a query that reflects the real support matrix. Example:
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead",
"not IE 11"
]
}
Run the update command once so the local DB matches the query:
npx browserslist --update-db
Where to run: project root. Permissions: regular user. Risk: none; only updates a cache file.
2. Install core-js (stable) as a production dependency
npm install core-js@3 --save
If you are authoring a library that must not pollute globals, install core-js-pure instead and follow the same steps.
3. Import only the polyfills required by the browserslist
At your application entry point (e.g., src/index.js or main.ts) add a single side‑effect import that core-js provides for browserslist‑driven selection:
import 'core-js/stable';
import 'regenerator-runtime/runtime'; // optional, for async/await transpilation
Because the import has side effects, the bundler must be told not to drop it. In package.json add:
{
"sideEffects": [
"core-js/stable",
"regenerator-runtime/runtime"
]
}
If you use webpack, ensure optimization.usedExports and optimization.sideEffects are enabled (default in production mode).
4. Verify the bundle contains only the expected modules
Run a production build and analyse the output:
npm run build # or your equivalent script
npx webpack-bundle-analyzer dist/main.[hash].js # webpack example
Look for entries under core-js/modules/. You should see only polyfills such as es.array.flat-map, es.promise.all-settled, etc., that correspond to features missing from the browsers in your list.
Expected checks
- Bundle size: Compare the gzipped size before and after switching from a full
core-jsimport to the browserslist‑driven import. A reduction of 10‑30 KB gzipped is typical for modern target sets. - Feature detection in the lowest supported browser: Open the built page in the oldest browser version covered by your browserslist (e.g., Chrome 89, Firefox 78, Safari 14). Run a quick console test:
Both should behave natively without errors.console.log([1,2,3].flatMap(x => [x, x*2])); console.log(Promise.allSettled([Promise.resolve(1), Promise.reject(2)])); - Absence of global prototype pollution (if using core-js-pure): In a library build, verify that
Array.prototype.flatMapisundefinedbefore your code runs, then call your library’s polyfill helper and confirm the method appears only on the library’s internal objects.
Recovery options
- Rollback to full import: Replace the selective import with
import 'core-js';and remove thesideEffectsentries. Re‑build and verify the bundle grows as expected. - Adjust browserslist: If a required polyfill is missing, broaden the query (e.g., add "IE 11" or remove "not dead") and rebuild. The new polyfills will appear automatically.
- Disable aggressive tree‑shaking for core-js: In webpack, set
module.rulesforcore-jswithsideEffects: trueor add/* webpackIgnore: true */comments if a specific module is being dropped incorrectly.
Limitations & practical verification
- core-js follows the ECMAScript spec; the set of "stable" features changes between minor releases (e.g., 3.30 → 3.31). Pin the version in
package-lock.jsonor usenpm cito avoid surprise additions. - Tree‑shaking only works when the bundler can statically analyse the import. Dynamic
import()ofcore-js/stabledefeats the optimisation. - For library authors,
core-js-pureavoids global prototype changes but requires consumers to polyfill themselves. Document this clearly in your README.
Quick checklist
| Step | Command / Action | Verification |
|---|---|---|
| Define browserslist | Edit package.json / .browserslistrc | npx browserslist shows expected versions |
| Install core-js | npm install core-js@3 --save | npm ls core-js lists 3.x |
| Add side‑effect import | Edit entry file | Build succeeds, no "missing module" errors |
| Mark sideEffects | Update package.json | Bundle analyser shows only needed modules |
| Test in lowest browser | Open built page | Feature detection logs succeed |
Following this guide gives you a reproducible, auditable way to keep polyfill payload minimal while maintaining confidence that the targeted browsers run your code correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.