十二、编译器核心
模板 DSL 编译器
vuejs 编译流程
- 模板编译器对 HTML 源码进行词法分析获得模板 AST
- 模板 AST 转换成 Javascript AST
- 根据 JS AST 生成对应 JS 代码
AST 即抽象语法书,下面展示了一个简单的 AST
即根节点下包裹一个 div 节点
// type:定义节点类型
// children:定义子节点
// tag:定义标签类型
// props:定义标签对应的属性节点
// name:指令节点特有,表示指令名称
// content:表达式节点特有,表示表达式内容
const exp = {
type: "Root",
children: [
{
type: "Element",
tag: "div",
},
],
};
对应解析函数
parse 函数:解析字符串模板为模板 AST
transform 函数:模板 AST 转换成 Javascript AST
generate 函数:JS AST 转换为 JS 代码
有限状态机
解析器 parser 的有限状态自动机
解析器会将字符串模板划分为多个 token
譬如 <p>1</p>
会被划分为 3 个 token: <p> & 1 & </p>
有限状态自动机可以理解为:在有限个状态内自动进行状态转移,下图就是 parser 的分析图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qC2d4oPW-1673567813480)(…/imgs/vue/vuejs_optimize/vp1.png)]
解析结果
使用 tokenize 函数(源码过长,此处不贴出)获取字符串模板的所有 token
下方代码表示对一段字符串模板进行解析后得到的 3 个 token
正则表达式的本质就是有限自动机
const tokens = tokenize(`<p>Vue</p>`);
// [
// { type: 'tag', name: 'p' }, // 开始标签
// { type: 'text', content: 'Vue' }, // 文本节点
// { type: 'tagEnd', name: 'p' } // 结束标签
// ]
构造 AST
引用 通用用途语言 GPL
中的 梯度下降算法
来实现 DSL
(因为 vuejs 无运算符优先级,故无需考虑)
构建过程
主要有三个元素,其中 elementStack 用来维护一个栈,栈中存放 token,之后出栈就完美的构成 AST
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQGoL6Ka-1673567813484)(…/imgs/vue/vuejs_optimize/vp2.png)]
AST 转换
AST
的转换,指的是对 AST
进行一系列操作,将其转换为新的 AST
的过程(用于适配不同语言)
使用 DFS
来替换 AST
中的指定标签,实现 AST
转换的功能!
该替换操作可以解耦为以外部函数,每次都使用对应函数来处理替换、增删功能
转换标签节点的代码,必须要卸载退出阶段的回调函数内;
这样可以保证所有子节点处理完毕后才开始转换!
AST 需要转换成 Javascript AST
转换完成后即可使用 render 渲染该 AST
代码生成
本节实现 generate 代码,将 Javascript AST 生成对应的 JS 代码
基本原理
这是 generate 函数运作的基本流程
- context 表示上下文,在里面处理文本
- genNode 用来生成 JS,调用上下文 context
可以在 context 对象内部添加一些文本处理函数,譬如
添加缩进indent()
或者换行newline()
function generate(node) {
const context = {
// 存储最终生成的渲染函数代码
code: "",
// 在生成代码时,通过调用 push 函数完成代码的拼接
push(code) {
context.code += code;
},
};
// 调用 genNode 函数完成代码生成的工作,
genNode(node, context);
// 返回渲染函数代码
return context.code;
}
genNode
很简单,使用 switch 匹配不同的 JavascriptAST 节点,并使用对应生成函数即可