概述
编译与转译
编译(Compile):通常是 高级语言 → 机器码/字节码,比如 C → 汇编。
转译(Transpile):通常是 一种语言的源代码 → 另一种语言的源代码,保持抽象层级接近。
前端中常见的转译操作,如下:
- ES6 -> ES5
- TS -> JS
- SASS -> CSS
代码转译示例
Babel库是目前非常流行的 Javascript Transpiler,即JS转译器。
// ES6
let foo = 123// ES5
var foo = 123
Babel转译演示
- 安装
npm install @babel/core @babel/cli @babel/preset-env
- 创建babel.config.json
{"presets": ["@babel/preset-env"],"plugins": []
}
- babel转译
babel src --out-dir dist
- plugins使用
{"presets": [],"plugins": ["@babel/plugin-transform-block-scoping"]
}
转译的原理
核心步骤
- parse:通过 parser 把源码转成抽象语法树(AST)
- transform:遍历 AST,调用各种 transform 插件对 AST 进行增删改
- generate:把转换后的 AST 输出成目标代码

可视化工具
AST explorer 是一个 AST 可视化工具,通过它可以查看各种编程语言代码解析后的 AST 结构,帮助开发者更直观地观察代码与 AST 节点树具体节点的对应关系。
查看代码:https://astexplorer.net/#/gist/2335b6e2175368119301cc8edad3642f/a2cefc03c9451ed387caebe338a2c5e99b501923
parse详解
通过 parser 把源码转成抽象语法树(AST)
- 词法分析
- 语法分析
词法分析
将整个代码字符串分割成最小语法单元数组。这些词法单元(tokens)包括数字,标点符号,运算符等,这些词法单元之间都是独立的。
// 源代码
let foo = 123// 词法分析转换后
const tokens = [{"type": { "label": "name" }, "value": "let", "start": 0, "end": 3},{"type": { "label": "name" }, "value": "foo", "start": 4, "end": 7},{"type": { "label": "=" }, "value": "=", "start": 8, "end": 9 },{"type": { "label": "num" }, "value": 123, "start": 10, "end": 13 },{"type": { "label": "eof" }, "start": 13, "end": 13}
]
注:可视化工具查看分割的tokens集合。
利用状态机,简单实现词法分析:

:::info
01demo
:::
语法分析
将词法分析出来的 tokens 按照不同的语法结构如声明语句、赋值表达式等转化成有语法含义的抽象语法树结构。

AST 是对源码的抽象,字面量、标识符、表达式、语句、模块语法、class 语法都有各自的 AST。
- Literal:字面量
- Identifer:标识符
- statement:语句
- Declaration:声明
- Expression:表达式
- Class:类
- Modules:模块
- 其他
- program:整个程序的节点
- directives:指令,例如:"use strict"
- comments:注释
const tokens = [{ type: { label: 'name' }, start: 0, end: 3, value: 'let' },{ type: { label: 'name' }, start: 4, end: 7, value: 'foo' },{ type: { label: '=' }, start: 8, end: 9, value: '=' },{ type: { label: 'num' }, start: 10, end: 13, value: 123 },{ type: { label: 'eof' }, start: 13, end: 13 }
]const AST = {"type": "Program","start": 0,"end": 13,"body": [{"type": "VariableDeclaration","start": 0,"end": 13,"declarations": [{"type": "VariableDeclarator","start": 4,"end": 12,"id": {"type": "Identifier","start": 4,"end": 7,"name": "foo"},"init": {"type": "NumericLiteral","start": 10,"end": 13,"value": 123}}],"kind": "let"}]
}
如何处理复杂的语法分析呢,代码如下:
if (true) {if (true) {let foo = 123}
}const tokens = [{ type: { label: 'if' }, start: 0, end: 2, value: 'if' },{ type: { label: '(' }, start: 3, end: 4 },{ type: { label: 'true' }, start: 4, end: 8, value: 'true' },{ type: { label: ')' }, start: 8, end: 9 },{ type: { label: '{' }, start: 10, end: 11 },{ type: { label: 'if' }, start: 14, end: 16, value: 'if' },{ type: { label: '(' }, start: 17, end: 18 },{ type: { label: 'true' }, start: 18, end: 22, value: 'true' },{ type: { label: ')' }, start: 22, end: 23 },{ type: { label: '{' }, start: 24, end: 25 },...{ type: { label: '}' }, start: 46, end: 47 },{ type: { label: '}' }, start: 48, end: 49 }
]
如何保证正确的嵌套关系,利用数据结构中的堆栈来实现
:::info
02demo
:::
transform详解
对AST对象进行遍历,遍历的过程中处理到不同的 AST 节点会调用注册的相应的 visitor 函数,visitor 函数里可以对 AST 节点进行增删改,返回新的 AST。
- traverser 遍历(深度优先遍历)
- transformer 转换
:::info
03demo
:::
generate详解
AST 根节点进行递归的字符串拼接,就可以生成目标代码的字符串。
:::info
04demo
:::
自定义Babel插件
// my-plugin.js
module.exports = ({ types: t }) => {return {name: "myPlugin",visitor: {VariableDeclaration(path) {path.node.kind = "var";},Identifier(path) {path.node.name = "bar";},},};
};
