AST 是 Abstract Syntax Tree 的縮寫,即 “抽象語法樹”.它是以樹狀的形式表現編程語言的語法結構. webpack 打包 JS 代碼的時候,webpack 會在我們的原有代碼基礎上新增一些代碼, 例如我們可以在打包JS 代碼的時候將高級代碼轉為低級代碼,就是通過 AST 語法樹來完成的
AST在線生成地址
babel插件查看使用地址
AST生成過程由源碼->詞法分析->語法分析->抽象語法樹
例如
let a = 1 + 2
詞法分析
- 從左至右一個字符一個字符地讀入源程序, 從中識別出一個一個 “單詞”“符號”等 讀出來的就是普通的字符,沒有任何編程語言的函數
- 將分析之後結果保存在一個詞法單元數組中
| 單詞 | 單詞 | 符號 | 數字 | 符號 | 數字 |
|---|---|---|---|---|---|
| let | a | = | 1 | + | 2 |
[
{"type": "word", value: "let"},
{"type": "word", value: "a"},
{"type": "Punctuator", value: "="},
{"type": "Numberic", value: "1"},
{"type": "Punctuator", value: "+"},
{"type": "Numberic", value: "2"},
]
之後進入詞法分析
語法分析
將單詞序列組合成各類的語法短語
| 關鍵字 | 標識符 | 賦值運算符 | 字面量 | 二元運算符 | 字面量 |
|---|---|---|---|---|---|
| let | a | = | 1 | + | 2 |
[{
"type": "VariableDecLaration",
"content": {
{"type": "kind", "value": "let"}, // kind 表示是什麼類型的聲明
{"type": "Identifier", "value": "a"}, // Identifier 表示是標識符
{"type": "init", "value": "="}, // 表示初始值的表達式
{"type": "Literal", "value": "1"}, // Literal 表示是一個字面量
{"type": "operator", "value": "+"}, // operator 表示是一個二元運算符
{"type": "Literal", "value": "2"},
}
}]
抽象語法樹
"program": {
"type": "Program",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"declarations": [ // 這裏是數組,表示可以同時聲明多個變量
{
"type": "VariableDeclarator",
"start": 4,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 13
}
},
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 5
},
"identifierName": "a"
},
"name": "a"
},
"init": {
"type": "BinaryExpression",
"start": 8,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 13
}
},
"left": {
"type": "NumericLiteral",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
},
"operator": "+",
"right": {
"type": "NumericLiteral",
"start": 12,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 12
},
"end": {
"line": 1,
"column": 13
}
},
"extra": {
"rawValue": 2,
"raw": "2"
},
"value": 2
}
}
}
],
"kind": "let"
}
],
"directives": []
}
程序示例
package.json依賴
"@babel/generator": "^7.11.6", // 轉換AST結構
"@babel/parser": "^7.11.5", // 拆解AST樹結構
"@babel/traverse": "^7.11.5",
"@babel/types": "^7.11.5",
@babel/parse生成 AST
import * as parser from '@babel/parser'
const code = `let a = 1 + 2`
const ast = parser.parse(code)
console.log(ast)
修改語法樹
通過 babel 的 traverse 模塊遍歷節點,找到對應節點後,通過 babel 中的 generator 模塊來轉換語法樹為代碼
import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
const code = `let val = 1 + 2`
const ast = parser.parse(code)
console.log(ast)
// traverse 方法可以遍歷所有的語法樹結點
traverse(ast, {
enter(path) { // 這個path會找到所有的node
if (path.node.type == 'Identifier') {
path.node.name = 'modify'
path.stop()
}
}
})
const ret = generator(ast)
console.log(ret)
這裏會把val修改為modify
創建語法樹
通過@babel/types 模塊創建語法樹節點然後 push 到 body 中就可以實現語法樹的手動創建,
import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
import * as t from '@babel/types'
let code = ``
let ast = parser.parse(code)
let left = t.NumericLiteral(1)
let right = t.NumericLiteral(2)
let init = t.binaryExpression("+", left, right)
let id = t.identifier("add")
let variable = t.variableDeclarator(id, init)
let declaration = t.variableDeclaration('let', [variable])
ast.program.body.push(declaration)
// 轉換為code
let genCode = generator(ast)
console.log(genCode.code)
刪除語法樹節點
想要刪除語法節點的核心就是先遍歷找到所有的節點,通過 @babel/traverse 來完成, 找到每個節點之後就可以通過具體的方法來完成增刪改查操作
-
NodePath 常用的屬性
- node: 獲取當前節點
- parent : 父節點
- parentPath :父path
- scope: 作用域
- context : 上下文
-
NodePath 常用的方法
- get: 當前節點
- findParent:向父節點搜尋節點
- getSibling: 獲取兄弟節點
- replaceWith: 用 AST 節點替換該節點
- replaceWithMultiple :用多個 AST 節點替換該節點
- insertBefore: 在節點前插入節點
- insertAfter: 在節點後插入節點
- remove: 刪除節點
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generator = require('@babel/generator').default
let code = `
console.log('jake')
let sum = 1 + 2
let minus = 2 - 1
console.log("tom")
`
let ast = parser.parse(code)
console.log(ast)
// traverse 遍歷語法樹的
traverse(ast, {
Identifier(path) {
if (path.node.name == 'sum') {
path.parentPath.remove()
}
}
})
console.log(generator(ast))
刪除了sum之後