英语能力有限,难免有错误,欢迎批评指正。
原文链接:Babel under the hood.. Have you ever thought how this awesome… | by Mahak Narayan Singh | Medium
引言
你可曾想过,你在日常开发中用到的Babel工具,它的原理是什么?编译器在日常工作中是如何被使用的?你是否需要一个个性化的编译器呢?在这篇文章中,相信你能找到答案。
Babel绝对是一件趁手的工具。Facebook、谷歌、Netflix和全球其他数百家大厂都在使用它。其在npm上的月下载次数超过700万次。Babel不仅提供了让我们使用ES6的能力,还支持polyfills、ES7、JSX等。可能这些你还觉得不够,没关系,Babel也在与TC39(领导JavaScript设计和开发的技术委员会)紧密合作,及时提供最新的JavaScript功能实现(有些功能甚至还没有被TC39正式确定)。
接下来,让我们来看看Babel是如何工作的。
Babel是一款源到源的编译器。为了方便理解Babel,我们以ES6的新特性——箭头函数为例,来说明整个过程。假如我们有这样一段ES6的代码:
(foo, bar) => foo + bar;
接着,我们把上面这段代码编译成浏览器支持的JavaScript代码(ES5):
"use strict";
(function (foo, bar) {
return foo + bar;
});
Babel编译的三个主要阶段
第一步:语法分析
Babel拿到源代码,并对源代码进行解析,解析的结果是一种代码的抽象表示,我们称这个抽象表示为抽象语法树(AST,Abstract Syntax Tree)。抽象语法树是源代码抽象语法结构的树形表示,在这棵树上的每个节点,都对应源代码中的一个具体的“结构”。这些节点的作用主要是对源代码进行转换。
让我们来看一个由Babel的babylon语法分析器处理箭头函数的例子,还是跟上面相同的箭头函数。
(foo, bar) => foo + bar;
这个箭头函数的抽象语法树表示,会是这样:
// AST shortened for clarity
{
"program": {
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "ArrowFunctionExpression",
"params": [
{
"type": "Identifier",
"name": "foo"
},
{
"type": "Identifier",
"name": "bar"
}
],
"body": {
"type": "BinaryExpression",
"left": {
"type": "Identifier",
"name": "foo"
},
"operator": "+",
"right": {
"type": "Identifier",
"name": "bar"
}
}
}
}
]
}
}
正如你看到的这样,这棵抽象语法树实际上就是在描述源代码每部分的语法以及它们之间的关系。当然,你可以在AST explorer中,尝试使用不同的源代码生成抽象语法树,来帮助你更好的理解。
第二步 转换
这一步发生的事情相当奇妙。在这一步中,Babel会从利用第一步获取到的抽象语法树,生成由抽象语法树表示的且浏览器支持的代码。
这一步主要由Babel的插件或预设来完成。Babel的预设是一个简单的插件数组,使它可以更容易的进行一系列的连续转换,而不需要手动去指定。你可以使用Babel的插件来实现自己想要的功能,当然,你也可以自己动手写一个你自己的插件,来实现你的个性化编译。
所有的插件,都使用Babel提供的babel-traverse来遍历抽象语法树,这些插件中还规定了,对旧的抽象语法树进行替换和修改,从而生成一颗新的抽象语法树。
通过使用babel-traverse遍历抽象语法树,并利用Babel插件对抽象语法树的节点进行替换和修改。接下来让我们看看新的抽象语法树。
// AST shortened for clarity
{
"program": {
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "Literal",
"value": "use strict"
}
},
{
"type": "ExpressionStatement",
"expression": {
"type": "FunctionExpression",
"async": false,
"params": [
{
"type": "Identifier",
"name": "foo"
},
{
"type": "Identifier",
"name": "bar"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"left": {
"type": "Identifier",
"name": "foo"
},
"operator": "+",
"right": {
"type": "Identifier",
"name": "bar"
}
}
}
]
},
"parenthesizedExpression": true
}
}
]
}
}
你可以看看Babel的箭头函数插件代码,或者选择读一读这本Babel插件手册,来进一步了解如何对抽象语法树进行修改,或自己动手DIY一个Babel插件。
第三步:代码生成
在这一步中,我们会把第二步得到的新抽象语法树转换为源代码。生成的源代码是可以被浏览器支持的(ES5)。
"use strict";
(function (foo, bar) {
return foo + bar;
});
这一步由Babel的babel-generator完成。
Babel的整个流程,我们总结下来可以用这样一张图来表示:
如果你想更深入的了解Babel,欢迎参考官方文档Babel · The compiler for next generation JavaScript (babeljs.io)。