Stop Over-Transpiling: Optimizing @babel/preset-env Targets
Stop shipping bloated ES5 code to modern browsers. Learn how to configure @babel/preset-env targets and useBuiltIns to balance compatibility with bundle size.
17 Dec 2025, 08:04 UTC

The Cost of "Safe" Defaults
Many JavaScript projects start with a generic Babel configuration that transforms everything down to ES5. While this ensures the app runs everywhere, it creates a performance tax for the vast majority of your users. Over-transpilation replaces concise modern syntax (like async/await or #private fields) with verbose helper functions and heavy polyfills, increasing bundle size and slowing down execution in modern engines.
The goal is to ship the most modern code possible that your specific target audience can actually execute. By configuring @babel/preset-env with explicit browser targets, you can stop shipping code for browsers your users aren't using.
Defining Your Support Matrix
Babel doesn't guess which browsers you support; it relies on a Browserslist query. This is a standardized way to define target environments across different tools (Babel, Autoprefixer, ESLint). You can define this in a .browserslistrc file or within your Babel config.
Avoid using generic defaults like "defaults", which often include legacy browsers that bloat your bundle. Instead, use a query that reflects your actual telemetry or business requirements. For example, ">0.5%, not dead" targets browsers with more than 0.5% global usage that are still receiving official support.
Automating Polyfills with useBuiltIns
Syntax transformation (changing () => {} to function() {}) is only half the battle. You also need polyfills for missing APIs like Promise.allSettled or Array.prototype.includes. The useBuiltIns: "usage" option is the most efficient approach because it analyzes your code and injects only the polyfills required by the features you actually use, based on your target browsers.
To make this work, you must specify the corejs version (typically 3) to ensure Babel knows which polyfill library to reference. Note that core-js must be installed as a production dependency in your package.json since it is bundled into the final application.
Implementation Example
Below is a production-ready configuration for a project targeting modern browsers while preserving ES modules for tree-shaking in bundlers like Webpack or Vite.
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.5%, last 2 versions, not dead",
"useBuiltIns": "usage",
"corejs": 3,
"modules": false,
"bugfixes": true
}
]
]
}
Configuration Breakdown:
"modules": false: Prevents Babel from transforming ES modules to CommonJS, allowing your bundler to remove unused code (tree-shaking)."bugfixes": true: Tells Babel to compile the most modern syntax that the target browser supports, rather than transpiling a whole feature group just because one small bug exists in a specific version."useBuiltIns": "usage": Only imports polyfills for features used in the source code that are missing in the target browsers.
Verifying the Output
To confirm your targets are resolving correctly, run the following command in your terminal (requires @babel/core and @babel/preset-env installed):
# Run from project root to see resolved targets and active plugins
npx babel --show-config
If you want to see exactly why a specific file is being transpiled, you can use an environment variable during the build process:
# Linux/macOS
BABEL_SHOW_CONFIG_FOR=./src/index.js npx babel ./src/index.js
Trade-offs and Limitations
While useBuiltIns: "usage" is powerful, it has a significant blind spot: Global Web APIs. Babel polyfills ECMAScript features (via core-js), but it does not polyfill browser-specific APIs like fetch, IntersectionObserver, or ResizeObserver. If your target browsers lack these, you must still import those polyfills manually at your application entry point.
Additionally, remember that browserslist data changes as browser versions are released. To keep your bundles lean, you should periodically update the underlying database:
npx browserslist --update-db
Actionable Summary
To optimize your build: move your browser targets to a .browserslistrc file for consistency, switch to useBuiltIns: "usage" with corejs: 3, and ensure modules: false is set to enable tree-shaking. Finally, audit your final bundle using a tool like webpack-bundle-analyzer to ensure core-js isn't taking up more space than necessary due to overly broad targets.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.