Babel

[TOC]

抽象语法树 AST(Abstract Syntax Tree)

将我们所写的代码转化为机器能识别的一种树形结构。本身有一堆节点(Node)构成。每个节点都代表一种结构,不同结构用类型(Type)区分。

AST 结构

JS 为了统一 ECMAScript 标准,社区中衍生出了 ESTree Spec 标准。

节点类型

类型 说明
File 文件(顶层节点包含 Program)
Program 程序(包含 body)
Directive 指令(如 “use strict”)
Comment 注释
Statement 语句
Literal 字面量(基本数据类型、复杂数据类型等值类型)
Identifier 标识符(变量名、属性名、函数名、参数名等)
Declaration 声明(变量声明、函数声明、Import、Export 声明等)
Specifier 关键字(ImportSpecifier、ImportDefaultSpecifier、ImportNamespaceSpecifier、ExportSpecifier 等)
Expression 表达式

公共属性

属性 说明
type 节点类型
start 记录该节点代码字符串起始下标
end 记录该节点代码字符串结束下标
loc 内含 line、column 属性,分别记录开始结束的行列号
leadingComments 开始的注释
innerComments 中间的注释
trailingComments 结尾的注释
extra 额外信息

AST 示例

有两个工具:

如下的代码:

1
2
3
4
function test(args) {
const a = 1;
console.log(args);
}

转化为 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
{
"type": "Program",
"start": 0,
"end": 62,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 62,
"id": {
"type": "Identifier",
"start": 9,
"end": 13,
"name": "test"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 14,
"end": 18,
"name": "args"
}
],
"body": {
"type": "BlockStatement",
"start": 20,
"end": 62,
"body": [
{
"type": "VariableDeclaration",
"start": 24,
"end": 36,
"declarations": [
{
"type": "VariableDeclarator",
"start": 30,
"end": 35,
"id": {
"type": "Identifier",
"start": 30,
"end": 31,
"name": "a"
},
"init": {
"type": "Literal",
"start": 34,
"end": 35,
"value": 1,
"raw": "1"
}
}
],
"kind": "const"
},
{
"type": "ExpressionStatement",
"start": 39,
"end": 60,
"expression": {
"type": "CallExpression",
"start": 39,
"end": 59,
"callee": {
"type": "MemberExpression",
"start": 39,
"end": 50,
"object": {
"type": "Identifier",
"start": 39,
"end": 46,
"name": "console"
},
"property": {
"type": "Identifier",
"start": 47,
"end": 50,
"name": "log"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "Identifier",
"start": 51,
"end": 55,
"name": "args"
},
{
"type": "Identifier",
"start": 57,
"end": 58,
"name": "a"
}
],
"optional": false
}
}
]
}
}
],
"sourceType": "module"
}

变量声明就是 VariableDeclaration,包含 declarations 数组和 kind,之所以是数组是因为有 const a = 1, b = 2; 这种写法。

数组中的每个元素是 VariableDeclarator,包含 idinit

idIdentifier 是变量名。

init 即初始值,包括 typevalue,此处 typeLiteralvalue1

kind 即声明类型,此处是 const

AST 应用

  • Babel、ESLint 插件
    • Babel 主要做代码转换
    • ESLint 主要做错误检查和修复
  • 代码转换
    • ES6 转 ES5
    • TypeScript、JSX 转 JavaScript
    • css 预处理器编译
  • 代码压缩/混淆
    • terser-webpack-plugin
  • 模板编译
    • JSX、Vue 模板编译
  • Code2Code
    • 如 vue-to-react
  • IDE
    • 代码高亮、代码提示、代码格式化
  • 可视化编程(LowCode 方向)
    • 相比于 Schema 驱动,AST 驱动更加灵活,配合 CloudIDE、CodeSandbox 等浏览器端在线编译实现

Babel

Babel 解析代码后生成的 AST 是以 ESTree 作为基础,并略作修改。

Babel 核心工具包

工具 说明
@babel/core Babel 转码的核心包,包括了整个 babel 工作流(集成@babel/types)
@babel/parser 解析器,将代码解析为 AST
@babel/traverse 遍历/修改 AST 的工具
@babel/generator 生成器,将 AST 还原成代码
@babel/types 包含手动构建 AST 和检查 AST 节点类型的方法
@babel/template 可将字符串代码片段转化为 AST 节点

Babel 插件

相当于是指令,告诉 Babel 如何转换代码。

主要分为两类:

  • 语法插件。作用于 @babel/parser,负责将代码转化为 AST。官方插件以 babel-plugin-syntax 开头。
  • 转换插件。作用于 @babel/core,负责转换 AST 的形态。绝大情况下我们都是在编写转换插件。

插件的本质是编写各种 visitor 去访问 AST 的节点,并进行 parse,将节点 transform 最终 generate 出代码。

如 ES6 => ES5 let 转 var:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function (babel) {
const { types: t } = babel;

return {
name: "let-to-var",
visitor: {
VariableDeclaration(path) {
if (path.node.kind === "let") {
path.node.kind = "var";
}
},
},
};
}

ESLint 插件

变量名长度限制:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports.rules = {
"var-lenght": (context) => {
VariableDeclaration(node) {
if (node.id.name.length <= 2) {
context.report({
node,
message: "变量名长度需要大于 2 个字符",
});
}
},
}
}

JSX 节点

1
<Button>{props.name}</Button>

转化为

1
2
3
4
5
types.jsxElement(
types.jsxOpeningElement(types.jsxIdentifier("Button"), []),
types.jsxClosingElement(types.jsxIdentifier("Button")),
[types.jsxExpressionContainer(types.identifier("props.name"))]
);
1
2
3
4
<>
<Button>{props.name}</Button>
<Button>{props.age}</Button>
</>

转化为

1
2
3
4
5
6
7
8
9
10
11
12
types.jsxFragment(types.jsxOpeningFragment(), types.jsxClosingFragment(), [
types.jsxElement(
types.jsxOpeningElement(types.jsxIdentifier("Button"), []),
types.jsxClosingElement(types.jsxIdentifier("Button")),
[types.jsxExpressionContainer(types.identifier("props.name"))]
),
types.jsxElement(
types.jsxOpeningElement(types.jsxIdentifier("Button"), []),
types.jsxClosingElement(types.jsxIdentifier("Button")),
[types.jsxExpressionContainer(types.identifier("props.age"))]
),
]);

引用

手把手带你走进 Babel 的编译世界——BoBoooooo