# Babel Regenerator and Polyfills
在写上一篇文章 升级 Babel 7 的过程中,发现 @babel/plugin-transform-runtime
(opens new window) 插件除了添加 helper 函数之外,还可以转换生成器函数和添加 polyfills。但是 @babel/plugin-transform-regenerator
(opens new window) 插件也可以转换生成器函数,那这两者有什么区别呢?同时 @babel/preset-env
(opens new window) 通过 core-js
(opens new window) 也能添加 polyfills,那他们之间又有什么区别呢?这篇文章我们来探讨一下。
下面的讨论是基于 Babel 7.23+
# Regenerator
首先我们来看看 Regenerator.
# @babel/plugin-transform-runtime
@babel/plugin-transform-runtime
(opens new window) 文档上说,设置 { "regenerator": true }
将使用 regenerator runtime (opens new window) 转换生成器函数,而不污染全局作用域。
比如要转换的代码为
// script.js
function* foo() {}
2
当 regenerator
为 false
,转换后的代码为
"use strict";
var _marked = [foo].map(regeneratorRuntime.mark);
function foo() {
return regeneratorRuntime.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
case "end":
return _context.stop();
}
}
},
_marked[0],
this
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当 regenerator
为 true
,转换后的代码为
helper: true
"use strict";
var _regenerator = require("@babel/runtime/regenerator");
var _regenerator2 = _interopRequireDefault(_regenerator);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var _marked = [foo].map(_regenerator2.default.mark);
function foo() {
return _regenerator2.default.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
case "end":
return _context.stop();
}
}
},
_marked[0],
this
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
但是我发现当没有配置 @babel/preset-env
(opens new window) 或者说没有配置 @babel/plugin-transform-regenerator
(opens new window) 时,根本无法转换生成器函数,下面是我的配置
// .browserslistrc
not ie <= 8
2
// babel.config.json
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true,
"version": "^7.23.8"
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
或者
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"exclude": ["@babel/plugin-transform-regenerator"],
"useBuiltIns": "usage",
"corejs": "^3.35.0"
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true,
"version": "^7.23.8"
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用 CLI 也无法转换
$ npx babel --plugins @babel/plugin-transform-runtime script.js
使用 Babel 提供的在线工具 Try it out (opens new window) 也是一样的。
# @babel/plugin-transform-regenerator
但是当我使用 @babel/plugin-transform-regenerator
(opens new window) 时,能使用 regenerator runtime (opens new window) 转换了生成器函数,并且也没有污染全局作用域。
配置文件为
{
"plugins": [["@babel/plugin-transform-regenerator"]]
}
2
3
转换后的代码为
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function () {
/* 这里省略了函数实现 */
}
var _marked = /*#__PURE__*/_regeneratorRuntime().mark(foo);
function foo() {
return _regeneratorRuntime().wrap(function foo$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
case "end":
return _context.stop();
}
}, _marked);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
当和 @babel/plugin-transform-runtime
(opens new window) 一起使用时
{
"plugins": [
"@babel/plugin-transform-regenerator",
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": false,
"version": "^7.23.8"
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
转换后的代码为
import _regeneratorRuntime from "@babel/runtime/helpers/regeneratorRuntime";
var _marked = /*#__PURE__*/_regeneratorRuntime().mark(foo);
function foo() {
return _regeneratorRuntime().wrap(function foo$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
case "end":
return _context.stop();
}
}, _marked);
}
2
3
4
5
6
7
8
9
10
11
# 结论
所以结论就是 Babel 文档错误或者没有更新,@babel/plugin-transform-runtime
(opens new window) 插件无法转换生成器函数,而是 @babel/plugin-transform-regenerator
(opens new window) 插件可以转换生成器函数,可以搭配 @babel/plugin-transform-runtime
(opens new window) 添加 helper 函数。
@babel/plugin-transform-runtime
(opens new window) 插件的 regenerator
选项没有任何作用,详情请参考我提的 issues-16260 (opens new window).
# Polyfills
现在我们来讨论 Polyfills
# @babel/preset-env
我们都知道 Babel 7 通过 @babel/preset-env
(opens new window) 和 core-js
(opens new window) 添加 polyfills,以 { "useBuiltIns": "usage" }
为例
配置文件
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "^3.35.0"
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
要转换的代码
// script.js
const a = new Set();
2
转换后的代码
"use strict";
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.set.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
var a = new Set();
2
3
4
5
6
7
8
这里有几个注意点:
Set
添加在全局作用域里- 正如 issues-16149 (opens new window) 里说的,当使用
{ "useBuiltIns": "usage" }
和@babel/plugin-transform-runtime
(opens new window)({ "helpers": true }
)时,@babel/runtime (opens new window) 里面的 helper 函数没有被 polyfill,可能在旧的浏览器里出现问题。 - 解决办法可以是使用
{ "useBuiltIns": "entry" }
、将@babel/runtime
映射为@babel/runtime-corejs3
或者转义@babel/runtime
。
# @babel/plugin-transform-runtime
@babel/plugin-transform-runtime
(opens new window) 通过 core-js
选项也能添加 polyfills
首先使用 @babel/runtime-corejs3
替换 @babel/runtime (opens new window)
$ npm install --save @babel/runtime-corejs3
配置文件
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true,
"corejs": 3,
"version": "^7.23.8"
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
转换后的代码
import _Set from "@babel/runtime-corejs3/core-js-stable/set";
const a = new _Set();
2
从 @babel/runtime-corejs3
引入 _Set
,没有污染全局作用域
# 结论
综上所述目前有三种方式使用 polyfills
- 使用
@babel/preset-env
(opens new window),带有选项{ "useBuiltIns": "entry" }
- 使用
@babel/preset-env
(opens new window),带有选项{ "useBuiltIns": "usage" }
- 使用
@babel/plugin-transform-runtime
(opens new window),带有选项{ "corejs": 3 }
前两者使用全局作用,多用于应用中。最后一项不使用全局作用域,多用于 library。
目前 Babel polyfills 的方案有下面的问题
@babel/preset-env
(opens new window) 没有办法不污染全局作用域- 只能使用
core-js
(opens new window) 来提供 polyfills,虽然core-js
(opens new window) 是一个很好的 polyfills,但它可能并不能满足所有用户的需求
所以 Babel 正打算推出全新的解决方案 babel-polyfills
(opens new window),注意这不是原来的 babel-polyfill
或 @babel/polyfill
babel-polyfills
(opens new window) 提供三种接入 polyfill 的方式:usage-pure
、 usage-global
、entry-global
和多个 polyfill 库,比如 babel-plugin-polyfill-corejs3
(opens new window),甚至能自定义 polyfills。现在可以这样来使用 polyfills
{
"targets": { "firefox": 42 },
"presets": ["@babel/preset-env"],
"plugins": [
["polyfill-corejs3", {
"method": "usage-global"
}]
]
}
2
3
4
5
6
7
8
9
这个方案还没有最终确定,可能会有所改变
# References
@babel/plugin-transform-runtime
(opens new window)@babel/plugin-transform-regenerator
(opens new window)@babel/preset-env
(opens new window)core-js
(opens new window)@babel/runtime
(opens new window)babel-polyfills
(opens new window)babel-plugin-polyfill-corejs3
(opens new window)- issues-16149 (opens new window)
- issues-16260 (opens new window)