很早之前就听同事分享了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