编译器概览
将源代码翻译为目标代码的过程称为编译,完整的编译过程通常包含以下几个步骤:
对于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
生成渲染函数代码。