此项目旨在加深对 Babel 的了解。
$ npm init -y安装两个包,其中 @babel/core 是 Babel 的核心库,@babel/cli 用于在命令行中使用 babel 命令编译文件:
$ npm i @babel/core @babel/cli -D我们创建一个 src/index.js 文件,在里面编写如下内容:
// src/index.js
const sym = Symbol();
const promise = Promise.resolve();
const arr = ["arr", "yeah!"];
const check = arr.includes("yeah!");
class Person { };
new Person();
console.log(arr[Symbol.iterator]());然后我们在 package.json 中添加一个 npm script ,将 src 下的文件全部编译到 lib 目录下:
"scripts": {
"compile": "babel src --out-dir lib"
},然后我们在没有安装任何 plugin 的情况下进行编译:
$ npm run compile可以看到就是原样输出:
// lib/index.js
const sym = Symbol();
const promise = Promise.resolve();
const arr = ["arr", "yeah!"];
const check = arr.includes("yeah!");
class Person {}
;
new Person();
console.log(arr[Symbol.iterator]());接下来我们安装 @babel/preset-env 用于转换 ES2015+ 语法,以及 core-js 对 API 进行 polyfill :
$ npm i @babel/preset-env core-js -D然后我们在根目录下创建 babel.config.js ,编写内容如下:
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: 3
}
]
],
plugins: []
}执行编译,查看编译后的文件,可以看到 class 是整个被编译了,然后 Symbol 、Promise 、includes 、iterator 等 API 都是采用全局污染的方式处理:
"use strict";
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var sym = Symbol();
var promise = Promise.resolve();
var arr = ["arr", "yeah!"];
var check = arr.includes("yeah!");
var Person = function Person() {
_classCallCheck(this, Person);
};
;
new Person();
console.log(arr[Symbol.iterator]());从上面的编译结果可以看出,polyfill 被添加到全局范围,会存在原型链污染的问题。这样一来如果还用到了 bluebird 之类的第三方 polyfill 可能会受到影响。为了避免这种情况,我们可以使用 @babel/plugin-transform-runtime 插件:
$ npm i @babel/plugin-transform-runtime -D然后在 babel.config.js 中添加如下配置:
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: 3
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
// false 从 @babel/runtime 中引入 helper 函数
// 2 从 @babel/runtime-corejs2 中引入 helper 函数和 polyfill
// 3 从 @babel/runtime-corejs3 中引入 helper 函数和 polyfill
corejs: 3,
helpers: true,
regenerator: true,
useESModules: true,
},
],
]
}再次执行编译,结果如下:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/classCallCheck"));
var _symbol = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/symbol"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _getIterator2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/get-iterator"));
var sym = (0, _symbol["default"])();
var promise = _promise["default"].resolve();
var arr = ["arr", "yeah!"];
var check = (0, _includes["default"])(arr).call(arr, "yeah!");
var Person = function Person() {
(0, _classCallCheck2["default"])(this, Person);
};
;
new Person();
console.log((0, _getIterator2["default"])(arr));对比上一次编译可以看出,polyfill 不再添加到全局范围,因此不会产生原型链污染的问题。另外 @babel/plugin-transform-runtime 从 @babel/runtime-corejs3 统一引入 hepler 函数,而不是将 helper 函数直接定义在当前模块内,例如上面编译结果中的 _classCallCheck2 ,这在一定程度上可以减小打包后的体积,避免某些模块重复打包。
这边总结下 @babel/plugin-transform-runtime 主要的三个作用:
- 自动引入
@babel/runtime/regenerator,当你使用了generator/async函数 (通过regenerator选项打开,默认为true) - 提取一些 babel 中的 helper 函数来达到减小打包体积的作用
- 如果开启了
corejs选项(默认为false),会自动建立一个沙箱环境,避免和全局引入的 polyfill 产生冲突
看大佬的文章说 @babel/plugin-transform-runtime 不会对实例方法进行 polyfill ,例如数组的 includes 和 filter ,但是对静态方法会进行 polyfill ,例如 Array.isArray 。
经过本人实验,这是在 corejs 配置项设为 2 的结果:
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _isArray = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/is-array"));
var arr = ["arr", "yeah!"];
var check = arr.includes("yeah!");
(0, _isArray["default"])([1, 2]);不过 corejs 设为 3 之后,实例方法就会被 polyfill :
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
var arr = ["arr", "yeah!"];
var check = (0, _includes["default"])(arr).call(arr, "yeah!");
(0, _isArray["default"])([1, 2]);