编译器
# 编译流程
编译:把 A (源代码)翻译为 B (目标代码)的过程
源代码 => 词法分析 => 语法分析 => 语义分析 => 中间代码生成 => 优化 => 目标代码生成 => 目标代码
编译前端:词法分析、语法分析、语义分析,通常与目标的平台无关,仅负责分析源代码
编译中端:中间代码生成、优化
编译后端:目标代码生成,通常与目标的平台有关,负责生成中间代码和目标代码,可能会包含编译中端
源代码:
<div>
<h1 :id="appId">this is h1</h1>
</div>
经过 vue 的模板编译后生成的目标代码:
function render() {
return h('div', [
h('h1', { id: appId }, 'this is h1')
])
}
<div>
<h1 v-if="ok">this is h1</h1>
</div>
转化成的 ast 如下
const ast = {
type: 'root',
children: [
{
type: 'element',
tag: 'div',
children: [
{
type: 'element',
tag: 'h1',
props: [
{
// 指令
type: 'directive',
// 指令名称
name: 'if',
expression: {
// 表达式
type: 'expression',
content: 'ok'
}
}
],
}
]
}
]
}
- 根据 type 来进行区分
- 子节点都在 children 中
- 属性节点和指定节点存储在 props 中
- 不同类型的节点会有不同的对象属性描述
以上可以通过下面的代码来解释
const template = `
<div>
<h1 v-if="ok">this is h1</h1>
</div>
`
// 模板通过解析生成 templateAst
const templateAst = parse(template)
// 经过转换生成 jsAst
const jsAst = transforme(templateAst)
// 生成渲染函数
const code = generate(jsAst)
流程:
- 将模板字符串解析为模板 AST ,解析器 parser
- 将模板 AST 转换为 jsAST, 转换器 transformer
- 根据 jsAST 生成渲染函数,生成器 generator
该流程也可以参考 base/compiler/mini-compiler.js
# parse 实现
# 构造 ast
由于 html 的标签格式比较固定,标签元素之间天然嵌套,形成父子关系,因此可以通过树形结构构造一个 ast。
构造的过程中我们可以维护一个栈 elementStack ,在扫描 tokens 列表的过程中,每遇到开始一个节点我们就把它压入栈中,遇到一个结束标签则把当前栈顶的节点弹出。
# ast 的转换
当我们可以访问每个 ast 节点的时候,那就可以对某些特定节点进行增删改操作。
为了解耦节点的访问和操作,使用了插件化的架构,将节点的操作封装到独立的转换函数中。
context: 上下文,可以理解为程序在某个范围内的全局变量
react中React.createContext创建的上下文对象,允许将数据一层一层传递下去,无论组件嵌套多深都可以访问到vue中的inject/provide
在转换的时候,要保证子节点全部处理完成之后,才能处理处理父节点。因此对节点的访问需要增加进入和退出2个阶段。
当函数转换处于进入时,先进入父节点,然后进入子节点,当函数处于退出时,先退出子节点,再退出父节点。
# ast 转为 jsAST
const template = `<div><p>Vue</p><p>React</p></div>`
等价的渲染函数
function render() {
return h('div', [
h('p', 'Vue'),
h('p', 'React')
])
}
通过将模板 ast 转换为 jsAst 之后,可以通过跟节点的 jsNode 来访问转换后的 jsAst
js 中的函数声明语句
const FunctionDeclNode = {
// 代表该节点是函数声明
type: 'FunctionDecl',
// id 函数名称 标识符为 Identifier
id: {
type: 'Identifier',
name: 'render'
},
// 函数的参数
params: [],
// 函数体,可以包含多个语句
body: [
{
type: 'ReturnStatement',
return: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'h'
},
arguments: [
// 第一参数是字符串 div
{
type: 'StringLiteral',
value: 'div'
},
// 第二个参数是数组
{
type: 'ArrayExpression',
elements: [
// 第一个元素 h 调用
{
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'h'
},
// 第一个参数是字符串 p, 第二个参数也是 字符串
arguments: [
{
type: 'StringLiteral',
value: 'p'
},
{
type: 'StringLiteral',
value: 'Vue'
}
]
},
{
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'h'
},
arguments: [
{
type: 'StringLiteral',
value: 'p'
},
{
type: 'StringLiteral',
value: 'React'
}
]
}
]
}
]
}
}
]
};
# 代码生成
代码生成的本质是字符串的拼接,为每一种类型的节点生成相符合的 js 代码。
- 定义上下文
context, - 储存最终生成的渲染函数的
js代码片段,context.code - 代码拼接方法
context.push - 通过
generateNode来生成代码 - 返回
context.code