# 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() {}
1
2

regeneratorfalse,转换后的代码为

"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
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

regeneratortrue,转换后的代码为

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
  );
}
1
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
1
2
// babel.config.json
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "helpers": true,
        "regenerator": true,
        "version": "^7.23.8"
      }
    ]
  ]
}
1
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"
      }
    ]
  ]
}
1
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
1

使用 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"]]
}
1
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);
}
1
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"
      }
    ]
  ]
}
1
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);
}
1
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"
      }
    ]
  ]
}

1
2
3
4
5
6
7
8
9
10
11
12

要转换的代码

// script.js
const a = new Set();
1
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();
1
2
3
4
5
6
7
8

这里有几个注意点:

  1. Set 添加在全局作用域里
  2. 正如 issues-16149 (opens new window) 里说的,当使用 { "useBuiltIns": "usage" }@babel/plugin-transform-runtime (opens new window){ "helpers": true })时,@babel/runtime (opens new window) 里面的 helper 函数没有被 polyfill,可能在旧的浏览器里出现问题。
  3. 解决办法可以是使用 { "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
1

配置文件

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "helpers": true,
        "regenerator": true,
        "corejs": 3,
        "version": "^7.23.8"
      }
    ]
  ]
}
1
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();
1
2

@babel/runtime-corejs3 引入 _Set,没有污染全局作用域

# 结论

综上所述目前有三种方式使用 polyfills

前两者使用全局作用,多用于应用中。最后一项不使用全局作用域,多用于 library。

目前 Babel polyfills 的方案有下面的问题

所以 Babel 正打算推出全新的解决方案 babel-polyfills (opens new window),注意这不是原来的 babel-polyfill@babel/polyfill

babel-polyfills (opens new window) 提供三种接入 polyfill 的方式:usage-pureusage-globalentry-global 和多个 polyfill 库,比如 babel-plugin-polyfill-corejs3 (opens new window),甚至能自定义 polyfills。现在可以这样来使用 polyfills

{
  "targets": { "firefox": 42 },
  "presets": ["@babel/preset-env"],
  "plugins": [
    ["polyfill-corejs3", {
      "method": "usage-global"
    }]
  ]
}
1
2
3
4
5
6
7
8
9

这个方案还没有最终确定,可能会有所改变

# References