Skip to content

BabelJs - Babel Polyfill

BabelJS - 现代 API 的垫片(Polyfills)

Section titled “BabelJS - 现代 API 的垫片(Polyfills)”

虽然 Babel 擅长转译现代 JavaScript 语法,但它本身并不提供新 API 的实现(例如 Promise、Map、Set,或者内置对象的新方法,如 Array.prototype.includes)。这些功能由垫片(polyfills)处理。本章将解释如何使用 Babel 集成垫片,以确保您的现代 JavaScript 代码在旧环境中也能正确运行。

以前,@babel/polyfill 是一个常用的包,但它已被弃用。现代的方法是使用 core-js(一个全面的垫片库)和 regenerator-runtime(用于 generators 和 async 函数),通常由 @babel/preset-env 自动管理。

通常需要垫片(Polyfills)的常见特性

Section titled “通常需要垫片(Polyfills)的常见特性”

以下是通常需要垫片以支持旧版浏览器的常见 JavaScript 特性列表:

  • Promise (Promise)
  • 集合 (Map、Set、WeakMap、WeakSet)
  • Symbol (Symbol)
  • 迭代器和生成器(Iterators and Generators)(需要 regenerator-runtime)
  • Async/Await(需要 regenerator-runtime 和 Promise 垫片)
  • 内置对象的静态方法(例如,Array.from、Object.assign、Object.entries)
  • 内置对象的实例方法(例如,Array.prototype.includes、String.prototype.padStart)

使用 @babel/preset-env 配置垫片(Polyfills)

Section titled “使用 @babel/preset-env 配置垫片(Polyfills)”

处理垫片的推荐方法是配置 @babel/preset-env。这个预设可以根据您的代码和目标环境自动包含来自 core-js 的垫片。

首先,确保您已安装必要的包。创建一个新项目:

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

安装 Babel 和 core-js:

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

您的 package.json 中的 devDependencies 将类似于以下内容(版本可能有所不同):

{
"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"
}
}

现在,在您的项目根目录中创建一个 Babel 配置文件 babel.config.json(或 .babelrc.json):

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

配置说明:

  • "useBuiltIns": "usage":这告诉 Babel 分析您的代码,并自动为那些被使用且您的目标环境不支持的垫片添加 core-js 模块的导入。这非常高效,因为它只包含所需的部分。
  • "corejs": 3:指定您正在使用 core-js 版本 3。
  • "targets":定义您要支持的目标环境。垫片将根据这些环境中缺失的特性添加。您可以指定浏览器查询、Node 版本等(更多信息请参见 browserslist 文档:https://github.com/browserslist/browserslist)。

或者,如果使用 "useBuiltIns": "entry",您需要在主入口文件(例如 src/index.js)的开头手动添加 import "core-js/stable"; 和 import "regenerator-runtime/runtime";。然后 Babel 将根据您的 targets 用特定的导入替换这些语句。

我们创建一个 src 目录并添加一些示例。

创建 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();

使用 package.json 中的构建脚本编译此文件:

npm run build

这将在 lib/promises-example.js 中创建转译后的文件。如果您检查转译后的代码,您会看到 Babel 已经转换了 async/await 语法,并且由于 useBuiltIns: "usage" 和您的 targets 设置,如果 Promise 或 async 函数未完全被您的目标环境(例如 IE 11)支持,它会注入 import 语句,例如 import 'core-js/modules/es.promise.js' 和 import 'regenerator-runtime/runtime.js'。

要在浏览器中运行此代码,通常会使用像 Webpack 或 Parcel 这样的打包工具来解析这些导入并创建一个单独的打包文件。为了进行简单的测试,您可以创建一个 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>

注意:直接包含 babel-polyfill/dist/polyfill.min.js 的旧方法已不再是标准做法。现代工具链依赖于由 @babel/preset-env 和打包工具管理的模块化垫片。

String.prototype.padStart() 和 String.prototype.padEnd() 是在 ES2017 中引入的。

创建 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*****'

运行 npm run build 后,将生成 lib/string-padding-example.js。如果您的 targets 包括不支持字符串填充的浏览器,Babel(通过 core-js)将确保包含必要的垫片(如果可能,也会进行转换)。当在这样的浏览器中打包并运行时,控制台输出将是:

_____Babel
Babel*****

Map 和 Set 是 ES2015 的特性。

创建 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);

在运行 npm run build 后,如果目标环境较旧,lib/collections-example.js 中的转译代码在打包并运行时,将输出:

BabelJS
true
Set size: 3

Array.prototype.includes (ES2016) 和 Array.from (ES2015)。

创建 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']

在运行 npm run build 后,lib/array-methods-example.js 中的转译代码在打包并运行时,将输出:

Includes 3: true
Includes 6: false
Array from string: ["h", "e", "l", "l", "o"]
  • 打包工具至关重要:对于浏览器应用,使用模块打包工具(如 Webpack、Parcel 或 Rollup)是必不可少的。打包工具会解析 Babel 可能添加的 core-js 模块和 regenerator-runtime 的 import 语句,为您的 index.html 创建一个(或几个)JavaScript 文件。
  • 目标环境设置:在 Babel 配置中仔细定义您的 targets。这可以确保垫片仅包含在实际需要它们的浏览器/环境中,从而优化您的打包体积。
  • core-js 版本:始终在 Babel 配置中指定 corejs 版本,以确保行为一致并避免警告。core-js@3 是现代标准。
  • 全局污染:core-js 垫片可能会修改全局对象(例如,Promise、Array.prototype)。这对于应用来说通常没有问题,但对于库来说可能需要考虑。库可能更倾向于使用 @babel/plugin-transform-runtime 来避免全局污染,它提供了不会修改全局对象的辅助函数。

通过利用 @babel/preset-env 与 core-js 协同工作,您可以自信地使用现代 JavaScript API,并确保您的应用在各种环境中具有良好的健壮性和兼容性。有关 core-js 及其功能的详细信息,请访问:https://github.com/zloirock/core-js