@Dale-Lin
2022-09-18T22:01:12.000000Z
字数 2599
阅读 387
Babel
Babel 三个主要处理阶段分为:parse(解析),transform(变换),generate(生成)。
Parse 阶段,babel(@babel/parser)将代码转成 AST,进一步分为词法分析和语法分析。
将代码转成 stream of tokens。
tokens 可以理解为一组扁平的语法片段数组
n * n;
[
{ type: {...}, value: "n", start: 0, end: 1, loc: {...} },
{ type: {...}, value: "*", start: 2, end: 3, loc: {...} },
{ type: {...}, value: "n", start: 4, end: 5, loc: {...} },
...
]
每个 type
属性包含一组 token 的属性描述:
{
type: {
label: 'name',
keyword: undefined,
beforeExpr: false,
startsExpr: true,
rightAssociative: false,
isLoop: false,
isAssign: false,
prefix: false,
postfix: false,
binop: null,
updateContext: null
},
...
}
语法分析会把 stream of tokens 转换成 AST 的表示形式,利用了 tokens 的信息。AST 能更好的表示出代码的结构。
transform 阶段遍历 AST,并添加/更新/删除节点。
这是编译工具处理过程中最复杂的部分,也是插件操作的部分。
generate 阶段将 AST 转换回代码字符串,同时生成 source map。
生成阶段很简单,深度优先遍历 AST,同时构建表示转换后代码的字符串。
要对 AST 节点做变换必须递归遍历整棵树。
对每个 type 的节点,要知道它的结构和属性,然后按顺序深度优先遍历。
visitors 是 AST 遍历的术语,简单看做一个对象,具有能接受不同 type
的节点的方法,例如:
const MyVisitor = {
Identifier() {
console.log('called!');
}
}
// 可以后续继续添加方法
// MyVisitor.MemberExpression = function() {};
Identifier() {...}
是Identifier: { enter() {...} }
的简写。
上面的 visitor 会对每个 Identifier
节点做处理。
通常情况下,visitor 的方法都会在节点 enter 的时候调用,但也可以在 exit 的时候调用,例如:
{
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"
}
}
}]
}
}
转换成树结构
- FunctionDeclaration
- Identifier (id)
- Identifier (params[0])
- BlockStatement (body)
- ReturnStatement (body)
- BinaryExpression (argument)
- Identifier (left)
- Identifier (right)
遍历步骤会是深度优先队列(先进先出),所以 visitor 的每个 type 的方法可以有两次访问节点的时机:
const MyVisitor = {
Identifier: {
enter() {
console.log('Entered!');
},
exit() {
console.log('Exited!');
}
}
}
必要时可以一个函数处理多种 type 的节点,只要用 |
隔开:
const MyVisitor = {
"ExportNamedDeclaration|Flow"(path) {}
}
babel-types 定义了一些类型的别名,例如:
Function
等于 FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ObjectMethod|ClassMethod
const MyVisitor = {
Function(path) {}
}
Path 是用来链接 AST 上节点之间的桥梁,一个 Path 是一个表示两个节点之间连接的对象,例如:
一个具有子节点的节点:
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
...
}
用 Path 表示:
{
"parent": {
"type": "FunctionDeclaration",
"id": {...},
...
},
"node": {
"type": "Identifier",
"name": "square"
}
}
path 还包含额外的一些源信息:
{
"parent": {...},
"node": {...},
"hub": {...},
"contexts": [],
"data": {},
"shouldSkip": false,
"shouldStop": false,
"removed": false,
"state": null,
"opts": null,
"skipKeys": null,
"parentPath": null,
"context": null,
"container": null,
"listKey": null,
"inList": false,
"parentKey": null,
"key": null,
"scope": null,
"type": null,
"typeAnnotation": null
}
并且具有非常多的与添加/更新/移动/删除节点的方法。
Paths 是响应式的,任何时候调用方法更新了 AST,paths 信息都会更新。
Paths 是响应式的,但AST 对象不是响应式的。