Skip to content

第4.3节:codegen 如何将 AST 转换为 render 函数字符串?

概述

代码生成(codegen)是Vue 3编译器的最后一个阶段,负责将经过转换优化的AST转换为可执行的JavaScript代码字符串。这个阶段的核心任务是生成高效的render函数,同时保持代码的可读性和性能优化。

1. 代码生成器架构

1.1 核心入口函数 - generate

typescript
export function generate(
  ast: RootNode,
  options: CodegenOptions & {
    onContextCreated?: (context: CodegenContext) => void
  } = {},
): CodegenResult {
  const context = createCodegenContext(ast, options)
  if (options.onContextCreated) options.onContextCreated(context)
  
  const {
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr,
  } = context

  const helpers = Array.from(ast.helpers)
  const hasHelpers = helpers.length > 0
  const useWithBlock = !prefixIdentifiers && mode !== 'module'
  const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  const isSetupInlined = !__BROWSER__ && !!options.inline

  // 生成前导码
  const preambleContext = isSetupInlined
    ? createCodegenContext(ast, options)
    : context
  if (!__BROWSER__ && mode === 'module') {
    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  } else {
    genFunctionPreamble(ast, preambleContext)
  }
  
  // 生成render函数
  const functionName = ssr ? `ssrRender` : `render`
  const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  
  // 函数签名生成
  const signature = !__BROWSER__ && options.isTS
    ? args.map(arg => `${arg}: any`).join(',')
    : args.join(', ')

  if (isSetupInlined) {
    push(`(${signature}) => {`)
  } else {
    push(`function ${functionName}(${signature}) {`)
  }
  indent()

  // with块处理
  if (useWithBlock) {
    push(`with (_ctx) {`)
    indent()
    if (hasHelpers) {
      push(
        `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
        NewlineType.End,
      )
      newline()
    }
  }

  // 生成资源解析语句
  if (ast.components.length) {
    genAssets(ast.components, 'component', context)
  }
  if (ast.directives.length) {
    genAssets(ast.directives, 'directive', context)
  }

  // 生成临时变量
  if (ast.temps > 0) {
    push(`let `)
    for (let i = 0; i < ast.temps; i++) {
      push(`${i > 0 ? `, ` : ``}_temp${i}`)
    }
  }

  // 生成VNode树表达式
  if (!ssr) {
    push(`return `)
  }
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`null`)
  }

  if (useWithBlock) {
    deindent()
    push(`}`)
  }

  deindent()
  push(`}`)

  return {
    ast,
    code: context.code,
    preamble: isSetupInlined ? preambleContext.code : ``,
    map: context.map ? context.map.toJSON() : undefined,
  }
}

1.2 代码生成上下文 - CodegenContext

typescript
export interface CodegenContext
  extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
  source: string
  code: string
  line: number
  column: number
  offset: number
  indentLevel: number
  pure: boolean
  map?: CodegenSourceMapGenerator
  helper(key: symbol): string
  push(code: string, newlineIndex?: number, node?: CodegenNode): void
  indent(): void
  deindent(withoutNewLine?: boolean): void
  newline(): void
}

核心功能:

  1. 代码累积:通过push方法逐步构建代码字符串
  2. 缩进管理:维护代码的格式化和可读性
  3. 源码映射:生成Source Map用于调试
  4. 辅助函数管理:处理运行时helper的引用

1.3 createCodegenContext函数

typescript
function createCodegenContext(
  ast: RootNode,
  {
    mode = 'function',
    prefixIdentifiers = mode === 'module',
    sourceMap = false,
    filename = `template.vue.html`,
    scopeId = null,
    optimizeImports = false,
    runtimeGlobalName = `Vue`,
    runtimeModuleName = `vue`,
    ssrRuntimeModuleName = 'vue/server-renderer',
    ssr = false,
    isTS = false,
    inSSR = false,
  }: CodegenOptions,
): CodegenContext {
  const context: CodegenContext = {
    // 配置选项
    mode,
    prefixIdentifiers,
    sourceMap,
    filename,
    scopeId,
    optimizeImports,
    runtimeGlobalName,
    runtimeModuleName,
    ssrRuntimeModuleName,
    ssr,
    isTS,
    inSSR,
    
    // 状态管理
    source: ast.source,
    code: ``,
    column: 1,
    line: 1,
    offset: 0,
    indentLevel: 0,
    pure: false,
    map: undefined,
    
    // 核心方法
    helper(key) {
      return `_${helperNameMap[key]}`
    },
    
    push(code, newlineIndex = NewlineType.None, node) {
      context.code += code
      
      // Source Map处理
      if (!__BROWSER__ && context.map) {
        if (node) {
          let name
          if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
            const content = node.content.replace(/^_ctx\./, '')
            if (content !== node.content && isSimpleIdentifier(content)) {
              name = content
            }
          }
          if (node.loc.source) {
            addMapping(node.loc.start, name)
          }
        }
        
        // 位置更新
        if (newlineIndex === NewlineType.Unknown) {
          advancePositionWithMutation(context, code)
        } else {
          context.offset += code.length
          if (newlineIndex === NewlineType.None) {
            context.column += code.length
          } else {
            if (newlineIndex === NewlineType.End) {
              newlineIndex = code.length - 1
            }
            context.line++
            context.column = code.length - newlineIndex
          }
        }
        
        if (node && node.loc !== locStub && node.loc.source) {
          addMapping(node.loc.end)
        }
      }
    },
    
    indent() {
      newline(++context.indentLevel)
    },
    
    deindent(withoutNewLine = false) {
      if (withoutNewLine) {
        --context.indentLevel
      } else {
        newline(--context.indentLevel)
      }
    },
    
    newline() {
      newline(context.indentLevel)
    },
  }

  function newline(n: number) {
    context.push('\n' + `  `.repeat(n), NewlineType.Start)
  }

  function addMapping(loc: Position, name: string | null = null) {
    const { _names, _mappings } = context.map!
    if (name !== null && !_names.has(name)) _names.add(name)
    _mappings.add({
      originalLine: loc.line,
      originalColumn: loc.column - 1,
      generatedLine: context.line,
      generatedColumn: context.column - 1,
      source: filename,
      name,
    })
  }

  // Source Map初始化
  if (!__BROWSER__ && sourceMap) {
    context.map = new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
    context.map.setSourceContent(filename, context.source)
    context.map._sources.add(filename)
  }

  return context
}

2. 运行时辅助函数管理

2.1 运行时Helper定义

typescript
// 核心VNode创建函数
export const CREATE_VNODE: unique symbol = Symbol(__DEV__ ? `createVNode` : ``)
export const CREATE_ELEMENT_VNODE: unique symbol = Symbol(__DEV__ ? `createElementVNode` : ``)
export const CREATE_COMMENT: unique symbol = Symbol(__DEV__ ? `createCommentVNode` : ``)
export const CREATE_TEXT: unique symbol = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC: unique symbol = Symbol(__DEV__ ? `createStaticVNode` : ``)

// 块级优化函数
export const OPEN_BLOCK: unique symbol = Symbol(__DEV__ ? `openBlock` : ``)
export const CREATE_BLOCK: unique symbol = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_ELEMENT_BLOCK: unique symbol = Symbol(__DEV__ ? `createElementBlock` : ``)

// 组件和指令解析
export const RESOLVE_COMPONENT: unique symbol = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol(__DEV__ ? `resolveDynamicComponent` : ``)
export const RESOLVE_DIRECTIVE: unique symbol = Symbol(__DEV__ ? `resolveDirective` : ``)

// 指令处理
export const WITH_DIRECTIVES: unique symbol = Symbol(__DEV__ ? `withDirectives` : ``)

// 列表渲染
export const RENDER_LIST: unique symbol = Symbol(__DEV__ ? `renderList` : ``)

// 插槽处理
export const RENDER_SLOT: unique symbol = Symbol(__DEV__ ? `renderSlot` : ``)
export const CREATE_SLOTS: unique symbol = Symbol(__DEV__ ? `createSlots` : ``)

// 工具函数
export const TO_DISPLAY_STRING: unique symbol = Symbol(__DEV__ ? `toDisplayString` : ``)
export const MERGE_PROPS: unique symbol = Symbol(__DEV__ ? `mergeProps` : ``)
export const NORMALIZE_CLASS: unique symbol = Symbol(__DEV__ ? `normalizeClass` : ``)
export const NORMALIZE_STYLE: unique symbol = Symbol(__DEV__ ? `normalizeStyle` : ``)

// 事件处理
export const TO_HANDLERS: unique symbol = Symbol(__DEV__ ? `toHandlers` : ``)
export const WITH_CTX: unique symbol = Symbol(__DEV__ ? `withCtx` : ``)

// 响应式相关
export const UNREF: unique symbol = Symbol(__DEV__ ? `unref` : ``)
export const IS_REF: unique symbol = Symbol(__DEV__ ? `isRef` : ``)

// 缓存优化
export const WITH_MEMO: unique symbol = Symbol(__DEV__ ? `withMemo` : ``)
export const IS_MEMO_SAME: unique symbol = Symbol(__DEV__ ? `isMemoSame` : ``)

2.2 Helper名称映射

typescript
export const helperNameMap: Record<symbol, string> = {
  [FRAGMENT]: `Fragment`,
  [TELEPORT]: `Teleport`,
  [SUSPENSE]: `Suspense`,
  [KEEP_ALIVE]: `KeepAlive`,
  [BASE_TRANSITION]: `BaseTransition`,
  [OPEN_BLOCK]: `openBlock`,
  [CREATE_BLOCK]: `createBlock`,
  [CREATE_ELEMENT_BLOCK]: `createElementBlock`,
  [CREATE_VNODE]: `createVNode`,
  [CREATE_ELEMENT_VNODE]: `createElementVNode`,
  [CREATE_COMMENT]: `createCommentVNode`,
  [CREATE_TEXT]: `createTextVNode`,
  [CREATE_STATIC]: `createStaticVNode`,
  [RESOLVE_COMPONENT]: `resolveComponent`,
  [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
  [RESOLVE_DIRECTIVE]: `resolveDirective`,
  [WITH_DIRECTIVES]: `withDirectives`,
  [RENDER_LIST]: `renderList`,
  [RENDER_SLOT]: `renderSlot`,
  [CREATE_SLOTS]: `createSlots`,
  [TO_DISPLAY_STRING]: `toDisplayString`,
  [MERGE_PROPS]: `mergeProps`,
  [NORMALIZE_CLASS]: `normalizeClass`,
  [NORMALIZE_STYLE]: `normalizeStyle`,
  [TO_HANDLERS]: `toHandlers`,
  [WITH_CTX]: `withCtx`,
  [UNREF]: `unref`,
  [IS_REF]: `isRef`,
  [WITH_MEMO]: `withMemo`,
  [IS_MEMO_SAME]: `isMemoSame`,
}

2.3 Helper别名生成

typescript
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`

// 生成结果示例:
// "createVNode: _createVNode, openBlock: _openBlock"

3. 节点代码生成 - genNode

3.1 核心分发函数

typescript
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  if (isString(node)) {
    context.push(node, NewlineType.Unknown)
    return
  }
  if (isSymbol(node)) {
    context.push(context.helper(node))
    return
  }
  
  switch (node.type) {
    case NodeTypes.ELEMENT:
    case NodeTypes.IF:
    case NodeTypes.FOR:
      __DEV__ &&
        assert(
          node.codegenNode != null,
          `Codegen node is missing for element/if/for node. ` +
            `Apply appropriate transforms first.`,
        )
      genNode(node.codegenNode!, context)
      break
      
    case NodeTypes.TEXT:
      genText(node, context)
      break
      
    case NodeTypes.SIMPLE_EXPRESSION:
      genExpression(node, context)
      break
      
    case NodeTypes.INTERPOLATION:
      genInterpolation(node, context)
      break
      
    case NodeTypes.TEXT_CALL:
      genNode(node.codegenNode, context)
      break
      
    case NodeTypes.COMPOUND_EXPRESSION:
      genCompoundExpression(node, context)
      break
      
    case NodeTypes.COMMENT:
      genComment(node, context)
      break
      
    case NodeTypes.VNODE_CALL:
      genVNodeCall(node, context)
      break

    // JavaScript表达式节点
    case NodeTypes.JS_CALL_EXPRESSION:
      genCallExpression(node, context)
      break
      
    case NodeTypes.JS_OBJECT_EXPRESSION:
      genObjectExpression(node, context)
      break
      
    case NodeTypes.JS_ARRAY_EXPRESSION:
      genArrayExpression(node, context)
      break
      
    case NodeTypes.JS_FUNCTION_EXPRESSION:
      genFunctionExpression(node, context)
      break
      
    case NodeTypes.JS_CONDITIONAL_EXPRESSION:
      genConditionalExpression(node, context)
      break
      
    case NodeTypes.JS_CACHE_EXPRESSION:
      genCacheExpression(node, context)
      break
      
    case NodeTypes.JS_BLOCK_STATEMENT:
      genNodeList(node.body, context, true, false)
      break

    // SSR专用节点类型
    case NodeTypes.JS_TEMPLATE_LITERAL:
      !__BROWSER__ && genTemplateLiteral(node, context)
      break
      
    case NodeTypes.JS_IF_STATEMENT:
      !__BROWSER__ && genIfStatement(node, context)
      break
      
    case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
      !__BROWSER__ && genAssignmentExpression(node, context)
      break
      
    case NodeTypes.JS_SEQUENCE_EXPRESSION:
      !__BROWSER__ && genSequenceExpression(node, context)
      break
      
    case NodeTypes.JS_RETURN_STATEMENT:
      !__BROWSER__ && genReturnStatement(node, context)
      break

    case NodeTypes.IF_BRANCH:
      // noop
      break
      
    default:
      if (__DEV__) {
        assert(false, `unhandled codegen node type: ${(node as any).type}`)
      }
  }
}

3.2 VNode调用生成 - genVNodeCall

typescript
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  const { push, helper, pure } = context
  const {
    tag,
    props,
    children,
    patchFlag,
    dynamicProps,
    directives,
    isBlock,
    disableTracking,
    isComponent,
  } = node

  // 添加开发模式下的patch flag注释
  let patchFlagString
  if (patchFlag) {
    if (__DEV__) {
      if (patchFlag < 0) {
        // 特殊标志(负数且互斥)
        patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
      } else {
        // 位运算标志
        const flagNames = Object.keys(PatchFlagNames)
          .map(Number)
          .filter(n => n > 0 && patchFlag & n)
          .map(n => PatchFlagNames[n as PatchFlags])
          .join(`, `)
        patchFlagString = patchFlag + ` /* ${flagNames} */`
      }
    } else {
      patchFlagString = String(patchFlag)
    }
  }

  // 指令包装
  if (directives) {
    push(helper(WITH_DIRECTIVES) + `(`)
  }
  
  // 块级优化
  if (isBlock) {
    push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
  }
  
  // 纯函数标记
  if (pure) {
    push(PURE_ANNOTATION)
  }
  
  // 选择合适的helper函数
  const callHelper: symbol = isBlock
    ? getVNodeBlockHelper(context.inSSR, isComponent)
    : getVNodeHelper(context.inSSR, isComponent)
    
  push(helper(callHelper) + `(`, NewlineType.None, node)
  
  // 生成参数列表
  genNodeList(
    genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
    context,
  )
  
  push(`)`)
  
  if (isBlock) {
    push(`)`)
  }
  
  if (directives) {
    push(`, `)
    genNode(directives, context)
    push(`)`)
  }
}

4. 静态提升优化

4.1 静态节点提升生成

typescript
function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
  if (!hoists.length) {
    return
  }
  
  context.pure = true
  const { push, newline } = context
  newline()

  for (let i = 0; i < hoists.length; i++) {
    const exp = hoists[i]
    if (exp) {
      push(`const _hoisted_${i + 1} = `)
      genNode(exp, context)
      newline()
    }
  }

  context.pure = false
}

生成示例:

javascript
// 原始模板
<div>
  <p>静态文本</p>
  <span>{{ message }}</span>
</div>

// 生成的代码
const _hoisted_1 = /*#__PURE__*/ _createElementVNode("p", null, "静态文本", -1)

function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _createElementVNode("span", null, _toDisplayString(_ctx.message), 1)
  ])
}

4.2 缓存表达式生成

typescript
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
  const { push, helper, indent, deindent, newline } = context
  push(`_cache[${node.index}] || (`)
  if (node.isVNode) {
    indent()
    push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
    newline()
  }
  push(`_cache[${node.index}] = `)
  genNode(node.value, context)
  if (node.isVNode) {
    push(`,`)
    newline()
    push(`${helper(SET_BLOCK_TRACKING)}(1),`)
    newline()
    push(`_cache[${node.index}]`)
    deindent()
  }
  push(`)`)
}

5. 作用域管理

5.1 模块模式前导码生成

typescript
function genModulePreamble(
  ast: RootNode,
  context: CodegenContext,
  genScopeId: boolean,
  inline?: boolean,
) {
  const {
    push,
    newline,
    optimizeImports,
    runtimeModuleName,
    ssrRuntimeModuleName,
  } = context

  if (genScopeId && ast.hoists.length) {
    ast.hoists.forEach((exp, i) => {
      if (exp) {
        push(
          `const _hoisted_${i + 1} = /*#__PURE__*/ _withScopeId(() => `,
        )
        genNode(exp, context)
        push(`)`)
        newline()
      }
    })
  }

  // 生成导入语句
  if (ast.helpers.size) {
    if (optimizeImports) {
      // 优化导入:按需导入
      push(
        `import { ${ast.helpers
          .map(s => helperNameMap[s])
          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
      )
    } else {
      // 全量导入
      push(`import * as _Vue from ${JSON.stringify(runtimeModuleName)}\n`)
    }
  }

  // SSR导入
  if (ast.ssrHelpers && ast.ssrHelpers.length) {
    push(
      `import { ${ast.ssrHelpers
        .map(s => helperNameMap[s])
        .join(', ')} } from ${JSON.stringify(ssrRuntimeModuleName)}\n`,
    )
  }

  // 生成静态提升
  if (ast.hoists.length && !genScopeId) {
    genHoists(ast.hoists, context)
  }

  // 生成导入语句
  if (ast.imports.length) {
    genImports(ast.imports, context)
    newline()
  }

  genTemps(ast, context)
}

5.2 函数模式前导码生成

typescript
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  const {
    ssr,
    prefixIdentifiers,
    push,
    newline,
    runtimeGlobalName,
    runtimeModuleName,
    ssrRuntimeModuleName,
  } = context
  const VueBinding =
    !__BROWSER__ && ssr
      ? `require(${JSON.stringify(runtimeModuleName)})`
      : runtimeGlobalName
  const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
  
  // 生成helper函数声明
  if (ast.helpers.size > 0) {
    if (!__BROWSER__ && prefixIdentifiers) {
      push(
        `const { ${Array.from(ast.helpers)
          .map(aliasHelper)
          .join(', ')} } = ${VueBinding}\n`,
      )
    } else {
      // 在with块内部处理
      push(`const _Vue = ${VueBinding}\n`)
      if (ast.hoists.length) {
        const staticHelpers = [
          CREATE_VNODE,
          CREATE_ELEMENT_VNODE,
          CREATE_COMMENT,
          CREATE_TEXT,
          CREATE_STATIC,
        ]
          .filter(helper => ast.helpers.has(helper))
          .map(aliasHelper)
          .join(', ')
        push(`const { ${staticHelpers} } = _Vue\n`)
      }
    }
  }
  
  // 生成静态提升
  if (ast.hoists.length) {
    genHoists(ast.hoists, context)
  }
  
  // 生成导入
  if (ast.imports.length) {
    genImports(ast.imports, context)
    newline()
  }
  
  genTemps(ast, context)
}

6. 优化输出策略

6.1 代码压缩优化

typescript
// 1. 移除不必要的参数
function genNullableArgs(args: any[]): CallExpression['arguments'] {
  let i = args.length
  while (i--) {
    if (args[i] != null) break
  }
  return args.slice(0, i + 1).map(arg => arg || `null`)
}

// 2. 内联组件props优化
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
  const { push, indent, deindent, newline } = context
  const { properties } = node
  
  if (!properties.length) {
    push(`{}`, NewlineType.None, node)
    return
  }
  
  const multilines =
    properties.length > 1 ||
    properties.some(
      p =>
        p.value.type !== NodeTypes.SIMPLE_EXPRESSION ||
        !p.value.isStatic
    )
    
  push(multilines ? `{` : `{ `)
  multilines && indent()
  
  for (let i = 0; i < properties.length; i++) {
    const { key, value } = properties[i]
    
    // key处理
    genExpressionAsPropertyKey(key, context)
    push(`: `)
    
    // value处理
    genNode(value, context)
    
    if (i < properties.length - 1) {
      push(`,`)
      multilines && newline()
    }
  }
  
  multilines && deindent()
  push(multilines ? `}` : ` }`)
}

6.2 事件处理器优化

typescript
// 事件处理器内联优化
function genFunctionExpression(
  node: FunctionExpression,
  context: CodegenContext,
) {
  const { push, indent, deindent } = context
  const { params, returns, body, newline, isSlot, isNonScopedSlot } = node
  
  if (isSlot) {
    push(`_${helperNameMap[WITH_CTX]}(`)
  }
  
  push(`(`, NewlineType.None, node)
  
  if (isArray(params)) {
    genNodeList(params, context)
  } else if (params) {
    genNode(params, context)
  }
  
  push(`) => `)
  
  if (newline || body) {
    push(`{`)
    indent()
  }
  
  if (returns) {
    if (newline) {
      push(`return `)
    }
    if (isArray(returns)) {
      genNodeListAsArray(returns, context)
    } else {
      genNode(returns, context)
    }
  } else if (body) {
    genNode(body, context)
  }
  
  if (newline || body) {
    deindent()
    push(`}`)
  }
  
  if (isSlot && !isNonScopedSlot) {
    push(`)`)
  }
}

7. 实际应用示例

7.1 简单模板转换

输入模板:

vue
<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
  </div>
</template>

生成的render函数:

javascript
function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("div", {
    class: "container"
  }, [
    _createElementVNode("h1", null, _toDisplayString(_ctx.title), 1 /* TEXT */),
    _createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ])
}

7.2 条件渲染转换

输入模板:

vue
<template>
  <div>
    <p v-if="show">显示内容</p>
    <p v-else>隐藏内容</p>
  </div>
</template>

生成的render函数:

javascript
function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("div", null, [
    _ctx.show
      ? (_openBlock(), _createElementBlock("p", { key: 0 }, "显示内容"))
      : (_openBlock(), _createElementBlock("p", { key: 1 }, "隐藏内容"))
  ])
}

7.3 列表渲染转换

输入模板:

vue
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

生成的render函数:

javascript
function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("ul", null, [
    (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.items, (item) => {
      return (_openBlock(), _createElementBlock("li", {
        key: item.id
      }, _toDisplayString(item.name), 1 /* TEXT */))
    }), 128 /* KEYED_FRAGMENT */))
  ])
}

7.4 静态提升优化示例

输入模板:

vue
<template>
  <div>
    <header>
      <h1>静态标题</h1>
      <nav>
        <a href="/home">首页</a>
        <a href="/about">关于</a>
      </nav>
    </header>
    <main>
      <p>{{ content }}</p>
    </main>
  </div>
</template>

生成的代码:

javascript
const _hoisted_1 = /*#__PURE__*/ _createElementVNode("h1", null, "静态标题", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/ _createElementVNode("a", { href: "/home" }, "首页", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/ _createElementVNode("a", { href: "/about" }, "关于", -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/ _createElementVNode("nav", null, [
  _hoisted_2,
  _hoisted_3
], -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/ _createElementVNode("header", null, [
  _hoisted_1,
  _hoisted_4
], -1 /* HOISTED */)

function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("div", null, [
    _hoisted_5,
    _createElementVNode("main", null, [
      _createElementVNode("p", null, _toDisplayString(_ctx.content), 1 /* TEXT */)
    ])
  ])
}

8. 性能优化特性

8.1 Patch Flag系统

typescript
// Patch Flag枚举
export const enum PatchFlags {
  TEXT = 1,                    // 动态文本内容
  CLASS = 1 << 1,             // 动态class
  STYLE = 1 << 2,             // 动态style
  PROPS = 1 << 3,             // 动态属性
  FULL_PROPS = 1 << 4,        // 具有动态key的属性
  HYDRATE_EVENTS = 1 << 5,    // 事件监听器
  STABLE_FRAGMENT = 1 << 6,   // 稳定的fragment
  KEYED_FRAGMENT = 1 << 7,    // 带key的fragment
  UNKEYED_FRAGMENT = 1 << 8,  // 不带key的fragment
  NEED_PATCH = 1 << 9,        // 需要patch
  DYNAMIC_SLOTS = 1 << 10,    // 动态插槽
  DEV_ROOT_FRAGMENT = 1 << 11, // 开发模式根fragment
  HOISTED = -1,               // 静态提升
  BAIL = -2,                  // 差异算法回退
}

// Patch Flag名称映射(开发模式)
export const PatchFlagNames: Record<PatchFlags, string> = {
  [PatchFlags.TEXT]: `TEXT`,
  [PatchFlags.CLASS]: `CLASS`,
  [PatchFlags.STYLE]: `STYLE`,
  [PatchFlags.PROPS]: `PROPS`,
  [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
  [PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
  [PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
  [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
  [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
  [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
  [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
  [PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
  [PatchFlags.HOISTED]: `HOISTED`,
  [PatchFlags.BAIL]: `BAIL`,
}

8.2 块级优化

typescript
// 块级优化helper选择
function getVNodeHelper(ssr: boolean, isComponent: boolean) {
  return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE
}

function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) {
  return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
}

8.3 缓存优化

typescript
// 内联组件props缓存
function genCachedExpression(
  node: CacheExpression,
  context: CodegenContext
) {
  const { push, helper } = context
  
  push(`_cache[${node.index}] || (`)
  
  if (node.isVNode) {
    push(`${helper(SET_BLOCK_TRACKING)}(-1), `)
  }
  
  push(`_cache[${node.index}] = `)
  genNode(node.value, context)
  
  if (node.isVNode) {
    push(`, ${helper(SET_BLOCK_TRACKING)}(1), _cache[${node.index}]`)
  }
  
  push(`)`)
}

9. 调试与开发支持

9.1 Source Map生成

typescript
interface CodegenSourceMapGenerator {
  setSourceContent(sourceFile: string, sourceContent: string): void
  toJSON(): RawSourceMap
  _sources: Set<string>
  _names: Set<string>
  _mappings: {
    add(mapping: MappingItem): void
  }
}

interface MappingItem {
  source: string
  generatedLine: number
  generatedColumn: number
  originalLine: number
  originalColumn: number
  name: string | null
}

9.2 开发模式增强

typescript
// 开发模式下的patch flag注释
if (__DEV__) {
  if (patchFlag < 0) {
    patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
  } else {
    const flagNames = Object.keys(PatchFlagNames)
      .map(Number)
      .filter(n => n > 0 && patchFlag & n)
      .map(n => PatchFlagNames[n as PatchFlags])
      .join(`, `)
    patchFlagString = patchFlag + ` /* ${flagNames} */`
  }
}

10. 最佳实践与注意事项

10.1 代码生成最佳实践

  1. 合理使用静态提升

    • 识别真正的静态内容
    • 避免过度提升导致内存占用
    • 考虑组件复用场景
  2. 优化patch flag使用

    • 精确标记动态内容类型
    • 避免不必要的FULL_PROPS标记
    • 合理使用BAIL标记
  3. 缓存策略优化

    • 识别适合缓存的表达式
    • 避免缓存简单表达式
    • 考虑缓存的内存开销

10.2 性能优化建议

  1. 减少运行时开销

    typescript
    // 好的做法:使用具体的helper
    _createElementVNode("div", null, "text")
    
    // 避免:使用通用helper
    _createVNode("div", null, "text")
  2. 优化事件处理

    typescript
    // 好的做法:内联事件处理器
    onClick: $event => (_ctx.count++)
    
    // 避免:复杂的事件处理器
    onClick: _ctx.handleClick
  3. 合理使用Fragment

    typescript
    // 好的做法:稳定的Fragment
    _createElementBlock(_Fragment, null, [
      // 稳定的子节点
    ], 64 /* STABLE_FRAGMENT */)
    
    // 注意:动态Fragment需要key
    _createElementBlock(_Fragment, null, _renderList(items, item => {
      // 动态内容
    }), 128 /* KEYED_FRAGMENT */)

10.3 常见问题与解决方案

  1. Source Map不准确

    • 确保正确设置filename
    • 检查位置映射逻辑
    • 验证原始源码内容
  2. 生成代码过大

    • 检查静态提升策略
    • 优化helper函数使用
    • 考虑代码分割
  3. 运行时错误

    • 验证helper函数导入
    • 检查作用域变量引用
    • 确保正确的上下文传递

总结

Vue 3的代码生成阶段是一个高度优化的系统,它将AST转换为高效的JavaScript代码。通过静态提升、patch flag系统、块级优化等技术,生成的代码在运行时具有出色的性能表现。

核心特性:

  1. 高效的代码生成:通过精确的节点类型分发和优化的helper函数使用
  2. 静态优化:静态提升和缓存机制减少运行时开销
  3. 调试支持:完整的Source Map支持和开发模式增强
  4. 灵活的配置:支持多种输出模式和优化策略
  5. 性能优化:patch flag系统和块级优化提升更新性能

理解代码生成的原理和优化策略,有助于开发者编写更高效的Vue组件,并在需要时进行性能调优和问题排查。


微信公众号二维码