很早之前就听同事分享了babel原理,其核心就是 AST(Abstract Syntax Tree),今天将自己所了解的知识点简单整理记录一下。
Babel处理流程
有简单了解过 loader 实现的朋友知道,对于 loader 来说,源代码将作为字符串参数传入。
举个例子,有以下代码
const name = 'Job';
对于 loader 来说,其实就是运行
babelLoader(`const name = 'Job'`)
那 babelLoader 函数是如何处理呢?以前我以为是通过正则处理的,但其实不是,真实处理的流程是以 AST 为桥梁实现的
parse => transform => generate 即 解析 => 转化 => 生成
解析
解析就是将代码字符串解析为 ast,我们将其分为两个阶段,词法分析和语法分析
这边推荐个查看解析结果的网站AST查看
词法分析
词法分析就是将代码(字符串序列)拆分为词法单元,我们将词法单元称为token,所以我们也将词法分析叫做tokenization
还是以上面的例子继续
const name = 'Job';
经过词法分析得到词法单元数组
[
{
"type": "Keyword",
"value": "const"
},
{
"type": "Identifier",
"value": "name"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "String",
"value": "'Job'"
},
{
"type": "Punctuator",
"value": ";"
}
]
可以看出词法分析将const name = 'Job';拆分为const,name,=,'Job',;,而且分别有不同的类型描述 Keyword,Identifier 等
语法分析
词法分析得到的词法单元将作为参数供语法分析进一步处理,经过语法分析将得到解析的最终产物 ast,我们直接来看看生成的 ast
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "name"
},
"init": {
"type": "Literal",
"value": "Job",
"raw": "'Job'"
}
}
],
"kind": "const"
}
],
"sourceType": "script"
}
如此,我们就得到一个可以描述我们代码的 json 数据,实际真实的 ast 会比这样要复杂一些,例如还包括了词法单元所在的行列数据。
转化
transform就是处理我们从代码中得到的ast,对其进行修改,例如我们将const修改为var
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "name"
},
"init": {
"type": "Literal",
"value": "Job",
"raw": "'Job'"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
生成
generate就是将我们在前面经过处理后的ast处理生成code的过程。
示例
我们用babel提供的工具包来演示一遍刚刚的分析过程
- 使用
@babel/parser将代码解析为ast
const ast = require("@babel/parser").parse("const name = 'Job';");
{
"type":"File",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"errors":[
],
"program":{
"type":"Program",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"sourceType":"script",
"interpreter":null,
"body":[
{
"type":"VariableDeclaration",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"declarations":[
{
"type":"VariableDeclarator",
"start":6,
"end":18,
"loc":{
"start":{
"line":1,
"column":6
},
"end":{
"line":1,
"column":18
}
},
"id":{
"type":"Identifier",
"start":6,
"end":10,
"loc":{
"start":{
"line":1,
"column":6
},
"end":{
"line":1,
"column":10
},
"identifierName":"name"
},
"name":"name"
},
"init":{
"type":"StringLiteral",
"start":13,
"end":18,
"loc":{
"start":{
"line":1,
"column":13
},
"end":{
"line":1,
"column":18
}
},
"extra":{
"rawValue":"Job",
"raw":"'Job'"
},
"value":"Job"
}
}
],
"kind":"const"
}
],
"directives":[
]
},
"comments":[
]
}
可以发现 program 和我们之前分析的 ast 差不多,但是会复杂一些,主要是加上了很多记录位置的字段。
- 使用
@babel/traverse转化ast
const traverse = require("@babel/traverse").default
traverse(ast, {
enter(path) {
// 用var声明替换const
if (path.node.type === 'VariableDeclaration' && path.node.kind === 'const') {
path.node.kind = 'var'
};
}
})
{
"type":"File",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"errors":[
],
"program":{
"type":"Program",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"sourceType":"script",
"interpreter":null,
"body":[
{
"type":"VariableDeclaration",
"start":0,
"end":19,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
},
"declarations":[
{
"type":"VariableDeclarator",
"start":6,
"end":18,
"loc":{
"start":{
"line":1,
"column":6
},
"end":{
"line":1,
"column":18
}
},
"id":{
"type":"Identifier",
"start":6,
"end":10,
"loc":{
"start":{
"line":1,
"column":6
},
"end":{
"line":1,
"column":10
},
"identifierName":"name"
},
"name":"name"
},
"init":{
"type":"StringLiteral",
"start":13,
"end":18,
"loc":{
"start":{
"line":1,
"column":13
},
"end":{
"line":1,
"column":18
}
},
"extra":{
"rawValue":"Job",
"raw":"'Job'"
},
"value":"Job"
}
}
],
"kind":"var"
}
],
"directives":[
]
},
"comments":[
]
}
不出所料,其中的kind: const转化为kind: const了,其它部分保持不变
- 使用
@babel/generator生成代码
const generator = require("@babel/generator").default;
const code = generator(ast, {}).code;
经过generator函数最终生成我们的编译后的代码字符串
var name = 'Job';
至此,我们完成了parse(code) => ast => tansform(ast) => generator(ast) => code 这一babel流程。当然我们的示例代码非常简单,处理的过程也非常粗暴,真实的const 替换为 var的过程还要考虑作用域相关的问题,实际的词法分析,语法分析及转化都是非常复杂的过程,我们这边稍微了解其原理即可。
示例代码
const traverse = require("@babel/traverse").default
const generator = require("@babel/generator").default
const ast = require("@babel/parser").parse("const name = 'Job';");
traverse(ast, {
enter(path) {
if (path.node.type === 'VariableDeclaration' && path.node.kind === 'const') {
path.node.kind = 'var'
};
}
})
const code = generator(ast, {}).code;
后话
本篇文章简述了babel原理,分析了babel编译过程中ast的生成及运用,通过示例简单粗暴描述了babel编译的流程。good good staduy day day up