Babel作为source to source的转换编辑器(transpiler)转换过程主要分为三步:
- parse: 通过
parser把源码转换成抽象语法树(AST),词法分析根据规则拆分成各个token再通过语法分析将token递归组装成可用的AST - transform: 通过相应
visitor函数遍历AST调用各种transform插件对AST进行操作修改 - generate: 把转换后的
AST生成对应结构的字符串并拼接还会并生成sourcemap
抽象语法树ast
ast节点组成
处理过程中每一步都涉及到创建或是操作抽象语法树,babel使用基于ESTree标准的ast
function square(n) {
return n * n;
}
解析后的ast如下:
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
params: [{
type: "Identifier",
name: "n"
}],
body: {
type: "BlockStatement",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
operator: "*",
left: {
type: "Identifier",
name: "n"
},
right: {
type: "Identifier",
name: "n"
}
}
}]
}
}
每个层级都有相同的结构:
{
type: "FunctionDeclaration",
id: {...},
params: [...],
body: {...}
}
{
type: "Identifier",
name: ...
}
{
type: "BinaryExpression",
operator: ...,
left: {...},
right: {...}
}
ast节点类型
节点类型包括:
LiteralsProgramsFunctionsStatementsDeclarationsMiscExpressionsTemplate LiteralsPatternsPatternsClassesModules
具体构成及说明文档位于 @babel/parser ast specification
ast节点属性
{
type: ...,
start: 0,
end: 38,
loc: {
start: {
line: 1,
column: 0
},
end: {
line: 3,
column: 1
}
},
...
}
<Callout type="info"> <p><strong>js parser历史:</strong></p> <p>SpiderMonkey的AST标准 esprima ,后来形成了estree标准</p> <p>accorn也是estree标准但是比esprima更快而且支持插件对语法进行扩展</p> <p>eslint的 espree 源自esprima但是espree2.0基于accron重新实现也适用accorn的插件机制扩展语法</p> <p>babel parser(babylon)在accorn的基础上对AST节点和属性做了 扩展 </p> </Callout>
转换过程
语法解析
通过@babel/parser将源码解析(词法分析、语法分析)为ast,具体 options 配置项决定产出形式
require('@babel/parser').parse("soucecode", {
sourceType: 'module',
plugins: [
'jsx'
]
})
ast遍历
通过@babel/traverse进行遍历ast节点进行增删改,在遍历过程中通过访问者模式(visitor)对指定的 节点类型 例如:FunctionDeclaration进行访问,同时可使用@babel/types提供的工具函数进行节点判断及生成
interface traverse {
ast: AST
visitor: (path, state) => {}
}
traverse(ast, {
FunctionDeclaration: {
enter(path, state) {}, // 进入节点时调用
exit(path, state) {} // 离开节点时调用
}
})
// 只指定一个函数那就是enter阶段调用
traverse(ast, {
FunctionDeclaration(path, state) {} // 进入节点时调用
})
// 多个ast类型节点处理,通过"|"
// 通过别名指定离开各种 Declaration 节点时调用
traverse(ast, {
'FunctionDeclaration|VariableDeclaration'(path, state) {}
})
<Callout type="info"> <p>path记录了节点间相互关系的路径,同时提供了很多 属性和方法 方便遍历操作</p> <p>path.scope包含了js中能生成作用域(模块、函数、块)等所形成的作用域链</p> <p>state保存了在传递到对应节点是的 options和file以及共享数据 </p> </Callout>
转换后生成
使用@babel/generator将ast生成代码字符串
function (ast: Object, opts: Object, code: string): {code, map}
import generate from "@babel/generator";
const { code, map } = generate(ast, { sourceMaps: true })
同时可使用@babel/code-frame在生成过程中打印报错代码位置
import { codeFrameColumns } from "@babel/code-frame";
const rawLines = `class Foo {
constructor()
}`;
const location = { start: { line: 2, column: 16 } };
const result = codeFrameColumns(rawLines, location, {
/* options */
});
console.log(result);
<Callout type="info"> <p>@babel/generator通过对应AST的 生成方法 进行生成</p> <p>sourcemap通常用于从目标代码映射到源码位置,具体创建、消费、及源码节点格式可以查看 文档 </p> </Callout>