@Dale-Lin
2022-09-18T14:01:12.000000Z
字数 2599
阅读 669
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 对象不是响应式的。