babel最开始的名字叫做6to5
,主要是做es6到es5语法的转换和polyfill,后来在4.0版本时改成了babel。虽然从6to5改名到了babel,但是做的事情却没有变化,依然是将高版本的js语法转化为低版本的语法,并自动polyfill缺少的语法。
babel是如何实现这些功能的呢?
从plugin到preset
要实现转换,第一步要明确转换什么;划定一个集合放要转换的特性,再划定一个集合放转换到的目标特性,两者建立一一映射关系。就确定了我们要做哪些转换。
exponentiation operator
比如幂运算,我们会用Math.pow来实现
js
let x = 10 ** 2;
x **= 3;
转换为
js
let x = Math.pow(10, 2);
x = Math.pow(x, 3);
class
再比如class,我们会用function、prototype来实现
js
class Test {
constructor(name) {
this.name = name;
}
logger() {
console.log("Hello", this.name);
}
}
转换为:
js
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Test = (function() {
function Test(name) {
_classCallCheck(this, Test);
this.name = name;
}
Test.prototype.logger = function logger() {
console.log("Hello", this.name);
};
return Test;
})();
每一个语法都可以这样转换为低版本的语法,那把所有的这种高版本语法写的代码转换为低版本的,这就实现了编译。
但是只是转换并不能解决所有问题,涉及到某个对象的api,比如Array.prototype.find,这种api的兼容并不是需要语法转换外,而是需要在环境中注入我们实现的api,也就是polyfill(垫片)。
所以我们做的事情除了语法转换外,还有api的polyfill。
先说语法转换:
我们要转换哪些语法呢?
babel插件需要转换的语法包括es标准语法,还有react、flow、typescript等特有语法。
es 标准语法
我们知道TC39是制定javaascript语言标准的组织,每年都会公布加入到语言标准的特性,es6,es7等等,都是我们要转换的语言特性范围。 在babel6的时候,分别用preset-es2015、preset-es2016等来维护对应的transform plugin,但在babel7的时候就改为了preset-env了。
proposal阶段的语法
babel要转换的不只是加入标准的特性,语法特性从提出到标准会有一个过程,分为几个阶段:
- 阶段0:Strawman - 只是一个想法,可能用babel plugin实现。
- 阶段1:Proposal - 值得继续的建议
- 阶段2:Draft - 建立spec
- 阶段3:Condidate - 完成spec并且在浏览器实现
- 阶段4:Finished - 会加入到下一年的es20xx spec
这些还未加入到语言标准的特性也是要支持的。
react、flow、typescript
只是转换javascript本身的的es spec和proposal的特性并不够,现在我们开发的时候jsx、typescript、flow这些都是会用的,babel肯定也得支持。
这些转换对应的plugin分别放在不同的preset中:preset-jsx、preset-typescript、preset-flow。
转换的范围又大了些:
上面是插件要转换的语言特性,babel7内置的实现这些特性的插件分别是syntas、transform、proposal 3类。
syntax plugin
syntax plugin是在parserOptions中放入一个flag,让parser知道要parse什么语法,最终parse的逻辑还是babel parser(babylon)实现的。
一般syntax plugin是这样实现的:
js
import { declare } from "@babel/helper-plugin-utils";
export default declare(api => {
api.assertVersion(7);
return {
name: "syntax-function-bind",
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("functionBind");
},
};
});
这些插件的目的就是让parser能够正确解析对应的语法成AST。
transform plugin
transform plugin是对 AST 的转换,各种es20xx语言特性、typescript、jsx语言特性等都是在transfrom里实现的。
有的时候需要结合syntax plugin 和 transform plugin,比如typescript的语法解析要使用 @babel/plugin-syntax-typescript 在parseOptions里放入typescript的语法选项,然后使用@babel/plugin-transform-typescript来转换解析出的typescript对应的AST。
平时我们都是使用的@babel/preset-typescript,它对上面的两个插件做了封装,是他们的集合。
proposal plugin
未加入语言标准的特性的 AST 转换插件叫proposal plugin,其实他也是transform plugin,但是为了和标准特性区分,所以才这样叫。
完成proposal特性的支持,有时同样需要syntax plugin 和 proposal plugin,比如function bind(::操作符),就需要同时使用@babel/plugin-syntax-function-bind 和 @babel/plugin-proposal-function-bind。
总之,babel内置的plugin就 @babel/syntax-plugin-xxx、@babel/transform-plugin-xxx、@babel/proposal-plugin-xxx 3种。
这样的plugin还很多,所以又设计了preset。
preset
用于不同的目的,需要不同的babel插件,所以babel设计了preset。
- 不同版本的语言标准支持设计了:preset-2015、preset-2016等,babel7用preset-env来代替。
- 未加入标准的语言特性的支持:用于stage0、stage1、stage2的特性,babel7之后单独引入proposal plugin。
- 用于jsx、react、flow支持:分别封装对应的preset-jsx、preset-react、preset-flow,直接使用对应的preset即可。
preset就是插件的集合,但是它可以动态的确定所包含的插件,比如preset-env就是根据stages来确定插件。
babel runtime
babel runtime里面放运行时加载的模块,会被打包工具打包到产物中,下面放着各种需要在runtime使用的函数, 包括三部分:regenerator、corejs、helper。
- corejs就是新的 api 的 polyfill,分为2和3两个版本,3才实现了实例方法的polyfill。
- regenerator是facebook实现的 async 的 runtime库,babel使用regenerator-runtime来支持实现async await的语法。
- helper 是babel做语法转换时用到的函数,比如_typeof、_extends
babel做语法转换和api的polyfill,需要自己实现一部分runtime的函数,就是helper部分。
有的也没有自己实现,用的第三方,比如regenerator用的facebook的, api的polyfill也是用core-js的,babel对他们做了整合。
因为async await这种特性的实现还是比较复杂的,标准api的实现也需要花精力,所以babel就直接使用了社区的实现。