@babel/plugin-transform-runtime

@babel/plugin-transform-runtimeopen in new window: 该插件会开启对 Babel 注入的helper codehelper可译为辅助函数)的复用,以节省代码体积。

需要注意的是,使用@babel/plugin-transform-runtime,不会对实例方法比如"foobar".includes("foo")进行转换,因为这将需要对已存在的内置对象进行修改(但你可以使用@babel/polyfill)。

安装

将该插件作为开发依赖安装。

npm install --save-dev @babel/plugin-transform-runtime
1

添加@babel/runtime作为生产依赖,由于它是用于运行时。

npm install --save @babel/runtime
1

该转换插件通常仅用于开发阶段,但是你部署后的代码将依赖运行时@babel/runtime。详见下方的示例。

为什么需要 @babel/plugin-transform-runtime

Babel 使用了一些很小的helpers作为通用函数比如_extend。默认情况下,这些helpers会被添加到需要的每一个文件里。这种代码重复有时是不需要的,尤其是你的应用分散在多个文件里。这也是@babel/plugin-transform-runtime插件出现的原因:所有的helpers将引用@babel/runtime模块以避免在编译输出文件里的代码重复。@babel/runtime将编译到构建输出文件里。

这个转换器的另一个目的是,为你的代码创建一个沙盒环境。若是你使用@babel/polyfill以及它提供的内置对象比如PromiseSetMap,将会污染全局作用域。尽管这对于应用或者命令行工具来说没啥问题,但是若你的代码是打算发布给别人使用的库,或你无法精确地控制你代码将要运行的环境,这就会成为问题。

该转换器将使用core-js替换这些内置对象,因此你可以无缝地使用它们而不必一定引用polyfill

使用

通过 .babelrc(推荐)

添加以下代码到你的.babelrc文件里:

不带参数:

{
  "plugins": ["@babel/plugin-transform-runtime"]
}
1
2
3

带参数(以下是默认值):

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

该插件默认假定所有可以polyfill的 API 都已经由用户提供好了。否则需要开启corejs选项。

通过 CLI

babel --plugins @babel/plugin-transform-runtime script.js
1

通过 Node API

require("@babel/core").transform("code", {
  plugins: ["@babel/plugin-transform-runtime"],
});
1
2
3

选项

corejs

booleannumber,默认是为false

比如['@babel/plugin-transform-runtime', { corejs: 2 }],指定一个数字将引入corejs来重写需要polyfillAPI 的helpers,这需要使用@babel/runtime-corejs2作为依赖,而不是@babel/runtime

译者注

若是不开启corejs选项,在转换时,Babel 使用的一些helper会假设已经存在全局的polyfill;开启之后,Babel 会认为全局的polyfill不存在,并会引入corejs来完成原来需要polyfill才能完成的工作。

指向选项的值为数字,即选择哪个版本的@babel-runtime-corejs:

  • 配置corejs3,需要预先安装@babel/runtime-corejs3
  • 配置corejs2,需要预先安装@babel/runtime-corejs2
  • 配置corejsfalse,需要预先安装@babel/runtime

helpers

boolean,默认是true

切换是否要引入模块来替换内联的 Babelhelpers(比如classCallCheckextends等)。

译者注

若是不开启helpers选项,可能每个文件都会引入同一个helper,导致多个文件里会存在多份同一个helper的代码;开启之后,是通过在每个文件里引入公共模块里的helper,即将helpers提取到了一个公共模块里。

polyfill

Babel 7 移除了该选项,将其作为默认的。

regenerator

boolean,默认是true

切换是否将generator函数转换为使用不污染全局作用的regenerator运行时。

useBuiltIns

Babel 7 移除了该选项,将其作为默认的。

useESModules

boolean,默认是false

启用时,转换使用的helpers将不经过@babel/plugin-transform-modules-commonjs插件处理。这将在类似 Webpack 等模块系统里产生更小的构建输出,因为它不需要保留 CommonJS 的语义代码。

比如,如下是禁用了useESModulesclassCallCheck

exports.__esModule = true;

exports.default = function(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
};
1
2
3
4
5
6
7

启用后:

export default function(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
1
2
3
4
5

absoluteRuntime

booleanstring,默认是false

这允许用户跨越整个项目来运行transform-runtime。默认情况下,transform-runtime直接从@babel/runtime/xxx引入,但是这要求@babel/runtime必须在被编译文件的node_modules里。因此对于嵌套的node_modulesnpm-linked的模块或位于用户项目之外的命令行,以及其他一些情况下,这是有问题的。为了避免担心如何解析runtime模块的位置,这个选项允许用户预先一次解析runtime,之后将runtime的绝对路径插入到输出代码里。

若是文件被编译并在一段时间之后使用,则可以不必使用绝对路径。但是在文件被编译并立即使用的环境里,它们是相当有帮助的。

技术细节

transform-runtime转换器插件会做三件事:

  • 当你使用generators/async函数时,自动引入@babel/runtime/regenerator(可通过regenerator选项切换)
  • 若是需要,将使用core-js作为helpers,而不是假定用户已经使用了polyfill(可通过corejs选项切换)
  • 自动移除内联的 Babel helpers并取而代之使用@babel/runtime/helpers模块(可通过helpers选项切换)

这到底意味着什么呢?基本上,你能无缝地使用PromiseSetMapSymbol等内置对象,以及所有需要使用polyfill的 Babel 特性,而无需导致全局污染,这尤其适合工具库(libraries)的开发。

注意,请确保你将@babel/runtime添加为了生产依赖(dependency)。

regenerator 替换

当你使用generatorasync函数时:

function* foo() {}
1

将生成以下代码:

"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
18
19

这并不理想,因为这要求已经包含了污染全局作用域的regenerator运行时。而用了runtime转换器后,这将编译为:

"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
23
24
25
26
27

这意味着,你可以使用regenerator运行时而不污染你的当前环境。

core-js 替换

有时你可能想使用新的内置对象如PromiseSetMap等,通常你只能通过引入导致全局污染的polyfill来使用。

corejs选项解决了这个问题。该插件会将如下的代码:

var sym = Symbol();

var promise = new Promise();

console.log(arr[Symbol.iterator]());
1
2
3
4
5

转换为:

"use strict";

var _getIterator2 = require("@babel/runtime-corejs2/core-js/get-iterator");

var _getIterator3 = _interopRequireDefault(_getIterator2);

var _promise = require("@babel/runtime-corejs2/core-js/promise");

var _promise2 = _interopRequireDefault(_promise);

var _symbol = require("@babel/runtime-corejs2/core-js/symbol");

var _symbol2 = _interopRequireDefault(_symbol);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

var sym = (0, _symbol2.default)();

var promise = new _promise2.default();

console.log((0, _getIterator3.default)(arr));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这意味着,你可以无缝地使用这些原生的内置对象和静态方法,而不需要担心它们是从哪里来的。

注意,实例方法比如"foobar".includes("foo")将不会被转换。

helper 替换

通常 Babel 会将helpers放置在文件的顶部做一些通用的任务以避免在当前文件里造成代码重复。但是有时在多个文件里,这些helpers仍然会造成一些冗余且不必要的重复。而runtime转换器会以引入一个模块的方式来替换这些helpers

这意味着,以下的代码:

class Person {}
1

通常会转换为:

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person() {
  _classCallCheck(this, Person);
};
1
2
3
4
5
6
7
8
9
10
11

但是使用runtime转换器会将代码转换为下面这样:

"use strict";

var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

var Person = function Person() {
  (0, _classCallCheck3.default)(this, Person);
};
1
2
3
4
5
6
7
8
9
10
11
12
13

译者总结

术语解释

helper是指 Babel 使用的一些辅助工具函数,比如_extend函数把一个对象的属性赋值到另一个对象上,这里的_extend就是helper

再比如class的编译,编译后代码里的_classCallCheck就是helper

// Babel 编译前
class People{
}

// Babel 编译后
'use strict';

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError('Cannot call a class as a function');
    }
}

var People = function People() {
    _classCallCheck(this, People);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@babel/plugin-transform-runtime的作用就是,将这些辅助工具函数转换成引入@babel/runtime模块的形式,进而消除掉各个文件都引入同一辅助工具函数导致的重复。

npm i @babel/runtime -S
npm i @babel/plugin-transform-runtime -D
1
2
module.exports = {
    presets: ['@babel/preset-env'],
    plugins: ['@babel/plugin-transform-runtime']
};
1
2
3
4

安装@babel/plugin-transform-runtime@babel/runtime并配置好之后,再经过 Babel 转换的结果为:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var People = function People() {
  (0, _classCallCheck2["default"])(this, People);
};
1
2
3
4
5
6
7
8
9

_classCallCheck2这个辅助工具函数已经变成从@babel/runtime包中引入,不会再产生单独的工具函数代码。正因为该辅助工具函数是从@babel/runtime包中引入,所以要安装@babel/runtime这个生产依赖包,在项目打包的时候才不会报错。

使用 @babel/plugin-transform-runtime 的原因

  • 若你不想污染全局内置对象
  • 若你是开发工具库
  • 提取出helps作为共用模块,进而缩小最终编译输出的体积

缺点

因为@babel/plugin-transform-runtime没有污染全局内置对象,对于实例方法如"foobar".includes("foo")就无法进行转换或兼容了。

关于 @babel/plugin-transform-runtime 和 @babel/polyfill 的区别

参考:Babel 快速上手使用指南open in new window

@babel/polyfill是引入相关文件来模拟 ES2015+ 的环境进而实现polyfill,但是其会污染全局变量。

@babel/plugin-transform-runtime通过配置corejs选项,提供一种runtimepolyfill

module.exports = {
    plugins: [['@babel/plugin-transform-runtime', { corejs: 2 }]]
};
1
2
3

这里的corejspresets里设置的corejs是不同的,这里的corejs指定了一个runtime-corejs的版本,因此使用时也需要通过 NPM 安装对应的包。

npm i @babel/runtime-corejs2 -S
1

示例

如下以Array.from静态方法为例,说明二者的差异。

// Babel 编译前
const a = Array.from([1])
1
2
// Babel 编译后(使用 @babel/polyfill)
"use strict";

require("core-js/modules/es6.string.iterator");

require("core-js/modules/es6.array.from");

var a = Array.from([1]);
1
2
3
4
5
6
7
8
// Babel 编译后(使用 @babel/plugin-transform-runtime)
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");

var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from"));

var a = (0, _from["default"])([1]);
1
2
3
4
5
6
7
8

经过与@babel/polyfill对比可以发现,@babel/plugin-transform-runtime并没有改变原生的内置Array.from方法,而是创建了一个_from来模拟Array.from的功能,编译前代码里调用Array.from的地方会被编译成调用_from方法,这样做的好处显而易见:不会污染Array上的静态方法from@babel/plugin-transform-runtime提供的runtime形式的polyfill都是这种形式。

除了如Array.prototype.includes这样的内置对象实例方法,其他的内置函数如PromiseSetMap,静态方法如Array.fromObject.assign都可以采用@babel/plugin-transform-runtime的这种形式。

使用场景的区别

  • 写类库时,使用@babel/plugin-transform-runtime
  • 写应用时,使用@babel/polyfill

@babel/plugin-transform-runtime不会污染全局变量,但是会导致多个文件出现重复代码。例如你的库是使用runtime-corejs做 Promise 兼容,但是使用你的库的人可能用的是 bulebird 的兼容库, 这里面就有两份 Promise 代码,虽然不影响使用,但是产生了冗余了。

若是我们在写类库时,使用了Array.prototype.includes这类内置对象实例方法,而我们的依赖库 B 也定义了这个函数,此时若是我们全局引入@babel/polyfill就会出问题,其会覆盖掉依赖库 B 的Array.prototype.includes。若是我们使用@babel/plugin-transform-runtime就安全了(cxl:但是就不能使用Array.prototype.includes这种实例方法了呀..),会默认创建一个沙盒,这种情况 Promise 尤其明显,很多库会依赖于 bluebird 或者其他的 Promise 实现,一般写库的时候不应该提供任何的 polyfill 方案,而是在使用手册中说明用到了哪些新特性,让使用者自己去polyfill