Skip to content

代码生成

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> 为例:

  1. AST 输入:经过 parse 和 transform 后的 AST
  2. 代码生成
    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 提供,用于创建虚拟节点和处理响应式更新。