Babel
一、babel的理解
官方文档对babel的描述是这样的Babel 是一个 JavaScript 编译器,相对于编译器compiler,叫转译器transpiler更准确,他是把比较高规则的语法转化为低规则的语法,做到向后兼容,而不像编译器那样,输出的是另一种更低级的语言代码,但是和编译器类似,babel的转译过程也分为三个阶段:parsing(解析)、transforming(转化)、generating(生成)
parsing阶段babel内部的 babylon 负责将es6代码进行语法分析和词法分析后转换成抽象语法树(AST)
transforming阶段内部的 babel-traverse 负责对抽象语法树进行变换操作得到新的AST树
printing阶段内部的 babel-generator 负责生成对应的代码
babel其实只是提供了一个“平台”,让更多有能力的plugins入驻我的平台,是这些plugins提供了将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法的能力,因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的
二、分类处理
babel对于ECMAScript 2015+ 版本的代码分为了两种情况处理:
语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译
api方法层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义
三、preset-env
preset-env
用于处理语法层
//compiled.js
const fn = () => {
console.log("wens");
};
const p = new Promise((resolve, reject) => {
resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);
加入preset-env
// babel.config.js
module.exports = {
presets: ["@babel/env"],
plugins: []
};
编译之后
//compiled.js
"use strict";
var fn = function fn() {
console.log("wens");
};
var p = new Promise(function (resolve, reject) {
resolve("wens");
});
var list = [1, 2, 3, 4].map(function (item) {
return item * 2;
});
语法层面都降级了。那么api层面要如何处理? 需要我们加入 @bable/polyfill
四、polyfill
用于处理api层,polyfill的中文意思是垫片,顾名思义就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。@babel/polyfill
是一个运行时包,主要是通过核心依赖core-js@2
来完成对应浏览器不支持的新的全局和实例api
的添加。在升级到core-js@3
后,如果还要保留@babel/polyfill
的使用,就要在@babel/polyfill
中添加core-js@2
和core-js@3
切换的选项,这样@babel/polyfill
中将包含core-js@2
和core-js@3
两个包,出于这个原因官方决定弃用@babel/polyfill
。
// Promise是api层面的,是一个es6中的全局对象
const p = new Promise((resolve, reject) => {
resolve(100);
});
编译之后
"use strict";
var p = new Promise(function (resolve, reject) {
resolve(100);
});
但是下载@bable/polyfill
是全部加载并没有按需导入,需要配置useBuiltIns
五、useBuiltIns
上面我们通过 import "@bable/polyfill"
的方式来实现针对api层面的“抹平”。然而从 babel v7.4.0开始官方就不建议采取这样的方式了。 因为引入 @bable/polyfill
就相当于在代码中引入下面两个库
import "core-js/stable";
import "regenerator-runtime/runtime";
useBuiltIns
这一配置项,它的值有三种:
false
: 不对polyfills
做任何操作entry
: 根据target
中浏览器版本的支持,将polyfills
拆分引入,仅引入有浏览器不支持的polyfill
usage(新)
:检测代码中ES6/7/8
等的使用情况,仅仅加载代码中用到的polyfills
然后我们修改配置如下:
//.babelrc
{
"presets": [
["@babel/preset-env",{
"useBuiltIns": "usage",
"corejs":3
}]
]
}
六、@babel/plugin-transform-runtime
解决代码冗余
代码冗余是出现在转译语法层时出现的问题。
首先依然修改下我们的index.js,这次我们再写一个语法层面的es6–>class(let,const这些对比不出问题,所以用class),看看babel会把class转化成什么
//index.js
class Student {
constructor(name,age){
this.name = name;
this.age = age
}
}
编译之后
"use strict";require("core-js/modules/es.function.name");function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }var Student = function Student(name, age) { _classCallCheck(this, Student); this.name = name; this.age = age;};
上面出现的_classCallCheck函数叫做辅助函数,是在编译阶段辅助 Babel 的函数。在每个文件中都会出现,会造成冗余,plugin-transform-runtime 闪亮登场。
当使用了plugin-transform-runtime插件后,就可以将babel转译时添加到文件中的内联辅助函数统一隔离到babel-runtime提供的helper模块中,编译时,直接从helper模块加载,不在每个文件中重复的定义辅助函数,从而减少包的尺寸
可以看出preset-env在处理例如Promise这种的api时,只是引入了core-js中的相关的js库,这些库重新定义了Promise,然后将其挂载到了全局。
这里的问题就是:必然会造成全局变量污染,同理其他的例如Array.from等会修改这些全局对象的原型prototype,这也会造成全局对象的污染。
解决方式就是:将core-js交给transform-runtime处理。添加一个配置即可,将core-js这个属性添加到@babel/plugin-transform-runtime
这个插件的配置下,让这个插件处理,同时也不需要配置useBuiltIns了,因为在babel7中已经将其设置为默认值 (transform-runtime是利用plugin自动识别并替换代码中的新特性,检测到需要哪个就用哪个
{ "presets": [ ["@babel/preset-env"] ], "plugins": [ ["@babel/plugin-transform-runtime",{ "corejs":3 }] ]}
plugin-transform-runtime
插件借助babel-runtime
实现了下面两个重要的功能
对辅助函数的复用,解决转译语法层时出现的代码冗余
解决转译api层出现的全局变量污染
更多关于这个插件的配置可参考官方文档