Optimizing Bundle Size with Babel's useBuiltIns and Core-JS
Stop bloating your JS bundles with unnecessary polyfills. Learn how to use @babel/preset-env with 'useBuiltIns: usage' and core-js to inject only the code your target browsers actually need.
10 Jul 2025, 03:12 UTC

The Polyfill Bloat Problem
Modern JavaScript introduces APIs like Array.prototype.flat() or Object.fromEntries() that simply do not exist in older browsers. To bridge this gap, developers use polyfills—code that implements a missing feature. However, importing a full polyfill library like core-js often adds hundreds of kilobytes to a bundle, including code for features your application never actually uses.
The goal is to achieve targeted polyfilling: providing only the specific patches required by your supported browser list and your actual source code.
Choosing Your Polyfill Strategy
Using @babel/preset-env, you can automate this process via the useBuiltIns option. There are two primary strategies for handling polyfills, and choosing the wrong one can either break your app in IE11 or bloat your bundle for Chrome users.
The 'entry' Approach
When useBuiltIns: 'entry' is configured, Babel replaces a single global import (like import "core-js/stable";) with a list of individual imports based on your browserslist configuration. If your target is "last 2 versions," Babel only imports the polyfills those specific versions lack.
- Pros: Guaranteed coverage for all features needed by the target browsers.
- Cons: Includes polyfills for features you might not be using in your code, increasing bundle size.
The 'usage' Approach
With useBuiltIns: 'usage', Babel analyzes every file and injects an import for a polyfill only when it encounters a specific API that requires it. If you never use Promise.allSettled(), that polyfill is never added to the bundle, regardless of the browser target.
- Pros: Minimal bundle size; only what is actually used is included.
- Cons: Can occasionally miss polyfills for dependencies (node_modules) if they aren't processed by Babel.
Practical Configuration Example
To implement this, you need @babel/core, @babel/preset-env, and core-js installed. You must explicitly define the corejs version to avoid warnings and ensure the correct API mappings are used.
1. Define Browser Targets
Create a .browserslistrc file in your project root. This ensures all your tools (Babel, Autoprefixer, etc.) share the same target definition.
last 2 versions
> 0.5%
not dead
2. Configure Babel
In your babel.config.json, configure the preset to use the usage strategy:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.38",
"targets": "defaults"
}
]
]
}
3. Verification
To verify that Babel is correctly targeting your browsers, run the following command in your terminal:
# Run in project root
npx browserslist
This will output the exact list of browsers Babel is targeting. To verify the polyfills are being injected, compile a file using a modern method (e.g., [].flat()) and search the output JavaScript for core-js/modules/es.array.flat.js.
Trade-offs and Limitations
While useBuiltIns: 'usage' is generally superior for bundle size, it has a critical limitation: it only scans your source code.
If you import a third-party library from node_modules that uses Object.assign but doesn't provide its own polyfills, and you don't use Object.assign in your own code, Babel will not inject the polyfill. This can lead to runtime crashes in older browsers. If your dependencies are not pre-compiled to your target, useBuiltIns: 'entry' is the safer, albeit heavier, choice.
Actionable Summary
For most modern web applications, the following workflow provides the best balance of compatibility and performance:
- Use
.browserslistrcto define a realistic support window. - Set
useBuiltIns: 'usage'to keep bundles lean. - Specify a precise
corejsversion (e.g.,"3.38") to prevent version mismatch warnings. - If you encounter runtime errors in old browsers stemming from dependencies, switch to
'entry'and addimport "core-js/stable";to your main entry point.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.