Skip to content

BabelJs - Babel Polyfill

While Babel excels at transpiling modern JavaScript syntax, it doesn’t inherently provide implementations for new APIs (like Promise, Map, Set, or new methods on built-ins like Array.prototype.includes). These are handled by polyfills. This chapter explains how to use polyfills with Babel to ensure your modern JavaScript code runs correctly in older environments.

Previously, @babel/polyfill was a common package for this. However, it has been deprecated. The modern approach involves using core-js (a comprehensive library of polyfills) and regenerator-runtime (for generators and async functions), often managed automatically by @babel/preset-env.

Here’s a list of common JavaScript features that often need polyfills for older browser support:

  • Promises (Promise)
  • Collections (Map, Set, WeakMap, WeakSet)
  • Symbols (Symbol)
  • Iterators and Generators (require regenerator-runtime)
  • Async/Await (require regenerator-runtime and Promise polyfill)
  • Static methods on built-ins (e.g., Array.from, Object.assign, Object.entries)
  • Instance methods on built-ins (e.g., Array.prototype.includes, String.prototype.padStart)

Setting up Polyfills with @babel/preset-env

Section titled “Setting up Polyfills with @babel/preset-env”

The recommended way to handle polyfills is by configuring @babel/preset-env. This preset can automatically include polyfills from core-js based on your code and target environments.

First, ensure you have the necessary packages installed. Create a new project:

mkdir babel-polyfill-example
cd babel-polyfill-example
npm init -y

Install Babel and core-js:

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save core-js regenerator-runtime

Your package.json devDependencies will look something like this (versions may vary):

{
"name": "babel-polyfill-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "babel src -d lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.x.x",
"@babel/core": "^7.x.x",
"@babel/preset-env": "^7.x.x"
},
"dependencies": {
"core-js": "^3.x.x",
"regenerator-runtime": "^0.x.x"
}
}

Now, create a Babel configuration file, babel.config.json (or .babelrc.json), in your project root:

{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
"browsers": ["> 0.25%", "not dead", "ie >= 11"]
}
}
]
]
}

Explanation of the configuration:

  • "useBuiltIns": "usage": This tells Babel to analyze your code and automatically add imports to core-js modules for polyfills that are used and not supported by your target environments. This is very efficient as it only includes what’s needed.
  • "corejs": 3: Specifies that you are using core-js version 3.
  • "targets": Defines the environments you are targeting. Polyfills will be added based on the features missing in these environments. You can specify browser queries, Node versions, etc. (See browserslist documentation for more: https://github.com/browserslist/browserslist)

Alternatively, with "useBuiltIns": "entry", you would manually add import "core-js/stable"; and import "regenerator-runtime/runtime"; at the beginning of your main entry file (e.g., src/index.js). Babel would then replace these with specific imports based on your targets.

Let’s create a src directory and add some examples.

Create src/promises-example.js:

src/promises-example.js
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved after 1 second!");
}, 1000);
});
myPromise.then(message => {
console.log(message);
});
async function fetchData() {
await myPromise;
console.log("Data fetched after promise resolved (using async/await).");
}
fetchData();

Compile this file using the build script in package.json:

npm run build

This will create lib/promises-example.js. If you inspect the transpiled code, you’ll see Babel has transformed the async/await syntax and, due to useBuiltIns: "usage" and your targets, it would have injected import statements for core-js/modules/es.promise.js (or similar) and regenerator-runtime/runtime.js if Promises or async functions are not fully supported by your target (e.g., IE 11).

To run this in a browser, you’d typically use a bundler like Webpack or Parcel to resolve these imports and create a single bundle file. For a simple test, you could create an index.html:

index.html
<html>
<head>
<title>Babel Polyfill Test</title>
</head>
<body>
<h1>Testing Promises with Polyfills</h1>
<!-- In a real app, you'd use a bundler.
This direct script tag won't work if Babel added import statements for polyfills.
For this example, assume you bundle lib/promises-example.js into a single bundle.js -->
<!-- <script src="path/to/your/bundled_promises_example.js"></script> -->
<p>Open your browser's console to see the output.</p>
<p>If bundling 'lib/promises-example.js' and running it, the console would show:</p>
<pre>
Promise resolved after 1 second!
Data fetched after promise resolved (using async/await).
</pre>
</body>
</html>

Note: The old method of directly including babel-polyfill/dist/polyfill.min.js is no longer the standard practice. Modern toolchains rely on modular polyfills managed by @babel/preset-env and bundlers.

String.prototype.padStart() and String.prototype.padEnd() were introduced in ES2017.

Create src/string-padding-example.js:

src/string-padding-example.js
const str = 'Babel';
console.log(str.padStart(10, '_')); // Expected: '_____Babel'
console.log(str.padEnd(10, '*')); // Expected: 'Babel*****'

After running npm run build, lib/string-padding-example.js will be generated. If your targets include browsers that don’t support string padding, Babel (via core-js) will ensure the necessary polyfills are included (or transformations made if possible). When bundled and run in such a browser, the console output would be:

_____Babel
Babel*****

Map and Set are ES2015 features.

Create src/collections-example.js:

src/collections-example.js
let myMap = new Map();
myMap.set("name", "BabelJS");
myMap.set("type", "Transpiler");
console.log(myMap.get("name"));
let mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add(5); // duplicate, will be ignored
mySet.add(10);
console.log(mySet.has(5));
console.log("Set size:", mySet.size);

After npm run build, if targeting older environments, the transpiled code in lib/collections-example.js, when bundled and run, would output:

BabelJS
true
Set size: 3

Array.prototype.includes (ES2016) and Array.from (ES2015).

Create src/array-methods-example.js:

src/array-methods-example.js
const numbers = [1, 2, 3, 4, 5];
console.log("Includes 3:", numbers.includes(3)); // Expected: true
console.log("Includes 6:", numbers.includes(6)); // Expected: false
const newArray = Array.from("hello");
console.log("Array from string:", newArray); // Expected: ['h', 'e', 'l', 'l', 'o']

After npm run build, the transpiled code in lib/array-methods-example.js, when bundled and run, would output:

Includes 3: true
Includes 6: false
Array from string: ["h", "e", "l", "l", "o"]
  • Bundlers are Key: For browser applications, using a module bundler (like Webpack, Parcel, or Rollup) is essential. Bundlers resolve the import statements for core-js modules and regenerator-runtime that Babel might add, creating a single (or few) JavaScript file(s) for your index.html.
  • Targeting: Carefully define your targets in the Babel configuration. This ensures that polyfills are only included for browsers/environments that actually need them, optimizing your bundle size.
  • core-js Version: Always specify the corejs version in your Babel config to ensure consistent behavior and avoid warnings. core-js@3 is the modern standard.
  • Global Pollution: core-js polyfills can modify global objects (e.g., Promise, Array.prototype). This is generally fine for applications but might be a concern for libraries. Libraries might prefer using @babel/plugin-transform-runtime to avoid global pollution, which provides helpers that don’t modify globals.

By leveraging @babel/preset-env with core-js, you can confidently use modern JavaScript APIs and ensure your applications are robust and compatible across a wide range of environments. For detailed information on core-js and its capabilities, visit: https://github.com/zloirock/core-js