Ship smaller legacy bundles with Babel preset-env useBuiltIns usage
Use @babel/preset-env with useBuiltIns: "usage" to have Babel inject only the core-js polyfills your code actually uses for the browsers you target, keeping legacy bundles smaller.
03 Jan 2026, 09:51 UTC

Desired outcome
You need legacy browser support without shipping a blanket core-js bundle. With @babel/preset-env and "useBuiltIns": "usage", Babel statically analyzes your source and injects only the core-js modules that cover features missing in the browsers you target. The result is smaller bundles for modern browsers and polyfills only where needed.
Prerequisites
This mode requires Babel 7.12+ and core-js 3.x. core-js 2.x lacks the modular file layout that usage mode relies on and will cause missing or broken imports.
- @babel/core and @babel/preset-env as dev dependencies
- core-js@3 as a project dependency
- A browserslist query or explicit targets. An empty or overly broad query pulls in unnecessary polyfills.
- Build runs Babel after any TypeScript or other transpilation to avoid double polyfilling.
Install with user permissions in the project root:
npm install --save-dev @babel/core @babel/preset-env
npm install core-js@3Configure preset-env for usage mode
Create or edit babel.config.js at the project root. The key options are targets, useBuiltIns, and corejs version.
module.exports = {
presets: [
['@babel/preset-env', {
targets: { browsers: ['>0.5%', 'IE 11'] },
useBuiltIns: 'usage',
corejs: 3
}]
]
};targets can be a browserslist query from package.json or an explicit object. useBuiltIns: "usage" tells Babel to add imports per file. corejs: 3 matches the installed core-js major version.
With multiple entry points, Babel adds the imports at the top of each compiled file. Expect duplicate imports across entries unless you deduplicate at bundle time.
Build and verify
Run Babel from the project root where node_modules is available:
npx babel src --out-dir lib --source-mapsExpected checks:
- Open a generated file in lib and look for lines like import 'core-js/modules/es.array.flat.js'; at the top. The module name reflects the feature actually used in that file.
- Run Babel with --debug to view the transformation log and confirm core-js imports are being added.
- Check package.json for the exact core-js version and compare it to the corejs option in the preset.
- Execute the compiled bundle in the target browsers using a tool such as BrowserStack or a local VM. Verify that code using the targeted features runs without syntax errors.
- Run existing unit tests in the target environments to verify functional correctness.
If a feature is removed from source, the corresponding import should disappear on the next build, showing the dynamic nature of usage mode.
Limitations and recovery
Static analysis cannot reliably see features used inside dynamic imports or runtime feature detection. Those cases may require manual imports.
Common recovery options if the build breaks:
- Verify core-js is 3.x. Downgrading to core-js 2.x will cause errors or missing polyfills.
- Narrow the browserslist query to avoid pulling in unnecessary modules.
- If per-file imports cause duplication, switch temporarily to useBuiltIns: "entry" and import 'core-js/stable' and 'regenerator-runtime/runtime' once in your entry file.
- Revert the preset-env options to a previous working config and rebuild to restore state.
Usage mode keeps native implementations untouched in modern browsers, so the bundle stays lightweight where support exists.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.