15.1 模板 DSL 的编译器
编译器就是把「一种语言 A」翻译成「另一种语言 B」的程序;Vue 的模板编译器把模板编译成渲染函数。
一、编译器在做什么
- 语言 A 叫源代码(source code),语言 B 叫目标代码(object code / target code),翻译过程叫编译(compile)。
- 完整的编译过程通常包含:词法分析 → 语法分析 → 语义分析 → 中间代码生成 → 优化 → 目标代码生成。
- 按是否与目标平台相关,整个过程分为两段:
| 阶段 | 包含环节 | 是否与目标平台相关 |
|---|---|---|
| 编译前端 | 词法分析、语法分析、语义分析 | 无关,只负责分析源代码 |
| 编译后端 | 中间代码生成、优化、目标代码生成 | 有关 |
「中间代码生成」和「优化」有时被单独称为「中端」;编译后端并不一定包含这两个环节,取决于具体场景与实现。
二、Vue 模板编译器的输入与输出
- 源代码:组件的模板。
- 目标代码:能在浏览器(或任何拥有 JavaScript 运行时的平台)上运行的 JavaScript 代码,最终形态就是渲染函数。
// 模板
`<div><p>Vue</p></div>`
// 编译产物:渲染函数
function render() {
return h('div', [h('p', 'Vue')])
}
三、三大阶段
模板字符串
│ parse(词法分析 + 语法分析)
▼
模板 AST
│ transform(把模板 AST 转成 JavaScript AST)
▼
JavaScript AST
│ generate(生成代码)
▼
渲染函数代码
| 函数 | 职责 | 产出 |
|---|---|---|
parse | 对模板做词法分析与语法分析 | 模板 AST |
transform | 把模板 AST 转换为 JavaScript AST | JavaScript AST |
generate | 根据 JavaScript AST 生成渲染函数代码 | 字符串源码 |
四、AST 是什么
- AST = abstract syntax tree,即抽象语法树,本质是一个具有层级结构的对象。
- 模板 AST 与模板同构:模板怎么嵌套,AST 就怎么嵌套。
- 不同类型的节点靠
type属性区分;标签节点的子节点存在children数组中;不同类型的节点用不同属性描述。
// 模板 <div><p>Vue</p></div> 对应的模板 AST(示意)
{
type: 'Root',
children: [
{
type: 'Element',
tag: 'div',
children: [
{ type: 'Element', tag: 'p', children: [{ type: 'Text', content: 'Vue' }] }
]
}
]
}
五、语义分析举例
语法分析只保证结构合法,语义分析还要检查含义是否成立:
- 检查
v-else指令是否存在相符的v-if指令; - 分析属性值是否是静态的、是否是常量;
- 插槽是否会引用上层作用域的变量。
六、模板是 DSL,不是通用语言
- DSL(domain-specific language,领域特定语言):只满足特定场景的特定用途,不要求图灵完备,Vue 模板就属于 DSL。
- GPL(general-purpose language,通用编程语言):图灵完备,可以用它来实现 DSL,如 JavaScript。
- 不同用途的编译器之间可能差异极大,唯一的共同点就是把源代码转换成目标代码。
正因为模板是受限的 DSL,编译器才敢对它做静态分析(静态提升等优化见第 17 章)。
相关
- 15.2 parser 的实现原理与状态机:
parse的第一步——如何把模板字符串切成 Token