Reducing Bundle Size with Babel's useBuiltIns: 'usage'
Stop bloating your JS bundles with unnecessary polyfills. Learn how to use Babel's useBuiltIns: 'usage' to inject only the code your target browsers actually need.
30 Sept 2026, 12:51 UTC

The Problem: Polyfill Bloat
Supporting older browsers often requires polyfills—code that implements modern JavaScript features in environments that do not natively support them. Traditionally, developers imported a global polyfill library (like @babel/polyfill), which adds every possible polyfill to the bundle regardless of whether the application actually uses those features. This significantly increases bundle size and slows down page load times.
The solution is useBuiltIns: 'usage' within @babel/preset-env. This configuration tells Babel to analyze your source code and inject only the specific polyfills required by the features you have actually written, based on your target browser list.
Worked Configuration
To implement selective polyfilling, you must have core-js installed in your project. This example assumes core-js@3 is used, as version 2 is deprecated.
1. Install dependencies
Run this in your project root:
npm install core-js@3
2. Configure Babel
Add the following to your babel.config.json:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead",
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
How it works in practice
If your source code contains [].flat() but does not use Promise.allSettled(), Babel will automatically insert import "core-js/modules/es.array.flat.js"; at the top of the file during the build process. It will ignore the allSettled polyfill entirely, keeping the bundle lean.
Critical Implementation Details
The Entry Point
When using useBuiltIns: 'usage', you must not import core-js or @babel/polyfill globally in your entry file. Doing so imports the entire library, defeating the purpose of the "usage" setting. The only global import you may still need is regenerator-runtime/runtime if you are targeting very old environments and using async/await or generators.
Plugin Execution Order
Babel applies plugins before presets. If you use a plugin that transforms a modern feature into an older syntax before preset-env sees it, preset-env may not detect the need for a polyfill. To avoid this, ensure your preset-env configuration is correctly placed in the presets array. Because Babel presets run in reverse order (last to first), @babel/preset-env should generally be the last preset listed to ensure it processes the code as intended.
Limitations and Common Pitfalls
- Static Analysis Limits: Babel performs static analysis. It cannot detect features used inside
eval()or features called via dynamic property access (e.g.,window['Promise']). These will not be polyfilled automatically. - Dependency Polyfilling: By default,
babel-loader(and similar tools) often excludesnode_modulesfrom transpilation. If a third-party library uses a modern feature that your target browsers don't support,useBuiltIns: 'usage'will not see that code and will not provide the necessary polyfill. You may need to include specific dependencies in your Babel loader'sincludepath. - Version Mismatch: The
corejsversion in your config must match the major version of the installedcore-jspackage. Specifyingcorejs: 3while havingcore-js@2installed will result in module-not-found errors during the build.
Verifying the Result
To confirm that selective polyfilling is working, you can inspect your production build artifacts:
- Bundle Analysis: Use a tool like
webpack-bundle-analyzerorsource-map-explorer. Search the resulting map forcore-js. You should see individual module imports (e.g.,core-js/modules/es.array.flat.js) rather than a single massivecore-jsentry. - Runtime Testing: Load the application in a browser that lacks the feature you are testing (e.g., an older version of Safari or IE11). If the feature works, the polyfill was injected.
- Modern Browser Check: Ensure that the application still loads correctly in modern browsers without causing conflicts or excessive overhead.
Rollback
Since this configuration only affects the build output and not the source code, rolling back is straightforward:
- Remove
useBuiltIns: 'usage'andcorejs: 3frombabel.config.json. - If you need a global polyfill again, add
import 'core-js/stable';to your entry point. - Run a clean build to clear any cached transpilation results.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.