登录
Babel文档:https://babeljs.io/docs/en/。
只能说,Babel 的历史太厚重了。
之前的配置中,进行编译使用的是 babel-preset-es2015、babel-preset-stage-2、transform-object-rest-spread 、 @babel/polyfill 等包,现在已经不是这些了,有了新的配置方案,见后面。
以下历史参考自 https://juejin.cn/post/6976501655302832159。
core-js 是 JavaScript 的模块化标准库,包括了 ECMAScript 新 api 的兼容实现。它和 babel 高度集成,是 babel 解决新特性在浏览器中兼容问题的核心依赖。
目前 core-js 的版本是 3.x,与 core-js@2 相比不仅在本身的架构上有重大调整,还对 babel 中的一些插件有重大影响。
这里为了后面方便理解,先说两个名字,一个叫做污染包,一个叫做清洁包,后面马上就会用到。
总之,阅读了下面 的 core-js@2 和 core-js@3 的说明后,会知道 core-js@2 和 core-js@3 各有一个污染包和一个清洁包。
具体污染包和清洁包的实现具体实例,见后面的举例
现阶段能够编译 api 的插件一共有三个,见下:
@babel/polyfill 只能使用 core-js@2 的污染包(@babel/polyfill 包已被 Babel 废弃,不推荐使用)。
@babel/preset-env,根据配置,能使用 core-js@2 或 core-js@3 的污染包。
@babel/plugin-transform-runtime 使用 @babel/runtime-corejs2 或 @babel/runtime-corejs3:
core-js@2 被 @babel/polyfill、@babel/preset-env 和 @babel/runtime-corejs2 (最终会由 @babel/plugin-transform-runtime 使用) 引入,来进行新 api 的兼容处理。
其中有两个核心的模块,其实就是一个是污染包,一个清洁包:
modules:污染全局的 polyfill 模块,供 @babel/polyfill 和 @babel/preset-env 引入。
这个包我叫它 污染包,它主要是采用在全局和实例上添加 api 的方式解决兼容性问题,比如要兼容 Array 的 includes 方法,就直接给 Array 的原型链添加这个方法,会修改原型链。
library:不污染全局的 runtime 模块,供 @babel/runtime-corejs2 引入。
这个包我叫它 清洁包,它主要是采用模拟替换api的方式解决兼容性问题,比如发现代码中使用了 [1, 2].includes(2) 方法,就引入一个方法叫做 _includes,然后把代码部分替换为 _includes.call([1, 2], 2) 的方式,不污染原型链。
core-js@3 放弃了对 @babel/polyfill 的支持,被 @babel/preset-env 和 @babel/runtime-corejs3(最终会由 @babel/plugin-transform-runtime 使用) 引入来进行新api的兼容处理。
由于 core-js@2 包的体积太大(约2M),并且有很多重复的文件被引用,所以 core-js@3 对包进行拆分,其中两个核心,仍然一个是污染包,一个清洁包:
core-js@2分支已经冻结,不会再添加新特性,新特性都会添加到 core-js@3。
为了可以使用更多的新特性,建议大家使用 core-js@3,但我们其实不会直接使用 core-js@3,我们需要使用的是那些引入了 core-js@3 的插件和配置。
关于 core-js 的内容大家先了解这么多,先有个印象,大家只需要记住一点:corejs 才是 api 兼容实现的提供者!
使用污染包的编译前后,编译后的使用 require 引入的 js,就是给 Array 添加了 from 这个静态方法:
// 编译前
const array = Array.from([1, 2, 3, 4, 5])
console.log(array?.[6] || '无')
// 编译后
"use strict";
require("core-js/modules/es6.symbol.js");
require("core-js/modules/es6.array.from.js");
require("core-js/modules/es6.string.iterator.js");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.array.iterator.js");
require("core-js/modules/web.dom.iterable.js");
var array = Array.from([1, 2, 3, 4, 5]);
console.log((array === null || array === void 0 ? void 0 : array[6]) || '无');
使用清洁包的编译前后,可见对应编译前的 Array.form 方法,编译后是引入了一个 _from 来替换使用。
// 编译前
const array = Array.from([1, 2, 3, 4, 5])
console.log(array?.[6] || '无')
// 编译后
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from"));
var array = (0, _from.default)([1, 2, 3, 4, 5]);
console.log((array === null || array === void 0 ? void 0 : array[6]) || '无');
此包已被官方弃用,虽然仍然可以使用,但不建议使用,后面有完全取代的方案。
@babel/polyfill 是一个运行时包,主要是通过核心依赖 core-js@2 来完成对应浏览器不支持的新的全局和实例 api 的添加。
而在升级到 core-js@3 后,如果还要保留 @babel/polyfill 的使用,就要在 @babel/polyfill 中添加 core-js@2 和 core-js@3 切换的选项,这样 @babel/polyfill 中将包含 core-js@2 和 core-js@3 两个包。
出于这个原因,官方决定弃用 @babel/polyfill。
编译语法时,有的环境下可能需要转换几十种不同语法的代码,则需要配置几十个 plugin ,这显然会非常繁琐。
所以,为了解决这种问题,Babel 提供了预设插件机制 preset,preset 中可以 预设置一组插件来便捷的使用这些插件所提供的功能。目前,Babel 官方推荐使用 @babel/preset-env 预设插件。
从 babel@7 开始,已经移除了 @babel/preset-stage-x,所以当前的新版本中直接使用 @babel/preset-env 就行。
@babel/preset-env 主要的作用是用来转换那些已经被正式纳入TC39 中的语法。所以它无法对那些还在提案中的语法进行处理,对于处在 stage 中的语法,需要安装对应的 plugin 进行处理。
意思就是刚刚提出的新语法还无法编译,需要安装语法对应的 plugin 来处理,不过那种太新的语法,不太关注这些的程序员几乎也都不知道。
除了语法转换,@babel/preset-env 另一个重要的功能是对 api 的处理,也就是在代码中引入 polyfill。
但是,@babel/preset-env 默认是不开启处理 api 的功能,只有设置了 useBuiltIns 选项(不为 false )才会开启。
@babel/preset-env 主要还是依赖 core-js 来处理api的兼容性,在升级到 7.4.0 以上的版本以后,既支持 core-js@2,也支持 core-js@3,所以增加了 corejs 的配置来控制所需的版本。
**但注意,根据上面 core-js 的描述,如果使用 @babel/preset-env 来编译API,那只能使用 core-js@2 或 core-js@3 的污染包! **
如果设置了useBuiltIns选项(不为false)就得设置 corejs 版本,否则 babel 将会发出警告。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", // 也可配置成 entry,如果配置为 false 则不需要配置 corejs
"corejs": 3 // 3: 使用 core-js@3,2: 使用 core-js@2
}
]
]
}
下面是 useBuiltIns 的三个值的区别说明。
注意,设置了此项,需要同时设置 corejs 选项。
代码中不需要开发人员主动引入 polyfill 垫片,babel 会自动将代码里已使用到、且 browserslist 环境不支持的 polyfill 导入。
// 编译前
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)
const instance = new Promise((resolve, reject) => {
resolve(123)
})
// 编译后
"use strict";
require("core-js/modules/es.array.copy-within.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
resolve(123);
});
注意,设置了此项,需要同时设置 corejs 选项。
需要在代码运行之前导入,会将 browserslist 环境不支持的所有 polyfill 都导入。
// 编译前
import "core-js/stable";
import "regenerator-runtime/runtime";
// 上面两行是需要开发人员手动引入的 polyfill
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)
const instance = new Promise((resolve, reject) => {
resolve(123)
})
// 编译后
"use strict";
require("core-js/modules/es.symbol.js");
// ... 此处省略400+行代码,全是类似上下的导入语句
require("regenerator-runtime/runtime");
var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
resolve(123);
});
只做了语法转换,不会导入任何 polyfill 进来,并且 corejs 配置将无效。
// 编译前
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)
const instance = new Promise((resolve, reject) => {
resolve(123)
})
const shen = result?.a
// 编译后
"use strict";
var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
resolve(123);
});
var shen = result === null || result === void 0 ? void 0 : result.a;
在使用 @babel/preset-env 提供的全局 api 添加的功能时,由于使用的是污染包,难免会造成文件的体积增加以及api的全局污染。
为了解决这类问题,Babel 提供了另一种 api 转译的方式,引入了 runtime 的概念。
runtime 的核心思想是以引入方法替换的方式来解决兼容性问题。
runtime 包其实有三个:
三个包都依赖 helpers、regenerator-runtime 模块来实现语法的替换,helpers中 提供了一些语法模拟的函数,regenerator-runtime 中实现了 async/await 语法的转换。
所以实际使用时,直接使用 @babel/runtime-corejs3 就行,比如要使用数组的 includes 方法,可以像下面这么写:
import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
_includesInstanceProperty(foo).call(foo, "a");
但这样一个个手动导入很麻烦,这个时候我们就需要借助自动导入插件来帮助我们完成这项工作。
@babel/plugin-transform-runtime 就是为了方便 @babel/runtime 的使用。通过ast的分析,自动识别并替换代码中的新api,解决手动 require 的烦恼。
@babel/plugin-transform-runtime 是开发依赖,编译时负责处理 @babel/runtime,两者是搭配使用的。
下面是使用 @babel/plugin-transform-runtime 来编译 api 的配置,其中 corejs 选项来配置使用的是 @babel/runtime-corejs2 还是 @babel/runtime-corejs3。
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
目前,Babel 处理兼容性问题有两种方案:
@babel/preset-env + corejs@3:实现语法转换 + 在全局和实例上添加api,支持全量加载和按需加载,我们简称 polyfill 方案;
@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime:实现语法转换 + 模拟替换 api,只支持按需加载,我们简称 runtime 方案。
两种方案都依赖核心包 corejs@3,只不过依赖的模块(一个污染包一个清洁包)不同,导致实现方式不同。
所以,polyfill 方案比较适合单独运行的业务项目,如果你是想开发一些供别人使用的第三方工具库,则建议你使用 runtime 方案来处理兼容性方案,以免影响使用者的运行环境。
两种配置的模板在 Babel 配置和使用 的文档中。