概述
本节主要讲 @babel/plugin-transform-runtime,这个插件到底是用来干什么的呢?
首先,我们需要知道的是在我们用 Babel 进行语法转换的时候(注意,这里是单纯的语法转换,暂时不是用 polyfill 补齐 API), 有时候是需要 Babel 在转换后的代码里面注入一些函数以后,整个转换后的代码才能正常工作的。比如说下面这例子:
Babel 配置文件如下,仅用 @babel/preset-env 做语法转换:
// babel 的配置文件
module.exports = {
presets: [
"@babel/preset-env"
],
plugins: []
};
转换前的代码使用了 class 类语法:
class Person {
sayname() {
return 'name';
}
}
var john = new Person();
console.log(john);
Babel 转码后生成的代码如下:
我们可以看到转换后的代码增加了好几个函数声明,这就是我们注入的函数,我们称之为辅助函数。这些辅助函数就是 @babel/preset-env 在做语法转换的时候自动注入的。
以上方式会有一个问题:在我们正常的前端工程中,少则几十个 js 文件,多则上千个。如果每个文件里都使用了 class 类语法,那会导致转换后的文件都会注入这些相同的函数声明。这会导致我们用构建工具打包出来的包非常大。
那如何解决这个问题呢?我们把这些函数声明都放在一个 npm 包里,需要使用的时候直接从这个包里引入到我们的文件里。通过 webpack 这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了有效复用。
@babel/runtime 就是上面说到的这个包,它把所有辅助函数集成到了一起!!! 怎么把文件中的所有内联辅助函数替换成 @babel/runtime 包里面的函数呢?手动替换显然不太现实,这时候就要借助本文的主角:@babel/plugin-transform-runtime。
既然已经提到了 @babel/plugin-transform-runtime,我们就将 @babel/plugin-transform-runtime 的作用做一个提前总结,@babel/plugin-transform-runtime一共有三个作用:
- 自动移除语法转换后内联的辅助函数
(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代,这个作用前面已经提到了; - 当代码里使用了
core-js的API,自动引入@babel/runtime-corejs3,以此来替代全局引入的core-js; - 当代码里使用了
Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime;
作用一:替换内联辅助函数
在项目中安装一下两个包:
npm i @babel/runtime
npm i -D @babel/plugin-transform-runtime
然后在 Babel 配置文件里面添加一下插件:
module.exports = {
"presets": [
"@babel/env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
再次转换以后,结果如下:
可以看到,它生成的代码都是从 @babel/runtime 里引入辅助函数。
注
每个转换后的文件上部都会注入这些相同的函数声明,那为何不用 webpack 一类的打包工具去掉重复的函数声明,而是要单独再引一个辅助函数包?
webpack 在构建的时候,是基于模块来做去重工作的。每一个函数声明都是引用类型,在堆内存不同的空间存放,缺少唯一的地址来找到他们。所以 webpack 本身是做不到把每个文件的相同函数声明去重的。因此我们需要单独的辅助函数包,这样 webpack 打包的时候会基于模块来做去重工作。
作用二 和 作用三 - API补全和转换
作用二 和 作用三 的其实是做 API 补全和转换,转换过程会对内置对象进行重命名,以防止全局污染。
直接引入 @babel/polyfill 或 直接引入core-js 与 regenerator-runtime 来做全局的 API 补齐,会对运行环境产生污染,可能会改变全局对象及其原型链。
比如说有时候我们不想改变或补齐浏览器的 window.Promise,那就不能直接引入 @babel/polyfill 或者 core-js / regenerator-runtime,这个时候我们就可以使用 @babel/plugin-transform-runtime,它可以对我们代码里ES6的API进行转换。
我们以 Promise 对象为例,假如说我们直接用 @babel/polyfill 或者 core-js / regenerator-runtime 进行 API 补全,这种方式从代码层面来说就是配置 @babel/preset-env 的参数:
presets: [
[
"@babel/preset-env",
{
useBuiltIns: 'usage',
corejs: 3
}
]
]
转换前后如图所示:
接下来我们使用 @babel/plugin-transform-runtime 的 API 补全和转换功能,这种就是去掉 @babel/preset-env 中关于补全 API 的参数,然后给 @babel/plugin-transform-runtime 加上相应的参数:
// babel 的配置文件
module.exports = {
presets: [
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
};
转换前后如图所示:
这里要说明的是,@babel/runtime-corejs3 需要手动安装一下!!!
@babel/runtime-corejs3 是前面提到的 @babel/runtime 的进化版, @babel/runtime 只包含辅助函数,而 @babel/runtime-corejs3 除了包含Babel做语法转换的辅助函数,也包含了core-js的API转换函数,而且同时包含 core-js / regenerator-runtime 的所有功能。
在我们不需要做 API 转换(不开启 core-js 转换)的时候,我们可以使用 @babel/runtime,但是如果需要进行 API 转换,我们就需要使用 @babel/runtime-corejs3,@babel/runtime-corejs3 和 @babel/runtime 只需要安装一个即可。
还有一点要说明的是:@babel/plugin-transform-runtime 对 generators/async 则是默认开启了语法转换功能。 @babel/runtime 和 @babel/runtime-corejs3 都支持这种转换。
⚠️注意!!!
我们来想一个问题,既然明明通过 polyfill 补齐 API 的方式也可以使代码在浏览器正常运行,为什么还要使用这种 API 转换(定义到局部作用域)呢?其实这种 API 转换主要是给开发 JS 库或 npm 包等的人用的,我们的前端工程一般仍然使用 polyfill 补齐 API。
为什么说这种转换 API 的方式是给开发库的人用的呢?试想如果开发 JS 库的人使用 polyfill 补齐 API,我们前端工程也使用 polyfill 补齐 API,但 JS 库的 polyfill 版本或内容与我们前端工程的不一致,那么我们引入该 JS 库后很可能会导致我们的前端工程出问题。
@babel/plugin-transform-runtime 配置项
@babel/plugin-transform-runtime 是否要开启某功能,都是在配置项里设置的,某些配置项的设置是需要安装 npm 包的。默认配置为:
{
"helpers": true,
"corejs": false,
"regenerator": true,
"useESModules": false,
"absoluteRuntime": false,
"version": "7.0.0-beta.0"
}
helpers
该项是用来设置是否要自动引入辅助函数包。
corejs
跟 @babel/preset-env 里面的配置是一样的,取值为 false | 2 | 3。在前端业务项目里,我们一般对 corejs 取 false,即不对 Promise 这一类的 API 进行转换。而在开发 JS 库的时候设置为 2 或 3。
regenerator
是否转换 generators/async。
useESModules
该项用来设置是否使用 ES6 的模块化用法。在用 webpack 一类的打包工具的时候,我们可以设置为 true,以便做静态分析。
absoluteRuntime
该项用来自定义 @babel/plugin-transform-runtime 引入@babel/runtime/ 模块的路径规则,取值是布尔值或字符串。没有特殊需求,我们不需要修改,保持默认 false 即可。
version
该项主要是和 @babel/runtime 及其进化版 @babel/runtime-corejs2、@babel/runtime-corejs3的版本号有关系,这三个包我们只需要根据需要安装一个。
我们把安装的 npm 包的版本号设置给 version 即可。例如安装的@babel/runtime-corejs3 版本是 ^7.10.4,那么配置项version也取 ^7.10.4。
其实该项不填取默认值就行,目前填写版本号主要是可以减少打包体积。
总结
- 要使用
@babel/plugin-transform-runtime插件,其实只有一个npm包是必须要装的,那就是它自己@babel/plugin-transform-runtime。 - 对于
@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3,我们只需要根据自己的需要安装一个。 - 在安装
@babel/preset-env的时候,其实已经自动安装了@babel/runtime,不过在项目开发的时候,我们一般都会再单独安装一遍@babel/runtime。 - 如果你不需要对
core-js做API转换,那就安装@babel/runtime并把corejs配置项设置为false即可。 - 如果你需要用
core-js2做API转换,那就安装@babel/runtime-corejs2并把corejs配置项设置为2即可。 - 如果你需要用
core-js3做API转换,那就安装@babel/runtime-corejs3并把corejs配置项设置为3即可。