去年这个时候,曾经写过一篇文章 Polyfill 的前世今生,总结了常见的 polyfill 方案。受限于当时的认知以及 Babel 版本,有很多疏漏,觉得有必要更新一篇目前(2020.03) polyfill 的一些进展。

core-js@3

在我写下上一篇文章之后两周,2019.03.19,Babel@7.4.0 正式发布了。在 Babel@7.4.0 中,提供了对管道运算符、私有方法、TypeScript 3.4 等的支持,同时,polyfill 开始支持 core-js@3。对于 core-js ,可能有些人还很陌生,根据 npmtrends 的统计, core-js 事实上已经是目前最流行的 polyfill 方案了,@babel/polyfill 就是靠它来转换代码的,只是很多人可能没有留意到其实自己已经在间接使用它了。回想一下执行 npm install 的时候,是不是对 As advertising: the author is looking for a good job -) 似曾相识? 这条广告还引发过技术人员能不能在 npm log 中为自己打广告的争论, npm fund 也是为此而生。

提到这个,顺便想起了前几天在这条 issue 中看到的一件事, core-js 的作者看起来因交通肇事被判处了 1.5 年的监禁,希望他好好改造重新做人 →_→

升级指南

babel.config.json 与 .babelrc

在 babel@6.x 版本中,我们通常使用 .babelrc 作为 babel 的配置文件,但是在实践中,遇到了很多问题。现在 babel 团队建议使用这两种方式来对 babel 做出配置:

Project-wide configuration

  • babel.config.json files, with the different extensions

File-relative configuration

  • .babelrc.json files, with the different extensions
  • package.json files with a ‘babel’ key

更详细的信息可以参考这里: https://babeljs.io/docs/en/config-files

Browserslist

Babel 团队建议使用 .browserslistrc 文件来指定需要兼容到的浏览器,这样该配置可以被 autoprefixer、 eslint 等其他工具共用。

1
2
3
4
// 示例:
> 1%
last 2 version
not ie <= 10

Polyfill

在之前的版本中,如果我们将 babel 的 useBuiltIns 属性设置为 entryfalse,我们需要在代码中手动引入 @babel/polyfill,现在则只需要引入 regenerator 和 core-js 就可以了。在代码入口文件前引入它们,可以模拟完整的 ES2015+ 环境(不包含 < Stage 4 的提案)。

1
2
3
4
5
6
// before
import "@babel/polyfill";

// after
import "core-js/stable";
import "regenerator-runtime/runtime";

而如果我们对构建产物的大小有限制,我们可以继续使用 useBuiltIns: usage 来按需导入所需的 polyfill 内容。

1
2
3
4
5
6
7
8
9
// babel.config.json
presets: [
[
"@babel/preset-env", {
"useBuiltIns": "usage"
"corejs": 3
}
]
]

transform-runtime

我们曾经提到过,transform-runtime 的方案不支持如 "foobar".includes("foo") 这样的实例方法。在 core-js@3 中,这个问题得到了解决。

1
2
npm remove @babel/runtime-corejs2
npm install --save @babel/runtime-corejs3
1
2
3
4
5
6
7
8
// babel.config.json
plugins: [
[
"@babel/transform-runtime", {
"corejs": 3
}
]
];

如果我们需要支持 Stage < 4 阶段的提案,可以配置 proposals 属性来实现:

1
2
3
4
5
6
7
8
9
10
11
// babel.config.json
"plugins": [
[
"@babel/plugin-transform-runtime", {
"corejs": {
"version": 3,
"proposals": true
}
}
]
]

但是,@babel/plugin-transform-runtime 依然存在问题:它不能像 @babel/preset-env 一样指定 target。也就是说,哪怕针对现代浏览器,它依然会注入大量并不需要的内容,从而使得构建产物的体积大大增加。针对这个问题, babel 团队也还在思考中: https://github.com/babel/babel/issues/10008

总结

在 Babel < 7.4.0 的时代,我们可能都听说过,由于 @babel/polyfill 有全局污染,如果我们在开发一个库/框架,我们只能使用 @babel/runtime 来进行部分 polyfill,其余部分需要库的使用者自行通过 @babel/polyfill 来完成。

现在,如果我们对文件大小不是特别敏感,我们可以不用顾虑这个问题了,直接使用 @babel/preset-env 来进行语法转换,剩余部分 @babel/runtime 即可完成。附上一份完整的 babel.config.json 配置供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// babel.config.json
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-transform-runtime", {
"useESModules": true,
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}

如果我们的环境对性能有极致要求,我们则需要使用 @babel/preset-env 并设置 useBuiltIns: "usage" 来完成 ES6+ 的转换。而想要避免全局污染,则需手动引入我们代码中使用到的新特性相关的插件,如 @babel/transform-es2015-classes@babel/transform-es2015-arrow-functions 等。附上一份 @babel/preset-env 配置参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage",
"corejs": 3,
// "targets": "> 1%, not dead" // 推荐使用全局 browserslist
}
]
]
}