编译器概览
将源代码翻译为目标代码的过程称为编译,完整的编译过程通常包含以下几个步骤:

对于Vue.js模板编译器,源代码就是组件的模板,而目标代码就是能够在浏览器平台上运行的JavaScript代码。

Vue.js模板编译器的目标代码实际就是渲染函数。
其具体工作流程如下:

首先会对模板进行词法分析和语法分析,得到模板AST。接着,将模板AST转换成JavaScript AST。最后,根据JavaScript AST生成JavaScript代码,即渲染函数代码。
具体而言,Vue.js将这三部分分成具体的三个功能函数执行:
- 用来将模板字符串解析为模板
AST的解析器(parse) - 用来将模板
AST转换为JavaScript AST的转换器(transformer) - 用来根据
JavaScript AST生成渲染函数代码的生成器(generator)
完整的流程如下:
模板解析器实现原理
有限状态机
解析器的参数为字符串模板,解析会逐个读取字符串模板中的字符,并根据一定的规则将整个字符串切割为一个个Token。其依据规则就是有限状态自动机。
所谓有限状态就是有限个状态,自动机则是随着字符的输入,解析器会自动的在不同状态间迁移,例如在以下模板中:
1 | <p>Vue<p> |
解析器首先处于初始状态1,然后读取到字符<,此时状态机会进入下一个状态,即标签开始状态2,在该状态下,会读取到字符p,此时会认为进入到标签名称状态3,接着读取到>,此时状态机会从该状态迁移回初始状态1,并记录标签名称状态下产生的标签名称p。接着会循环往复,直到解析完整,得到最终的三个Token。
- 开始标签:
<p> - 文本节点:
vue - 结束标签:
</p>
因此通过有限状态自动机规则,解析器会根据模板字符串的内容不断地切换当前的状态,并记录当前状态的产物,得到一系列的Token,其本质就是实现模板的标记化,方便后续处理。
例如有以下模板:
1 | <div><p>Vue</p><p>Template</p></div> |
通过有限自动状态机转换后,得到如下tokens:
1 | const tokens = tokenzie(`<div><p>Vue</p><p>Template</p></div>`) |
构造AST
Vue.js的模板本质上和HTML类似,是一种标记语言,格式非常固定,标签元素之间天然嵌套,形成父子关系。因此,可以根据模板解析后生成的Token构造一个树形结构的AST。
构造AST的过程就是对Token列表进行扫描的过程。根据Token中的type信息进行父子关系组成,从而构成AST树。
例如之前的示例模板,转换后的AST结构为:
1 | const ast = parse(`<div><p>Vue</p><p>Template</p></div>`) |
AST的转换
AST转换就是对AST进行一系列操作,将其转换为新的AST的过程。因此我们可以对模板AST进行操作,将其转换为JavaScript AST,转换后的AST用于代码生成。
将模板AST转换为JavaScript AST
例如上述模板:
1 | <div><p>Vue</p><p>Template</p></div> |
与这段模板等价的渲染函数是:
1 | function render() { |
上述渲染函数的JavaScript代码所对应的JavaScript AST就是我们的转换目标。与模板AST是模板的描述一样,JavaScript AST就是JavaScript代码的描述,因此,本质上,我们需要设计一些数据结构来描述渲染函数的代码。
例如,一个函数的声明语句可以由以下几部分组成:
- id: 函数名称 是一个标识符
Identifier - params: 函数参数 是一个数组
- body: 函数体 函数体可以包含多个语句,也是个数组
因此,可以设计如下基本数据结构来描述函数声明语句:
1 | const FunctionDeclNode = { |
同样,我们也可以设计其他的语句,最终效果如下所示:
1 | const FunctionDeclNode = { |
上述代码就是对渲染函数代码的完整描述。
代码生成
构造完JavaScript AST后,就可以生成渲染函数的代码了,其本质就是字符串的拼接,访问JavaScript AST的每一个节点,并为其生成相应的JavaScript代码。
1 | function compile(template) { |
以FunctionDecl节点为例,使用genFunctionDecl函数为该类型节点生成对应的JavaScript代码:
1 | function genFunctionDecl(node, context) { |
最终生成的代码为:
1 | function render () { |
总结
Vue.js的模板编译器用于将模板编译为渲染函数,其工作流程大致分为三个步骤:
- 分析模板,将其解析为模板
AST。 - 将模板
AST转换为用于描述渲染函数的JavaScript AST。 - 根据
JavaScript AST生成渲染函数代码。