Stop Over-Polyfilling: Optimizing Bundle Size with @babel/preset-env
Stop shipping unnecessary polyfills. Learn how to use @babel/preset-env's useBuiltIns option to automatically inject only the code your target browsers actually need.
22 May 2026, 00:06 UTC

The Polyfill Bloat Problem
Many JavaScript projects suffer from "polyfill bloat." This happens when developers import a massive library like core-js in its entirety to ensure a few legacy browsers don't crash. The result is a bundle filled with code for features your users' browsers already support, slowing down page loads for everyone.
The solution is to shift from manual imports to conditional polyfilling. By using @babel/preset-env, you can tell Babel exactly which environments you support, and it will inject only the specific polyfills required for those targets.
How useBuiltIns Automates Compatibility
The useBuiltIns option in @babel/preset-env manages how Babel handles polyfills. Instead of you guessing which features need a fallback, Babel references the browserslist database to map your target browsers to the necessary core-js modules.
There are two primary modes for this feature:
entry: You add a single import ofcore-jsat your application's entry point. Babel replaces that one import with a list of specific imports required for your target browsers.usage: Babel analyzes every file and adds an import for a polyfill only if it sees the feature being used in that specific file.
Practical Configuration Example
To implement this, you need @babel/preset-env and core-js installed in your project. This example assumes core-js@3, which is the current standard for modular polyfills.
1. The Configuration
In your babel.config.json or .babelrc, define your targets and the polyfill strategy:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead",
"useBuiltIns": "entry",
"corejs": 3
}
]
]
}
2. The Entry Point
If using useBuiltIns: "entry", add these lines to the very top of your main JavaScript file (e.g., index.js):
import "core-js/stable";
import "regenerator-runtime/runtime";
3. Execution and Verification
Run your build process via your bundler (Webpack, Rollup) or the Babel CLI. To verify the result, search the generated bundle for core-js imports. You should see a long list of specific modules (e.g., core-js/modules/es.array.from.js) rather than one giant library import.
Comparing 'entry' vs 'usage'
| Feature | useBuiltIns: 'entry' | useBuiltIns: 'usage' |
|---|---|---|
| Bundle Size | Moderate (All targets' needs) | Smallest (Only used features) |
| Reliability | High (Covers all target gaps) | Moderate (May miss dynamic code) |
| Setup | Requires manual import at top | Automatic; no import needed |
Limitations and Risks
While this automation is powerful, it relies entirely on the accuracy of your targets string. If you specify "last 2 versions", but your actual user base includes a significant percentage of users on a 5-year-old browser, your app will throw runtime errors because the necessary polyfills were omitted.
Additionally, useBuiltIns: "usage" can sometimes struggle with third-party dependencies. If a library in node_modules requires a polyfill but isn't being processed by Babel, that polyfill won't be injected, leading to a crash in older browsers.
Actionable Closing
To optimize your project today: first, check your analytics to see which browsers your users actually use. Second, update your browserslist configuration to match that data. Finally, switch to useBuiltIns: "entry" for maximum reliability or "usage" for minimum bundle size, ensuring you specify corejs: 3 to avoid using the deprecated version 2.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.