10分钟上手ast

什么是AST

AST(Abstract Syntax Tree)简称是抽象语法树,是源代码语法结构的一种抽象表示.它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构.
比如一下代码:

1
const 1 = 5;

这行代码大致会被解析为以下ast

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "NumericLiteral",
"value": 5
}
}
],
"kind": "const"
}

这里推荐一个工具 ast explorer, 它可以帮你快速把代码转换成ast, 提高你的开发效率

可以看出来, 代码变成了一个树结构的数组对象, 描述了一个对一个变量的赋值. 那么我们能用ast来做点什么呢?

重新认识babel

社区里, 能够解析js代码到ast的工具有很多, 这里我们就使用大家都比较熟悉的babel来举例

babel是一个js编译器,能够把es6等语法编译成es5, 并且他还提供了丰富的工具,可以让我们做很多我们想干的事情.比如:

1
import { Button } from 'element'

转化成:

1
import Button from 'element/lib/Button'

如果想完成这样的事情,我们需要大概了解一下babel的工具包

  1. @babel/parser用来把js代码转换成ast
  2. @babel/traverse用来帮助你遍历ast
  3. @babel/types 用来帮助你判断ast节点,或生成相应的节点
  4. @babel/generator 用来把ast从新转换成js代码

开始

让我们想一下, 如果要把代码a转变成代码b, 应该是一个什么思路?我的思路是:

  1. 先吧js转换成ast
  2. 遍历ast收集信息
  3. 更具收集的信息生成ast
  4. 把新生城的ast转换为js代码并输出

第一步,转换ast

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
26
27
28
29
30
31
32
33
import { parse } from '@babel/parser';
import traverse from "@babel/traverse";

const code = "import { Button } from 'element'";
// 生成传入code的ast
const ast = parse(code);

const names = [];
traverse(ast, {
ImportDeclaration: function(path) { // 只处理import语句
const sourceValue = path.node.source.value;
if (sourceValue === 'element') {
if (types.isImportSpecifier(specifier)) { // 只处理解构方式声明的变量明
// 拿到所有结构的变量名
names.push(specifier.local.name);
}
}
}
});

//根据收集到的解构变量名list,生成新的ast
const body = names.map((name) => {
const source = types.stringLiteral(`element/lib/${name}`); // 生成source节点
const specifier = types.importDefaultSpecifier(types.identifier(name)); // 生成 specifier节点
return types.ImportDeclaration([specifier], source); // 生成Import声明节点
})

// 在外面包了一层program节点, 生成完整的树
const convertedAst = types.program(body, [], 'module');

// 把ast转换成js代码并输出
const convertCode = generator(convertedAst).code;
return convertCode; // import Button from 'element/lib/Button'

以上就是这个简单小功能的用法, 可以发现其实就是按部就班的来,类似于把大象放进冰箱的思路

其实在生成js代码的时候也可以用@babel/template来做, 也可能会更方便, 相关的工具api可以再babel的主页上查找

也许这个babel的小栗子可以为你打开一扇门, 让你能够知道, 其实你可以做的更多