代码生成
TIP
代码生成是编译器的最后一个阶段,将转换后的 AST 生成最终的渲染函数代码字符串。
概述
代码生成(Codegen)是 compiler-core
编译流程的第三步,负责将经过 transform
转换后的 AST 生成可执行的渲染函数代码。
整个过程就是遍历 AST 节点,根据不同的节点类型生成对应的 JavaScript 代码字符串。
核心流程
1. 创建代码生成上下文
typescript
function createCodegenContext(ast) {
const context = {
code: ``, // 生成的代码字符串
level: 0, // 缩进级别
helper(name) { // 获取辅助函数名
return `_${helperNameMap[name]}`
},
push(code) { // 添加代码
context.code += code
},
indent() { // 增加缩进
newLine(++context.level)
},
deindent() { // 减少缩进
newLine(--context.level)
}
}
return context
}
2. 生成函数前言
生成渲染函数的开头部分,包括导入辅助函数和函数声明:
typescript
function genFunctionPreamble(ast, context) {
const { push, newLine } = context
// 生成辅助函数导入
if (ast.helpers.length > 0) {
push(`const {${ast.helpers.map(item =>
`${helperNameMap[item]}:${context.helper(item)}`
)}} = Vue`)
}
// 生成渲染函数声明
newLine()
push(`return function render(_ctx) {`)
}
3. 节点代码生成
根据不同的节点类型生成相应的代码:
文本节点
typescript
function genText(node, context) {
context.push(JSON.stringify(node.content))
}
插值表达式
typescript
function genInterpolation(node, context) {
const { push, helper } = context
push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context)
push(')')
}
简单表达式
typescript
function genExpression(node, context) {
context.push(node.content)
}
VNode 调用
typescript
function genVnodeCall(node, context) {
const { push, helper } = context
const { tag, props, children, isBlock } = node
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(),`)
}
const h = isBlock ? CREATE_ELEMENT_BLOCK : CREATE_ELEMENT_VNODE
push(`${helper(h)}(`)
if (isBlock) push(')')
push(')')
}
4. 核心调度函数
typescript
function genNode(node, context) {
switch (node.type) {
case NodeTypes.TEXT:
genText(node, context)
break
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context)
break
case NodeTypes.VNODE_CALL:
genVnodeCall(node, context)
break
}
}
生成过程示例
以模板 <div></div>
为例:
- AST 输入:经过 parse 和 transform 后的 AST
- 代码生成:javascript
const {toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue return function render(_ctx) { return _createElementVNode("div", null, _toDisplayString(_ctx.msg)) }
完整流程
typescript
function generate(ast) {
const context = createCodegenContext(ast)
const { push, indent, deindent } = context
// 1. 生成函数前言
genFunctionPreamble(ast, context)
// 2. 生成函数体
indent()
push(`return `)
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`)
}
// 3. 结束函数
deindent()
push(`}`)
return context.code
}
辅助函数映射
代码生成过程中会用到一些运行时辅助函数:
typescript
export const helperNameMap = {
[TO_DISPLAY_STRING]: 'toDisplayString', // 转换为显示字符串
[CREATE_ELEMENT_VNODE]: 'createElementVNode', // 创建元素VNode
[OPEN_BLOCK]: 'openBlock', // 打开Block
[CREATE_ELEMENT_BLOCK]: 'createElementBlock', // 创建元素Block
[Fragment]: 'Fragment', // Fragment组件
}
这些辅助函数在运行时由 Vue 提供,用于创建虚拟节点和处理响应式更新。