Skip to content

实现编译器 - Codegen 代码生成

本节对标 Vue 3 源码 @vue/compiler-core 中的 codegen.ts 源码位置:packages/compiler-core/src/codegen.ts

Codegen 的作用

Codegen(Code Generation,代码生成)是编译器三段式的最后一个阶段。它将 Transform 后的 AST 转换为可执行的 render 函数代码字符串

Transform 后的 AST


   ┌─────────┐
   │ Codegen │
   └────┬────┘


  render 函数代码字符串

// 例如:
// const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
//
// return function render(_ctx, _cache) {
//   return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
// }

Codegen 的核心工作:

  1. 生成 import / 解构语句(导言部分)
  2. 生成 render 函数签名
  3. 递归遍历 AST 节点,根据类型生成对应代码
  4. 处理缩进和格式化

CodegenContext 上下文

ts
// 对标 packages/compiler-core/src/codegen.ts - CodegenContext
// 代码生成上下文接口:管理生成过程中的代码输出和格式化
interface CodegenContext {
  code: string                       // 最终生成的代码字符串
  indentLevel: number                // 当前缩进层级
  push(code: string): void           // 向代码字符串追加内容
  indent(): void                     // 增加缩进并换行
  deindent(): void                   // 减少缩进并换行
  newline(): void                    // 换行并应用当前缩进
  helper(key: symbol): string        // 将辅助函数 symbol 转为带 _ 前缀的变量名
}

// 创建代码生成上下文的工厂函数
function createCodegenContext() : CodegenContext {
  const context: CodegenContext = {
    code: '',          // 初始为空字符串,后续通过 push 不断追加
    indentLevel: 0,    // 初始缩进为 0

    // 向输出的代码字符串追加内容
    push(source: string) {
      context.code += source
    },

    // 增加缩进层级并换行(进入代码块时调用)
    indent() {
      context.indentLevel++
      context.newline() // 立即换行并应用新的缩进
    },

    // 减少缩进层级并换行(退出代码块时调用)
    deindent() {
      context.indentLevel--
      context.newline() // 立即换行并应用新的缩进
    },

    // 换行并在新行前添加当前缩进级别的空格(每级2个空格)
    newline() {
      context.code += '\n' + '  '.repeat(context.indentLevel)
    },

    // 将辅助函数 symbol 转换为代码中使用的变量名
    // 例如:TO_DISPLAY_STRING → '_toDisplayString'
    helper(key: symbol) {
      return `_${helperNameMap[key]}`
    },
  }

  return context
}

上下文设计解析

这个上下文的设计非常巧妙:

  • code:最终生成的代码字符串,通过 push 不断追加
  • push:所有代码输出都通过这个方法,方便调试和 source map 生成
  • indent/deindent:控制缩进层级,生成格式化的代码
  • newline:换行并添加当前缩进
  • helper:将辅助函数 symbol 转换为带 _ 前缀的变量名

generate 入口函数

ts
// 对标 packages/compiler-core/src/codegen.ts - generate
// 代码生成的入口函数:将转换后的 AST 生成完整的 render 函数代码字符串
export function generate(ast: any) {
  const context = createCodegenContext()                 // 创建代码生成上下文
  const { push, indent, deindent, newline } = context   // 解构常用方法

  // 第一步:生成导言部分(辅助函数的解构声明)
  genFunctionPreamble(ast, context)

  // 第二步:生成 render 函数签名
  const functionName = 'render'
  const args = ['_ctx', '_cache']      // render 函数的参数列表
  const signature = args.join(', ')    // 拼接为 '_ctx, _cache'

  push(`function ${functionName}(${signature}) {`)   // 输出函数声明头部
  indent()                                            // 进入函数体,增加缩进
  push('return ')                                     // 输出 return 关键字

  // 第三步:生成 render 函数体(返回的 VNode 表达式)
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)  // 根据 codegenNode 生成代码
  } else {
    push('null')  // 没有 codegenNode 时返回 null
  }

  deindent()     // 退出函数体,减少缩进
  push('}')      // 输出函数闭合大括号

  return {
    code: context.code,  // 返回最终生成的代码字符串
  }
}

genFunctionPreamble —— 生成函数导言

ts
// 对标 packages/compiler-core/src/codegen.ts - genFunctionPreamble
// 生成函数导言部分:从 Vue 对象中解构出需要的辅助函数
function genFunctionPreamble(ast: any, context: CodegenContext) {
  const { push, newline } = context
  // 生成辅助函数的别名映射,如 'toDisplayString: _toDisplayString'
  const aliasHelper = (s: symbol) =>
    `${helperNameMap[s]}: _${helperNameMap[s]}`

  if (ast.helpers.length > 0) {
    const VueBinging = 'Vue'
    // 生成解构语句:const { toDisplayString: _toDisplayString, ... } = Vue
    push(
      `const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinging}`,
    )
    push('\n')
    newline()
    // 添加 return 关键字,使整个输出成为一个返回 render 函数的表达式
    push('return ')
  }
}

生成的代码示例:

ts
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

return function render(_ctx, _cache) {
  return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
}

导言做了两件事:

  1. Vue 对象中解构出需要的辅助函数,并用 _ 前缀重命名(避免命名冲突)
  2. 添加 return 关键字,使整个输出是一个返回 render 函数的表达式

genNode —— 节点分发

ts
// 对标 packages/compiler-core/src/codegen.ts - genNode
// 节点代码生成的分发器:根据 AST 节点类型调用对应的代码生成函数
function genNode(node: any, context: CodegenContext) {
  switch (node.type) {
    case NodeTypes.TEXT:
      genText(node, context)             // 生成文本字面量
      break

    case NodeTypes.INTERPOLATION:
      genInterpolation(node, context)    // 生成 toDisplayString 调用
      break

    case NodeTypes.SIMPLE_EXPRESSION:
      genExpression(node, context)       // 生成表达式代码
      break

    case NodeTypes.ELEMENT:
      // 元素节点委托给其 codegenNode(通常是 VNODE_CALL)来生成代码
      genNode(node.codegenNode, context)
      break

    case NodeTypes.COMPOUND_EXPRESSION:
      genCompoundExpression(node, context) // 生成复合表达式(文本 + 插值拼接)
      break

    case NodeTypes.VNODE_CALL:
      genElement(node, context)          // 生成 createElementVNode 调用
      break

    default:
      break
  }
}

genNode 是一个分发器,根据节点类型调用对应的生成函数。注意 ELEMENT 类型会直接委托给其 codegenNode(通常是 VNODE_CALL 类型)。

genText —— 生成文本字面量

ts
// 对标 packages/compiler-core/src/codegen.ts - genText
// 生成文本节点的代码:将文本内容包装为字符串字面量
// 例如:{ content: "hello" } → 'hello'
function genText(node: any, context: CodegenContext) {
  context.push(`'${node.content}'`)
}

输入:{ type: TEXT, content: "hello" }

输出:'hello'

genInterpolation —— 生成 toDisplayString 调用

ts
// 对标 packages/compiler-core/src/codegen.ts - genInterpolation
// 生成插值表达式的代码:包装为 _toDisplayString(expr) 调用
function genInterpolation(node: any, context: CodegenContext) {
  const { push, helper } = context
  push(`${helper(TO_DISPLAY_STRING)}(`)  // 输出 '_toDisplayString('
  genNode(node.content, context)          // 递归生成内部表达式的代码
  push(')')                               // 输出闭合括号 ')'
}

输入:{ type: INTERPOLATION, content: { type: SIMPLE_EXPRESSION, content: "message" } }

输出:_toDisplayString(message)

toDisplayString 是 Vue 3 运行时提供的辅助函数,作用类似于 String(value),但对 null/undefined 等做了友好处理。

genExpression —— 生成表达式代码

ts
// 对标 packages/compiler-core/src/codegen.ts - genExpression
// 生成简单表达式的代码:直接输出表达式字符串
// 在完整 Vue 3 中,transform 阶段已经将 msg 转为 _ctx.msg,此处原样输出
function genExpression(node: any, context: CodegenContext) {
  context.push(node.content) // 直接输出表达式内容,如 'message' 或 '_ctx.msg'
}

输入:{ type: SIMPLE_EXPRESSION, content: "message" }

输出:message

在完整 Vue 3 中,expression 经过 transform 后已经被加上了 _ctx. 前缀,所以这里只需要原样输出。

genCompoundExpression —— 处理复合表达式

ts
// 对标 packages/compiler-core/src/codegen.ts - genCompoundExpression
// 生成复合表达式的代码:遍历 children 数组,对字符串直接输出,对节点递归生成
// children 数组格式如:[TextNode, " + ", InterpolationNode, " + ", TextNode]
function genCompoundExpression(node: any, context: CodegenContext) {
  const { push } = context

  for (let i = 0; i < node.children.length; i++) {
    const child = node.children[i]
    if (typeof child === 'string') {
      // 字符串类型:直接输出连接符 " + "
      push(child)
    } else {
      // AST 节点类型(TEXT 或 INTERPOLATION):递归调用 genNode 生成代码
      genNode(child, context)
    }
  }
}

复合表达式是 transformText 阶段合并的结果,其 children 数组交替包含 AST 节点和字符串连接符。

输入:

{
  type: COMPOUND_EXPRESSION,
  children: [
    { type: TEXT, content: "hi," },
    " + ",
    { type: INTERPOLATION, content: { type: SIMPLE_EXPRESSION, content: "message" } }
  ]
}

输出:'hi,' + _toDisplayString(message)

genElement —— 生成 createElementVNode 调用

ts
// 对标 packages/compiler-core/src/codegen.ts - genVNodeCall
// 生成元素的代码:输出 _createElementVNode(tag, props, children) 调用
function genElement(node: any, context: CodegenContext) {
  const { push, helper } = context
  const { tag, props, children } = node

  // 输出辅助函数名和左括号
  push(`${helper(CREATE_ELEMENT_VNODE)}(`)

  // 处理参数列表:genNullable 去除末尾多余的 null,genNodeList 生成逗号分隔的参数
  genNodeList(genNullable([tag, props, children]), context)

  push(')') // 输出闭合括号
}

// 从参数列表末尾去除 null 值,简化生成的代码
// 例如:['div', null, null] → ['div'](去掉尾部的 null)
// 但保留中间的 null:['div', null, children] → ['div', 'null', children]
function genNullable(args: any[]) {
  let i = args.length
  // 从末尾向前找到第一个非 null 值
  while (i--) {
    if (args[i] != null) break
  }
  // 截取到最后一个非 null 值,并将数组中的 null 值替换为字符串 'null'
  return args.slice(0, i + 1).map((arg) => arg || 'null')
}

// 生成参数列表:将节点数组转为逗号分隔的代码
function genNodeList(nodes: any[], context: CodegenContext) {
  const { push } = context

  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]

    if (typeof node === 'string') {
      push(node) // 字符串参数直接输出(如标签名 "'div'" 或 "null")
    } else {
      genNode(node, context) // AST 节点递归生成代码
    }

    if (i < nodes.length - 1) {
      push(', ') // 参数之间用逗号分隔
    }
  }
}

genNullable 的作用是去掉末尾的 null 参数。例如 createElementVNode('div', null, null) 可以简化为 createElementVNode('div')

输入:

{
  type: VNODE_CALL,
  tag: "'div'",
  props: null,
  children: { type: COMPOUND_EXPRESSION, children: [...] }
}

输出:_createElementVNode('div', null, 'hi,' + _toDisplayString(message))

生成的 Render 函数结构

完整的代码生成结果:

ts
// 输入模板:<div>hi,{{ message }}</div>

// 生成的代码:
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

return function render(_ctx, _cache) {
  return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
}

这段代码的结构:

  1. 导言:从 Vue 对象解构辅助函数
  2. render 函数:接收 _ctx(组件实例代理)和 _cache(缓存)
  3. 返回值:一个 createElementVNode 调用,描述 VNode 树

完整实现

ts
import { NodeTypes } from './ast'
import {
  CREATE_ELEMENT_VNODE,
  TO_DISPLAY_STRING,
  helperNameMap,
} from './runtimeHelpers'

// 代码生成入口函数
export function generate(ast: any) {
  const context = createCodegenContext()
  const { push, indent, deindent, newline } = context

  // 生成导言(辅助函数解构声明)
  genFunctionPreamble(ast, context)

  // 生成 render 函数
  push('function render(_ctx, _cache) {')
  indent()
  push('return ')

  // 根据 codegenNode 生成函数体
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push('null') // 空模板返回 null
  }

  deindent()
  push('}')

  return { code: context.code }
}

// 创建代码生成上下文
function createCodegenContext() {
  const context: any = {
    code: '',           // 累积的输出代码
    indentLevel: 0,     // 缩进层级
    push(source: string) {
      context.code += source  // 追加代码
    },
    indent() {
      context.indentLevel++   // 增加缩进
      context.newline()
    },
    deindent() {
      context.indentLevel--   // 减少缩进
      context.newline()
    },
    newline() {
      // 换行并添加缩进空格
      context.code += '\n' + '  '.repeat(context.indentLevel)
    },
    helper(key: symbol) {
      // Symbol → 带前缀的变量名,如 '_toDisplayString'
      return `_${helperNameMap[key]}`
    },
  }
  return context
}

// 生成导言:辅助函数解构 + return 关键字
function genFunctionPreamble(ast: any, context: any) {
  const { push, newline } = context
  // 将 Symbol 映射为 '原名: _原名' 格式的别名
  const aliasHelper = (s: symbol) =>
    `${helperNameMap[s]}: _${helperNameMap[s]}`

  if (ast.helpers.length > 0) {
    // 生成:const { toDisplayString: _toDisplayString, ... } = Vue
    push(
      `const { ${ast.helpers.map(aliasHelper).join(', ')} } = Vue`,
    )
    push('\n')
    newline()
    push('return ') // 整个输出是一个返回 render 函数的表达式
  }
}

// 节点分发器:根据类型调用对应的代码生成函数
function genNode(node: any, context: any) {
  switch (node.type) {
    case NodeTypes.TEXT:
      genText(node, context)               // 文本 → 字符串字面量
      break
    case NodeTypes.INTERPOLATION:
      genInterpolation(node, context)      // 插值 → toDisplayString 调用
      break
    case NodeTypes.SIMPLE_EXPRESSION:
      genExpression(node, context)         // 表达式 → 原样输出
      break
    case NodeTypes.ELEMENT:
      genNode(node.codegenNode, context)   // 元素 → 委托给 codegenNode
      break
    case NodeTypes.COMPOUND_EXPRESSION:
      genCompoundExpression(node, context) // 复合表达式 → 拼接输出
      break
    case NodeTypes.VNODE_CALL:
      genElement(node, context)            // VNode 调用 → createElementVNode
      break
  }
}

// 文本节点:输出为带引号的字符串
function genText(node: any, context: any) {
  context.push(`'${node.content}'`)
}

// 插值节点:包装为 _toDisplayString(expr) 调用
function genInterpolation(node: any, context: any) {
  const { push, helper } = context
  push(`${helper(TO_DISPLAY_STRING)}(`) // 输出函数名和左括号
  genNode(node.content, context)         // 生成内部表达式
  push(')')                              // 闭合括号
}

// 简单表达式:直接输出表达式字符串
function genExpression(node: any, context: any) {
  context.push(node.content)
}

// 复合表达式:交替输出文本/插值节点和 " + " 连接符
function genCompoundExpression(node: any, context: any) {
  for (let i = 0; i < node.children.length; i++) {
    const child = node.children[i]
    if (typeof child === 'string') {
      context.push(child) // 连接符 " + "
    } else {
      genNode(child, context) // AST 节点
    }
  }
}

// 元素节点:生成 _createElementVNode(tag, props, children) 调用
function genElement(node: any, context: any) {
  const { push, helper } = context
  const { tag, props, children } = node

  push(`${helper(CREATE_ELEMENT_VNODE)}(`)     // 输出函数名
  genNodeList(genNullable([tag, props, children]), context) // 输出参数列表
  push(')')
}

// 去除参数列表末尾多余的 null,保留中间的 null
function genNullable(args: any[]) {
  let i = args.length
  while (i--) {
    if (args[i] != null) break // 找到最后一个非 null 参数
  }
  return args.slice(0, i + 1).map((arg) => arg || 'null')
}

// 生成逗号分隔的参数列表
function genNodeList(nodes: any[], context: any) {
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    if (typeof node === 'string') {
      context.push(node) // 字符串参数直接输出
    } else {
      genNode(node, context) // AST 节点递归生成
    }
    if (i < nodes.length - 1) {
      context.push(', ') // 参数之间加逗号
    }
  }
}

更多代码生成示例

示例 1:纯文本

html
hi
ts
function render(_ctx, _cache) {
  return 'hi'
}

示例 2:单个插值

html
{{ message }}
ts
const { toDisplayString: _toDisplayString } = Vue

return function render(_ctx, _cache) {
  return _toDisplayString(_ctx.message)
}

示例 3:元素 + 文本

html
<div>hello</div>
ts
const { createElementVNode: _createElementVNode } = Vue

return function render(_ctx, _cache) {
  return _createElementVNode('div', null, 'hello')
}

示例 4:元素 + 复合表达式

html
<div>hi,{{ msg }}</div>
ts
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

return function render(_ctx, _cache) {
  return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.msg))
}

示例 5:带属性的元素

html
<div id="app" class="container">{{ msg }}</div>
ts
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

return function render(_ctx, _cache) {
  return _createElementVNode('div', {"id":"app","class":"container"}, _toDisplayString(_ctx.msg))
}

对比 React

维度Vue 3 CodegenReact JSX Compile
输出格式render 函数字符串JSX → jsx() / createElement() 调用
运行方式new Function(code) 生成函数直接编译为 JS 模块
辅助函数toDisplayString / createElementVNodejsx / jsxs / createElement
import 方式const { ... } = Vue 运行时解构import { jsx } from 'react/jsx-runtime'
运行时编译支持(但不推荐)不支持(必须预编译)
格式化indent/deindent 自动格式化Babel 输出 + prettier

一个关键区别:Vue 3 的 codegen 生成的是代码字符串,需要通过 new Function() 在运行时转换为函数。而 React 的 JSX 编译(通过 Babel/SWC)直接输出 JS 代码,不需要运行时 new Function

Vue 支持运行时编译(在浏览器中直接编译模板),这在开发阶段和某些动态场景下非常有用,但在生产环境中建议使用构建时编译以获得更好的性能。

测试用例

ts
import { baseParse, NodeTypes } from '../src/parse'
import { transform } from '../src/transform'
import { generate } from '../src/codegen'
import { transformElement } from '../src/transforms/transformElement'
import { transformText } from '../src/transforms/transformText'
import { transformExpression } from '../src/transforms/transformExpression'

describe('codegen', () => {
  // 测试:纯文本应生成返回字符串字面量的 render 函数
  it('should generate code for text', () => {
    const ast = baseParse('hi')
    transform(ast, { nodeTransforms: [transformExpression] })
    const { code } = generate(ast)

    // 使用 toMatchInlineSnapshot 验证生成代码的精确格式
    expect(code).toMatchInlineSnapshot(`
      "function render(_ctx, _cache) {
        return 'hi'
      }"
    `)
  })

  // 测试:插值表达式应生成包含 toDisplayString 调用的代码
  it('should generate code for interpolation', () => {
    const ast = baseParse('{{ message }}')
    transform(ast, { nodeTransforms: [transformExpression] })
    const { code } = generate(ast)

    // 断言:生成的代码包含辅助函数和表达式变量名
    expect(code).toContain('toDisplayString')
    expect(code).toContain('message')
  })

  // 测试:简单元素应生成 createElementVNode 调用
  it('should generate code for simple element', () => {
    const ast = baseParse('<div>hello</div>')
    transform(ast, {
      nodeTransforms: [transformExpression, transformElement, transformText],
    })
    const { code } = generate(ast)

    // 断言:代码中包含 createElementVNode、标签名和文本内容
    expect(code).toContain('createElementVNode')
    expect(code).toContain("'div'")
    expect(code).toContain("'hello'")
  })

  // 测试:元素内包含插值时应生成复合表达式(用 + 连接文本和 toDisplayString)
  it('should generate code for element with interpolation', () => {
    const ast = baseParse('<div>hi,{{ msg }}</div>')
    transform(ast, {
      nodeTransforms: [transformExpression, transformElement, transformText],
    })
    const { code } = generate(ast)

    // 断言:代码中同时包含元素创建、字符串转换、文本和连接符
    expect(code).toContain('createElementVNode')
    expect(code).toContain('toDisplayString')
    expect(code).toContain("'hi,'")
    expect(code).toContain('msg')
    expect(code).toContain(' + ')
  })

  // 测试:有辅助函数时应生成正确的导言(解构声明)
  it('should generate preamble with helpers', () => {
    const ast = baseParse('<div>{{ msg }}</div>')
    transform(ast, {
      nodeTransforms: [transformExpression, transformElement, transformText],
    })
    const { code } = generate(ast)

    // 断言:导言包含 const { ... } = Vue 的解构语句和 return 关键字
    expect(code).toContain('const {')
    expect(code).toContain('} = Vue')
    expect(code).toContain('return function render')
  })

  // 测试:空模板应生成返回 null 的 render 函数
  it('should generate null for empty ast', () => {
    const ast = baseParse('')
    transform(ast, { nodeTransforms: [] })
    ast.codegenNode = undefined // 手动清除 codegenNode 模拟空模板
    const { code } = generate(ast)

    // 断言:函数体中返回 null
    expect(code).toContain('return null')
  })

  // 测试:文本 + 插值 + 文本的复合表达式应正确拼接
  it('should handle text + interpolation + text', () => {
    const ast = baseParse('<p>hello {{ name }} world</p>')
    transform(ast, {
      nodeTransforms: [transformExpression, transformElement, transformText],
    })
    const { code } = generate(ast)

    // 断言:三段内容都出现在生成的代码中
    expect(code).toContain("'hello '")
    expect(code).toContain('name')
    expect(code).toContain("' world'")
  })

  // 测试:生成的代码是合法的 JavaScript,可以被 new Function 执行
  it('should generate complete render function', () => {
    const ast = baseParse('<div>hi,{{ message }}</div>')
    transform(ast, {
      nodeTransforms: [transformExpression, transformElement, transformText],
    })
    const { code } = generate(ast)

    // 断言:生成的代码不会抛出语法错误
    expect(() => new Function(code)).not.toThrow()
  })
})

设计分析

1. 字符串拼接 vs AST → Code

Vue 3 的 codegen 采用了最直接的方式——字符串拼接。虽然看起来"原始",但有以下好处:

  • 简单可靠:没有中间表示,减少了出错的可能
  • 可读性好:生成的代码格式化良好,便于调试
  • Source Map 支持:每次 push 都可以记录位置映射

2. push 方法的设计

所有代码输出都通过 push 方法,这不仅是封装,更是为 Source Map 做准备。在 Vue 3 完整实现中,push 方法还会记录 AST 节点的位置信息,用于生成 Source Map:

ts
// Vue 3 完整版的 push 方法,除了追加代码还支持 source map
function push(code: string, node?: any) {
  context.code += code
  if (node) {
    // 如果提供了 AST 节点,记录代码位置与模板位置的映射关系
    // 这使得浏览器 DevTools 能直接定位到模板中的位置
    context.map?.addMapping(...)
  }
}

3. genNullable 的尾部裁剪

createElementVNode('div', null, null) 虽然正确,但 null 参数是不必要的。genNullable 从末尾裁剪掉 null 参数,生成更简洁的代码:

ts
// 裁剪前:createElementVNode('div', null, null)
// 裁剪后:createElementVNode('div')
// 但不裁剪中间的 null:createElementVNode('div', null, children)

4. 缩进管理

indent/deindent/newline 三个方法协同工作,自动维护代码缩进:

ts
push('function render() {')  // function render() {
indent()                      //   (缩进+换行)
push('return ')               //   return
push('...')                   //   ...
deindent()                    // (减少缩进+换行)
push('}')                     // }

本节小结

  1. Codegen 的职责 — 将转换后的 AST 生成 render 函数代码字符串
  2. CodegenContext — 上下文对象,管理代码输出、缩进、辅助函数名称
  3. generate — 入口函数,协调导言生成和节点代码生成
  4. genNode — 分发器,根据节点类型调用不同的代码生成函数
  5. genElement — 生成 createElementVNode(tag, props, children) 调用
  6. genCompoundExpression — 处理文本+插值的组合表达式
  7. genFunctionPreamble — 生成导言部分(辅助函数解构)
  8. genNullable — 尾部裁剪 null 参数,生成更简洁的代码

下一节实现编译器与运行时的联调,完成 template → DOM 的全链路。

用心学习,用代码说话 💻