preset-env和plugin-transform-runtime的异同点
本文参考了但不限于以下文章,参考文章有些纰漏在本文已纠正
useBuiltIns 和 @babel/plugin-transform-runtime 是互斥的…
Babel 不同配置的实验报告]
@babel/preset-env 与@babel/plugin-transform-runtime 使用及场景区别
babel-plugin-transform-runtime官网
写在前面
babel一直在迭代,配置项也一直在变。其中配置有两个重要的概念:预设、插件。
这两个概念中又有两个高频词汇:@babel/preset-env和@babel/plugin-transform-runtime。
几个月前我以为我弄懂了这两个东西,并且实践出了一个最优配置。
直到有一天我发现打包产物比我预期大,还发现了一个长久以来大家都存在的误区。
具体代码、结论都在文末。本文默认读者对babel的polyfill有基本的了解。
基本流程:代码 → babel → 产物
一. 使用预设(preset)
-
@babel/preset-env默认配置只会处理ES6+新增的语法,如箭头函数、let、const。module.exports = { presets: ['@babel/preset-env'], }; -
要实现实现新增的内置基本函数(类)、实例方法、类静态方法、生成器函数,代码中按需引用
core-js、regenerator-runtime/runtime(以下统称垫片总入口文件,老版本是@babel/polyfill,@babel7+版本已不推荐使用)。总体分如下2步骤:
-
在配置中添加
preset的useBuiltIns参数:module.exports={ presets: [ [ '@babel/preset-env', { useBuiltIns: false // false是默认值 } ] ] }useBuiltIns有三个选项:
false(默认值):不处理polyfill文件,不管代码中是引入了import "core-js";import 'regenerator-runtime/runtime';。'usage':代码中不需要写垫片总入口文件,因为babel会按需引用对应的polyfill。'entry':代码顶部必须写垫片总入口文件(手动引入)。但是会把垫片总入口文件按照目标浏览器的功能需要,转换成大量polyfill的引用。
-
按照
useBuiltIns配置不同,可选在代码顶部引入垫片总入口文件:import "core-js" import 'regenerator-runtime/runtime' // 其他代码 ...
PS: 如果你非要引入垫片总入口文件,
useBuiltIns的3个选项按产物大小排序是:false>='entry'>'usage'。 -
-
corejs就是各个api的polyfill的集合,由于corejs@2已经不再添加新特性,如Array.prototype.flat(),所以我们要用corsjs@3:module.exports = { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 // 默认是2 } ] ] }配置之后产物中会生成垫片文件的引用。(但是这些垫片会污染全局变量,这个后文中第三点会解决。)
二. 优化帮助函数(使用插件@babel/plugin-transform-runtime)
场景:
如果a.js和b.js中都用到了类(如 new Promise()),那么在babel转译完2个js之后,2个产物文件中都会有一个_createClass的帮助函数来实现特性的兼容。如果转译的文件过多,就会出现多个_createClass帮助函数,这显然就不合理。
解决办法:
使用 @babel/plugin-transform-runtime 插件(为了行文方便,后文把 @babel/plugin-transform-runtime 插件简称为btr插件。)
-
此插件的功能:1. 按需引入
ESNextapi的垫片,并且不污染全局。 2. 抽离公共的帮助函数。后文把此2点特性称之为沙箱化。 -
使用
btr插件后(默认配置),产物中会引入@babel/runtime/xxxx等垫片,即生产环境使用@babel/runtime去实现特性的兼容。
抽离帮助函数
- 安装依赖,要安装
@babel/plugin-transform-runtime(开发时使用的)。@babel/runtime(生产用到)。(根据插件自身的corejs配置不同,这里可替换为@babel/runtime-corejs2、@babel/runtime-corejs3,详见后文)
- 配置:
module.exports = { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 } ] ], plugins: [ ['@babel/plugin-transform-runtime'] ], }; - 上述编译时会创建一个沙箱环境,各个产物文件最终用的帮助函数都是同一个,防止污染到全局变量。如编译产物中的
class帮助函数:var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
三. ESNest的API沙箱化
问题
preset-env并没有对 Promise 等api的变量名做沙箱化处理,生成的垫片会污染全局变量。
解决:
上述btr插件示例只对帮助函数做了沙箱化处理,要想给ESNext的API也做沙箱化处理,需要给btr插件加一个corejs的配置项:
module.exports = {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-transform-runtime', {
'corejs': 3 // 默认值false,使用2、3时,会抽离ESNEXT的api到沙箱环境
}]
]
};插件的corejs选项有3个:
false(默认值):仅支持帮助函数的沙箱化2: 除了帮助函数,还支持全局变量(例如Promise)和静态属性(例如Array.from)沙箱化3: 除了上述的,还支持实例属性(例如[].includes)沙箱化
注意,一旦btr插件的corejs配置不为false:
- 默认就有
preset-env的useBuiltIns: usage按需引用的效果。
做个测试,btr插件的corejs配置不为false时:
preset-env的useBuiltIns: usage配置 | 垫片总入口文件 | 结果 |
|---|---|---|
| 无 | 手动引入 | 1. corejs全量引入。2. 还重复引入了用到的api垫片(来自插件) |
| entry | 手动引入 | 1. corejs根据浏览器配置引用。2. 还重复引入了用到的api垫片(来自插件) |
| usage | 手动引入 | 只输出了来自插件api垫片(按需引用) |
| 无 | 无 | 只输出了来自插件api垫片(按需引用) |
| entry | 无 | 只输出了来自插件api垫片(按需引用) |
| usage | 无 | 只输出了来自插件api垫片(按需引用) |
所以关于btr插件的结论【划重点!!!】:
- 一旦
btr插件配置了corejs(不为false),就没必要给preset-env配置corejs和useBuiltIns了。 - 更不要在代码中引入垫片总入口文件,一旦写了,产物就会被重复引入polyfill文件。
四. 其他
browserslistrc
.browserslistrc对plugin-transform-runtime和preset-env都生效。
细节
要注意一点就是useBuiltIns: 'usage'不会处理第三方依赖包的引入模块,所以如果第三方依赖包使用了ESNext api而未处理兼容性的话,可能会出bug。
关于两个corejs配置
preset-env的corejs最终生成污染全局环境的垫片。plugin-transform-runtime的corejs也是生成垫片,不过还把垫片沙箱化了。- 同时配这两个地方的
corejs配置项会带来极大的心智负担,极力推荐只配一个!
两个配置的输出产物与生产依赖
-
preset-env:以
Promise为例:corejs配置项 转译产物 生产需依赖 2(默认值) require("core-js/modules/es6.promise.js")npm install --save core-js@23 require("core-js/modules/es.promise.js")npm install --save core-js@3 -
plugin-transform-runtime:以
Promise为例:corejs配置项 转译产物 生产需依赖 false(默认值) 无 npm install --save @babel/runtime2 _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"))npm install --save @babel/runtime-corejs23 _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"))npm install --save @babel/runtime-corejs3
五. 终极配置
没有所谓的终极配置,因为babel一直在更迭。这里只是暂时的一个比较合适的配置:
-
使用
preset-env时,需要安装依赖项:@babel/core@babel/cli@babel/preset-envcore-js@3(生产用到。如果配置了preset-env的corejs,转译产物会依赖此包)
如果用到
@babel/plugin-transform-runtime,除了btr插件本身:- 根据
btr插件的corejs配置不同,@babel/runtime、@babe/runtime-corejs2、@babel/runtime-corejs3这三个库三选一即可,帮生产环境依赖。 - 并且不再需要
core-js@3了(因为没有用到preset-env的corejs配置)【划重点!】。
-
配置文件:
babel.config.jsmodule.exports = { presets: ['@babel/preset-env'], plugins: [ ['@babel/plugin-transform-runtime', { 'corejs': 3 // 默认值false }] ] };就这么简单。懒得动脑子的话,记住
corejs只在配置文件中出现一次就行。
一些用来测试的文件
.browserslistrc# chrome 31 # 不支持class chrome 50 # 不支持promise # chrome 67 # 支持promisebabel.config.jsmodule.exports = { presets: [ [ "@babel/preset-env", { corejs: 3 }, ], ], plugins: [ ['@babel/plugin-transform-runtime', { 'corejs': 3 // 默认值false }] ] };src/index.jsconst p1 = new Promise(); class A { #a = 1 } const a = new A()- 指令:
npx babel src/*.js --out-dir lib/