Vue.js模板编译器用于把模板编译为渲染函数:
- 分析模板,将其解析为AST
- 将模板AST转换为用于描述渲染函数的JavaScript AST
- 根据JavaScript AST生成渲染函数代码
解析Token
为Vue.js模板构造AST,AST在结构上和模板同构
首先,将模板解析为一个个token,利用有限状态自动机构造一个词法分析器,词法分析的过程就是状态机在不同状态之间迁移的过程,在此过程中,状态机会产出一个个token,生成一个token列表我们使用该token列表来构造用于描述模板的AST
1 | //定义状态机状态 |
比如:
1 | const tokens = tokenize(`<div><p>Vue</p></div>) |
得到:
1 | const tokens = [ |
构建AST:
接下来,扫描token列表构建AST:
扫描Token列表维护一个标签栈,每当扫描到一个开始标签结点,将其压入栈顶,栈顶的结点始终作为下一个扫描的结点的父结点,这样,当所有token扫描完,构建一颗AST树
1 | //扫描token列表构建AST |
AST的转换
AST的转换,即对AST的一系列操作
transform函数完成AST的转换
为了解耦结点的访问和操作,设计了插件化架构,将结点的操作封装到独立的转换函数,这些转换函数可以通过context.nodeTransforms来注册,这里的context称为转换上下文,上下文对象维护程序的当前状态,可以实现结点的替换删除功能,但是有时候,当前结点的转换工作依赖于子节点的转换结果,所以为了优先执行子节点的转换,我们将整个转换过程分为“进入阶段“和”退出阶段“,每个转换函数分成两个阶段执行,这样可以实现更加细粒度的转换控制
1 |
|
增加回退功能的traverseNode:
1 | function traverseNode(ast, context) { |
将模板AST转为JavaScript AST
代码生成的过程就是字符串的拼接过程,需要为不同AST结点编写对应的代码生成函数。
这段模板:
1 | <div> |
等价于下面这段渲染函数:
1 | function render(){ |
等价于下面这段JavaScript AST:
1 | const FunctionDeclNode = { |
编写一些用来辅助创建js ast的函数
1 | function createStringLiteral(value) { |
编写转换文本节点,标签结点和根结点的函数:
1 | //编写转换文本节点函数 |
编译代码,代码生成:
1 | //编译 |
例子:
1 | const ast = parse(`<div><p>Vue</p><p>Template</p></div>`) |
转换为:
1 | function render(){ |