利用 babel 操作 AST 语法树
狐七 AST
# 将代码转为 ast 语法树
- 安装
npm i webpack webpack-cli
- webpack里面用到了,自己写插件也会用到。
- webpack.config.js
const path = require('path')
module.exports = {
devtool:'none',
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve('dist')
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 安装
npm i @babel/parser
- index.js中引用
const parser = require('@babel/parser')
let code = `let name = 'xm'`
// 利用 parse 接收一个 JS 代码片段,可以将它转为 ast
let ast = parser.parse(code)
console.log(ast)
1
2
3
4
5
6
7
2
3
4
5
6
7
使用webpack打包是可以直接测试,输出node对象就是成功,可以找到对应的东西。
# 修改AST语法树及code转换
- 把代码转换成语法树
- 遍历所有节点,在语法树上找到对应节点
- 对相应方法处理(处理之后还是语法树)
- 修改语法树要安装
npm i @babel/traverse
模块 - js中修改使用
// import traverse from "@babel/traverse"; export import 官网
const traverse = require('@babel/traverse').default // require module.exports 可以解构
// 用的不一样的模块引入,webpack都可以使用
let code = `let name = 'zce is a ML'`
let ast = parser.parse(code)
// 上面的 ast 就是语法树,我要怎么遍历呢?利用插件 traverse
traverse(ast, {
// 这个方法会遍历到所有的节点
enter(nodePath) {
// console.log(nodePath.node.type)
// 每一个node下面都有一个type属性,Identifier是关键字
if (nodePath.node.type === 'Identifier') {
// 在本例中就说明我们找到了 name 变量名 ,并修改同级的name
nodePath.node.name = 'aaa'
// 因为是遍历,所以要把循环遍历停掉
nodePath.stop()
}
}
})
let retCode = generator(ast)
console.log(retCode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 要把语法树转换成code需要安装
npm i @babel/generator
模块 - js写
import generator from '@babel/generator'
...
// 上面是转换好的语法树
const ret = generator(ast)
console.log(ret)
1
2
3
4
5
2
3
4
5
# 创建语法树及code转换
需求: 创建 let slogan = "我爱工作,我爱前端",节点添加至 body 中
- 先想办法创建一个空的语法树(写一个变量为空再parse装换成语法树)
- 依据目标语句,利用 babel 提供的插件或者方法来组装语法树的节点
- 添加至 body
- 创建空的语法树,在js中写
let code = ``
let ast = parser.parse(code) // 创建了一个空的语法树
console.log(ast)
1
2
3
2
3
- 需要安装
npm i @babel/types
,利用 types 里所提供的方法(type所对应的单词在 types中都有具体的方法,就相当于创建节点)
import * as t from '@babel/types'
...
// 利用方法自己创建一个语法树的节点:
// init,id这些都是语法树中的节点
// init是变量初始化
let init = t.stringLiteral('我爱工作,我爱前端')
// 变量名
let id = t.identifier('slogan')
// 变量声明,id和init放进去
let declarator = t.VariableDeclarator(id, init)
// 变量定义语句:添加kind关键字:let,
// 注意: 变量声明为数组,要包在数组里
let declaration = t.variableDeclaration('let', [declarator])
// push之前ast空语法树的body是个空数组,添加节点就push进去,组装完成
ast.program.body.push(declaration)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 将语法树转为code
let retCode = generator(ast)
console.log(retCode)
1
2
3
2
3
# 删除语法树节点
1 travese 方法可以实现 AST 语法树的遍历操作
2 traverse 中不仅存在 enter 方法,只要是语法树中存在的节点类型,都有对应的方法可以直接调用
3 这些方法只有在遍历到对应类型的时候才会被调用
想要删除语法节点的核心就是先遍历找到所有的节点,这个操作可以通过 @babel/traverse 来完成, 找到每个节点之后就可以通过具体的方法来完成增删改查操作
- NodePath 常用的属性
- node: 获取当前节点
- parent : 父节点
- parentPath :父path
- scope: 作用域
- context : 上下文
- NodePath 常用的方法
- get: 当前节点
- findParent:向父节点搜寻节点
- getSibling: 获取兄弟节点
- replaceWith: 用 AST 节点替换该节点
- replaceWithMultiple :用多个 AST 节点替换该节点
- insertBefore: 在节点前插入节点
- insertAfter: 在节点后插入节点
- remove: 删除节点
import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
import * as t from '@babel/types'
let code = `
console.log('拉勾教育')
let sum = 10 + 66
let minus = 88 - 66
console.log("互联网人的实战大学")
`
let ast = parser.parse(code)
console.log(ast)
// traverse 遍历语法树的
traverse(ast, {
Identifier(path) {
if (path.node.name == 'sum') {
path.parentPath.remove()
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22