一、基本概念
概念 | 作用 | 特殊说明 |
---|---|---|
一个纯运行时的包,不是babel插件 。新API的转化,如 Set、Maps、Proxy。 | @babel/polyfill可以看作是:core-js 加 regenerator-runtime。注意: 从Babel v7.4.0[5]开始, @babel/polyfill被废弃了,推荐直接引用core-js与regenerator-runtime替代。原因:@babel/polyfill,因为它只包含core-js 2.0的版本。 | |
@babel/preset-env:插件预设 | **基础语法synctx的转化。**core.js 的按需引入。 | 注意。1.无论如何设置,该插件require的 polyfill 代码都将污染全局的原型对象。2. @babel/preset-env 也因 core-js@3 的原因,需要配置 corejs 参数去指定使用的corejs 版本,否则 webpack 运行时会报 warning。 |
@babel/runtime | 语法转译通常使得原语法的部分语义由编译期来到运行时,因此需要提供额外的运行时代码(称为 Helper)。此库提供了所有babel可复用的 helper方法。 | 需要搭配插件@babel/plugin-transform-runtime使用,从而避免重复生成 helper。如果并不想复用 helper,该库可以不安装 |
@babel/runtime-corejs2 或者**@babel/runtime-corejs3:** | 转化新的API。结合了 @babel/runtime和core-js两个库的内容。 | 需要搭配插件@babel/plugin-transform-runtime使用(配置了corejs选项),为前者提供 helper 和 polyfill。core.js2 与core.js3 的区别:支持实例上的方法,例如iarray.includes(something) |
@babel/plugin-transform-runtime | 三个作用:1.自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代;2.当代码里使用了core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable;3.当代码里使用了Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime; | 这将无效化@babel/preset-env中targets属性(因为 plugin 先于 preset 运行)。 |
二、业务项目开发
-
@babel/polyfill改为:core-js/stable + regenerator-runtime/runtime -
@babel/preset-env: 配置tagets + 设置useBuiltIns: 'usage'选项。
2.1 polyfill(垫片) 简介
作为前端,最常见的Babel生态的库想必是 @babel/polyfill(垫片) 与 @babel/preset-env。
从上文我们知道,Babel本身只是JS的编译器,以上两者的转换功能是谁实现的呢?
答案是:core-js
2.2 core-js简介
core-js是一套模块化的JS标准库,包括:
- 一直到ES2021的polyfill。
- promise、symbols、iterators等一些特性的实现。
- ES提案中的特性实现。
- 跨平台的WHATWG / W3C特性,比如URL。
目前有版本2 和版本3。
对于版本3:
2.3 @babel/polyfill与core-js关系
@babel/polyfill可以看作是:core-js 加 regenerator-runtime。
regenerator-runtime是generator以及async/await的运行时依赖
单独使用 @babel/polyfill会将core-js全量导入,造成项目打包体积过大。
为了解决全量引入core-js造成打包体积过大的问题,我们需要配合使用 @babel/preset-env。
2.4 优化打包体积
使用 @babel/preset-env,可以**「按需」将core-js**中的特性打包,这样可以显著减少最终打包的体积。
这里的**「按需」**,分为两个粒度:
- 宿主环境的粒度。根据不同宿主环境将该环境下所需的所有特性打包
- 按使用情况的粒度。仅仅将使用了的特性打包
我们来依次看下。
宿主环境的粒度
指定应用所要支持的浏览器环境,以避免不必要的补丁,减少打包输出体积。
当我们按如下参数在项目目录下配置browserslist文件(或在 @babel/preset-env的targets属性内设置,或在package.json的browserslist属性中设置):
not IE 11
maintained node versions
会将**「非IE11」且「所有Node.js基金会维护的node版本」**下需要的特性打入最终的包。
按使用情况的粒度
更理想的情况是只打包我们使用过的特性。
这时候可以设置 @babel/preset-env的useBuiltIns属性为usage-按需导入。
useBuiltIns取值
false: 默认值,不注入垫片。
entry: 需要在整个项目入口,main.ts, 导入 core.js。会注入目标环境不支持的所有语法。
usage: 按需导入core.js。会注入目标环境的所有被用到的类型语法。
比如:
a.js:
var a = new Promise();
b.js:
var b = new Map();
当宿主环境不支持promise与Map时,输出的文件为:
a.js:
import "core-js/modules/es.promise";
var a = new Promise();
b.js:
import "core-js/modules/es.map";
var b = new Map();
当宿主环境支持这两个特性时,输出的文件为:
a.js:
var a = new Promise();
b.js:
var b = new Map();
2.5 分析
优点 | 缺点 | 使用场景 |
---|---|---|
可控能力强(targets) | 全局变量污染。helpers函数无法提出。(少可忽略) | 工程业务项目,自己控制,污染也无所谓。 |
三:类库、组件库开发
- @babel/preset-env: 基本语法转化
- @babel/plugin-transform-runtime + @babel/runtime-corejs2(3):配套使用: helpers + 新API
@babel/runtime-corejs2(3) = @babel/runtime + corejs2(3)
3.1 @babel/runtime
一句话:将preset-env所产生的helpers函数提出到一个独立文件中,从而减少代码量
@babel/runtime内部集成了:
- regenerator: 作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。当代码中有使用 generators/async 时自动引入。
- helpers, 如上面的 asyncToGenerator 就是其中之一,其他还有如 jsx, classCallCheck 等等,可以查看 babel-helpers。
会发现编译出的结果为:
其中 _classCallCheck为辅助方法。
如果多个文件都使用了class特性,那么每个文件打包对应的module中都将包含 _classCallCheck。
为了减少打包体积,更好的方式是:需要使用**「辅助方法」的module**都从同一个地方引用,而不是自己维护一份。
@babel/runtime包含了Babel所有**「辅助方法-工具函数」**。
单纯引入 @babel/runtime还不行,因为Babel不知道何时引用 @babel/runtime中的**「辅助方法」**。
所以,还需要引入 @babel/plugin-transform-runtime。
这个插件会在编译时将所有使用**「辅助方法-工具函数」的地方从「自己维护一份」**改为从 @babel/runtime中引入。
从定义方法改成引用,那重复定义就变成了重复引用,就不存在代码重复的问题了。
注意:
- 所以我们需要将 @babel/plugin-transform-runtime置为devDependence,因为他在编译时使用。
- 将 @babel/runtime置为dependence,因为他在运行时使用。
- runtime方式不支持类似 Array.prototype.includes 的实例方法 , 如果想要支持,还是要使用 core-js@3。
一句话:将preset-env所产生的helpers函数提出到一个独立文件中,从而减少代码量
3.2 @babel/runtime-corejs2(3)
@babel/polyfill 和@babel/runtime-corejs2 都使用了 core-js(v2)这个库来进行 api 的处理。core-js(v2)这个库有两个核心的文件夹,分别是 library 和 modules。
@babel/polyfill 使用 modules 这个文件夹,@babel/runtime-corejs2 使用 library 这个文件夹。
- library 使用 helper 的方式,局部实现某个 api,不会污染全局变量;
- modules 以污染全局变量的方法来实现 api;
- library 和 modules 包含的文件基本相同,最大的不同是_export.js 这个文件。
library下的这个$export方法,会实现一个wrapper函数,防止污染全局变量。
例如:对Promise的转译,@babel/polyfill和@babel/runtime-corejs2的转译方式差异如下
var p = new Promise();
// @babel/polyfill
require("core-js/modules/es6.promise");
var p = new Promise(); // 直接修改全局变量,上面加了Promise一个方法。
// @babel/runtime-corejs2
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise")); // 实现了一个新的_promise
var a = new _promise.default();
- 从上面这个例子可以看出,对于Promise这个api,@babel/polyfill引用了core-js/modules中的es6.promise.js文件,因为是对全局变量进行处理,所以赋值语句不用做处理;
- @babel/runtime-corejs2会生成一个局部变量_promise,然后把Promise都替换成_promise,这样就不会污染全局变量了。
babel-polyfill 是在原有的JS内置对象及方法上做向后兼容的处理。
比如说ES5里面的 Object 是没有自带 assign 方法的,那么你加载了babel-polyfill 之后,它就给 Object 扩展了一个 assign 方法,这样你就可以直接使用 Object.assign(obj1, obj2) 了
而 babel-runtime 的方式则需要babel作为工具。
在转换的过程中,检测到你使用了 Object.assign,而且你的 .babelrc 配置中需要对其做ES5兼容处理,那么结合 babel-plugin-transform-runtime,在该JS文件中引入 Object.assign 的Polyfill,这样也能实现 Object.assign 的功能,但是你无法在 Object 上直接找到 assign 的方法。
3.3 @babel/plugin-transform-runtime
corejs 可设置为3个参数:
取值 | 含义 | 说明 |
---|---|---|
false | 需安装 @babel/runtime ,不需要开启core-js相关API转换功能的时候。不包含core.js ,所以只做语法的转换。 | 做API转换,对内置对象进行重命名,以防止污染全局环境。 |
2 | 需安装 @babel/runtime-corejs2,开启core-js相关API转换功能的时候,避免全局变量污染时。 | |
3 | 需安装 @babel/runtime-corejs3,开启core-js相关API转换功能的时候,避免全局变量污染。 |
3.4 分析
优点 | 缺点 | 使用场景 |
---|---|---|
helpers函数可提出。避免全局变量污染。 | 可控能力弱,targets 不会起作用。 | 组件库,类库。 |