[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 | function test(args) { |
转化为 AST:
1 | { |
变量声明就是 VariableDeclaration,包含 declarations 数组和 kind,之所以是数组是因为有 const a = 1, b = 2; 这种写法。
数组中的每个元素是 VariableDeclarator,包含 id 和 init。
id 即 Identifier 是变量名。
init 即初始值,包括 type 和 value,此处 type 为 Literal,value 为 1。
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 | export default function (babel) { |
ESLint 插件
变量名长度限制:
1 | module.exports.rules = { |
JSX 节点
1 | <Button>{props.name}</Button> |
转化为
1 | types.jsxElement( |
1 | <> |
转化为
1 | types.jsxFragment(types.jsxOpeningFragment(), types.jsxClosingFragment(), [ |