babel的原理
- parse:把代码
code
变成AST
- traverse:遍历AST进行修改
- generate:把
AST
变成代码code2
即 code -> ast -> ast2 -> code2
示例:手动把let变成var
import { parse } from "@babel/parser" // 把代码`code`变成`AST`
import traverse from "@babel/traverse" // 遍历AST
import generate from "@babel/generator" // 把`AST`变成代码`code2`
const code = `let a = 'let'; let b = 2`
const ast = parse(code, { sourceType: 'module' })
traverse(ast, {
enter: item => {
if(item.node.type === 'VariableDeclaration'){
if(item.node.kind === 'let'){
item.node.kind = 'var'
}
}
}
})
const result = generate(ast, {}, code)
console.log(result.code)
在Chrome运行TS代码:node -r ts-node/register --inspect-brk xxx.ts
先打印看看ast是什么:
从ast对象的属性中,可以看到这个
program
就是上面code
这段代码的程序,程序的body
里有两个节点:
// const code = `let a = 'let'; let b = 2`
let a = 'let' // 节点1
let b = 2 // 节点2
再看一下这些节点都包含哪些属性,重点关注这三个属性
...
// 声明的节点信息
declarations: [Node]
// 变量声明的关键字是let
kind: "let"
// 类型,说明是一个变量声明
type: "VariableDeclaration"
...
// declarations是一个节点数组,因为可以同时声明多个节点
// let a = 'let'
// name为a的标识符,对应初始值value是'let',类型是字符串
declarations: [
0: Node
{
id: {
...,
name: "a",
type: "Identifier"
},
init: {
...,
value: "let",
type: "StringLiteral"
}
},
length: 1
]
所以说AST就是用来将源代码字符串,表示成一个树形结构
// traverse用来遍历AST,可以对AST进行修改
traverse(ast, {
// 每进入一个节点,就执行item=>{}函数
enter: item => {
...
}
})
这样看来,将let
变成var
就水到渠成了
traverse(ast, {
enter: item => {
if(item.node.type === 'VariableDeclaration'){ // 如果是变量声明
if(item.node.kind === 'let'){ // 如果是let声明
item.node.kind = 'var' // 将let改成var
}
}
}
})
再使用generate
函数将新的AST翻译成code
const result = generate(ast, {}, code) // 传入修改后的ast,还有原始code
console.log(result.code) // 得到新的code
运行后就得到了
var a = 'let';
var b = 2;
为什么要用AST
- 你很难用正则表达式去替换,正则很容易把
let a = 'let'
变成var a = 'var'
- 你需要识别每个单词的意思,才能做到只修改用于变量声明的
let
- 而AST能明确告诉你每个
let
的意思