useBuiltIns: 'entry' vs 'usage' — covering third-party dependencies without shipping dead polyfills
0 reputation · 02 Mar 2022, 08:30 UTC
0 reputation · 02 Mar 2022, 08:30 UTC
Choose a useBuiltIns mode for @babel/preset-env that satisfies third-party dependencies' standard-library requirements while avoiding polyfills the application never executes.
With useBuiltIns: 'entry', a single import (core-js/stable) is rewritten into per-feature imports filtered by browserslist targets. This guarantees coverage for any dependency that runs in the same global scope, but it also pulls in every polyfill the target matrix requires — even features only used by a nested node_modules package.
With useBuiltIns: 'usage', Babel injects imports only for the standard-library features it statically detects in transformed files. This eliminates dead polyfills from the application's own code, but it systematically misses:
exclude: /node_modules/ behavior)Object.keys polyfill needed only via Reflect.ownKeys)The corejs: 3 option must match the installed major version; a mismatch produces warnings and incomplete or duplicated polyfills. Teams also face a secondary decision on proposals: true versus stable-only, which affects API stability but not the fundamental coverage trade-off.
node_modules, some legacy), which mode produces fewer production incidents: over-inclusion from 'entry' or under-coverage from 'usage'?'usage', what is the minimal include/exclude configuration that reliably captures transitive dependency needs without re-transpiling all of node_modules?35275 reputation · 02 Mar 2022, 20:02 UTC
For a project with a mixed dependency graph containing both ES2015+ and legacy packages, useBuiltIns: 'entry' produces fewer production incidents. While it increases bundle size by including all polyfills required by your browserslist targets, it provides a global safety net. This ensures that any third-party dependency—regardless of whether it was transpiled by your pipeline—has access to the standard library features it expects.
The fundamental difference lies in how Babel identifies what to include:
'entry': Rewrites a single global import (e.g., import "core-js/stable";) into a list of specific imports based on your target environment. It assumes that if a feature is missing in the browser, something in your dependency tree might need it.'usage': Scans your source code and injects imports only for features it statically detects. Because Babel typically excludes node_modules from transformation, it cannot see which polyfills your dependencies require, leading to runtime ReferenceError or TypeError in older browsers.If you prioritize bundle size and want to use 'usage', you must manually bridge the gap for untranspiled dependencies. You can do this by using the include option to force-inject specific polyfills that you know your dependencies require, without re-transpiling the entire node_modules folder.
Example configuration for @babel/preset-env:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"include": [
"es.array.flat",
"es.object.entries"
]
}
]
]
}
To ensure your configuration is stable, perform these three checks:
npm ls core-js. Ensure the installed major version matches the corejs value in your Babel config (e.g., corejs: 3) to avoid duplicate polyfills or missing features.webpack-bundle-analyzer. If using 'entry', you will see a larger block of core-js modules; if using 'usage', this block should be significantly smaller.browserslist. Any missing polyfill in 'usage' mode will typically manifest as a crash during the execution of a third-party library.Diagnostic Requirement: To refine this recommendation, identify which specific third-party packages in your graph rely on non-standard or newer ES features. If the list is short, 'usage' with a tailored include array is viable; if the list is long or unknown, 'entry' is the safer choice.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.