Babel
babel/core
、Polyfill
、babel/plugin-transform-runtime
Babel 能够做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性(@babel/polyfill 模块)
- 源码转换(codemods)
核心 @babel/core
Babel 的核心功能包含在 @babel/core 模块中。不安装 @babel/core,无法使用 babel 进行编译。
Polyfill
@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,可以模拟完整的 ES2015+ 环境(不包含第4阶段前的提议)。 使我们能使用类似 Promise 和 WeakMap 构造函数、Array.form 或 Object.assign 的静态方法、Array.prototype.includes 之类的实例方法以及生成器函数(前提是使用了 @babel/plugin-transform-regenerator 插件)。为了添加这些功能,polyfill 将添加到全局范围和类似 String 这样的内置原型中。但会对全局环境造成污染。
首先,安装 @babel/polyfill 依赖:
1 | npm install --save @babel/polyfill |
我们需要将完整的 polyfill 在代码之前加载,修改我们的 src/index.js:
1 | import '@babel/polyfill'; |
webpack 中进行配置
例如:
1 | entry: [ |
polyfills.js 文件内容如下:
1 | //当然,还可能有一些其它的 polyfill,例如 stage 4之前的一些 polyfill |
如果完整的引入 @babel/polyfill 会导致我们最终构建出的包的体积增大
@babel/polyfill的包大小为89K (当前 @babel/polyfill 版本为 7.7.0)。
@babel/preset-env 提供了一个 useBuiltIns 参数,设置值为 usage 时,就只会包含代码需要的 polyfill 。有一点需要注意:配置此参数的值为 usage ,必须要同时设置 corejs (如果不设置,会给出警告,默认使用的是“corejs”: 2) ,注意: 这里仍然需要安装 @babel/polyfill(当前 @babel/polyfill 版本默认会安装 “corejs”: 2):
首先说一下使用 core-js@3 的原因,core-js@2 分支中已经不会再添加新特性,新特性都会添加到 core-js@3。例如你使用了 Array.prototype.flat(),如果你使用的是 core-js@2,那么其不包含此新特性。为了可以使用更多的新特性,建议大家使用 core-js@3。
安装依赖依赖:
1 | npm install --save core-js@3 |
现在,修改 Babel 的配置文件如下:
1 | //.babelrcconst presets = [ |
Babel 会检查所有代码,以便查找在目标环境中缺失的功能,然后仅仅把需要的 polyfill 包含进来。
例如,src/index.js 代码不变:
1 | const isHas = [1,2,3].includes(2); |
我们看看编译出来的文件 (lib/index):
1 | "use strict"; |
同样的代码,我们用 webpack 构建一下(production 模式),能看到最终的代码大小仅为: 20KB。而如果我们引入整个 @babel/polyfill 的话,构建出的包大小为:89KB
前面曾提到,在 useBuiltIns 参数值为 usage 时,仍然需要安装 @babel/polyfill,虽然我们上面的代码转换中看起来并没有使用到,但是,如果我们源码中使用到了 async/await,那么编译出来的代码需要 require(“regenerator-runtime/runtime”),在 @babel/polyfill 的依赖中,当然啦,你也可以只安装 regenerator-runtime/runtime 取代安装 @babel/polyfill。
下面我要说的内容,也许你已经知道,也许你还不知道,这都不重要,但是此刻起,你要知道了: Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。
假如,我们的 src/index.js 是这样的:
1 | class Point { |
编译出来的 lib/index.js,如下所示:`
1 | "use strict"; |
看起来,似乎并没有什么问题,但是你想一下,如果你有10个文件中都使用了这个 class,是不是意味着 _classCallCheck、_defineProperties、_createClass 这些方法被 inject 了10次。这显然会导致包体积增大,最关键的是,我们并不需要它 inject 多次。
这个时候,就是 @babel/plugin-transform-runtime 插件大显身手的时候了,使用 @babel/plugin-transform-runtime 插件,所有帮助程序都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积。
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件。
另外,@babel/plugin-transform-runtime 需要和 @babel/runtime 配合使用。
首先安装依赖,@babel/plugin-transform-runtime 通常仅在开发时使用,但是运行时最终代码需要依赖 @babel/runtime,所以 @babel/runtime 必须要作为生产依赖被安装,如下 :
1 | npm install --save-dev @babel/plugin-transform-runtime |
除了前文所说的,@babel/plugin-transform-runtime 可以减少编译后代码的体积外,我们使用它还有一个好处,它可以为代码创建一个沙盒环境,如果使用 @babel/polyfill 及其提供的内置程序(例如 Promise ,Set 和 Map ),则它们将污染全局范围。虽然这对于应用程序或命令行工具可能是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。
@babel/plugin-transform-runtime 会将这些内置别名作为 core-js 的别名,因此您可以无缝使用它们,而无需 polyfill。
修改 .babelrc 的配置,如下:
1 | //.babelrc |
重新编译 npm run compiler , 现在,编译出来的内容为(lib/index.js):
1 | "use strict"; |
可以看出,帮助函数现在不是直接被 inject 到代码中,而是从 @babel/runtime 中引入。前文说了使用 @babel/plugin-transform-runtime 可以避免全局污染,我们来看看是如何避免污染的。
修改 src/index.js 如下:
1 | let isHas = [1,2,3].includes(2); |
编译出来的代码如下(lib/index.js):
1 | "use strict"; |
Array.prototype 上新增了 includes 方法,并且新增了全局的 Promise 方法,污染了全局环境,这跟不使用 @babel/plugin-transform-runtime 没有区别嘛。
如果我们希望 @babel/plugin-transform-runtime 不仅仅处理帮助函数,同时也能加载 polyfill 的话,我们需要给 @babel/plugin-transform-runtime 增加配置信息。
首先新增依赖 @babel/runtime-corejs3:
1 | npm install @babel/runtime-corejs3 --save |
修改配置文件如下(移除了 @babel/preset-env 的 useBuiltIns 的配置,不然不就重复了嘛嘛嘛,不信的话,你用 async/await 编译下试试咯):
1 | { |
然后重新编译,看一下,编译出来的结果(lib/index.js):
1 | "use strict"; |
可以看出,没有直接去修改 Array.prototype,或者是新增 Promise 方法,避免了全局污染。如果上面 @babel/plugin-transform-runtime 配置的 core-js 是 “2”,其中不包含实例的 polyfill 需要单独引入。
划重点:如果我们配置的 corejs 是 3 版本,那么不管是实例方法还是全局方法,都不会再污染全局环境。
看到这里,不知道大家有没有这样一个疑问?给 @babel/plugin-transform-runtime 配置 corejs 是如此的完美,既可以将帮助函数变成引用的形式,又可以动态引入 polyfill,并且不会污染全局环境。何必要给 @babel/preset-env 提供 useBuiltIns 功能呢,看起来似乎不需要呀。
带着这样的疑问,我新建了几个文件(内容简单且基本一致,使用了些新特性),然后使用 webpack 构建,以下是我对比的数据:
序号 | .babelrc 配置 | webpack mode production |
---|---|---|
0 | 不使用 @babel/plugin-transform-runtime | 36KB |
1 | 使用 @babel/plugin-transform-runtime ,并配置参数 corejs: 3。不会污染全局环境 | 37KB |
2 | 使用 @babel/plugin-transform-runtime ,不配置 corejs | 22KB |
我猜测是 @babel/runtime-corejs3/XXX 的包本身比 *core-js/modules/XXX *要大一些
结语
@babel/plugin-transform-runtime
@babel/polyfill
合理配置Babel配置,使用对应polyfill达到优化的目的
参考 帖子:刘小夕-不容错过的 Babel7 知识