💚 Vue 3 编译器优化策略

源码级深度解析

基于 vuejs/core 编译器核心
2026-03-16 | 技术深度解读

📑 目录

第一部分:基础架构

  • 编译器简介
  • 核心架构
  • 编译流程
  • Vue 2 vs Vue 3

第二部分:优化策略

  • 静态提升 (HoistStatic)
  • Patch Flags
  • Block 树
  • 缓存机制

第三部分:核心类解析

  • TransformContext
  • RootNode / ElementNode
  • VNodeCall
  • ConstantTypes

第四部分:核心函数

  • baseCompile
  • cacheStatic
  • transformElement
  • generate

💚 Vue 3 编译器简介

Vue 3 编译器 是将模板转换为渲染函数的核心引擎,实现了多项编译时优化。

核心职责

  • 模板解析为 AST
  • AST 语义转换
  • 渲染函数代码生成
  • 编译时优化标记

关键文件

  • compile.ts - 编译入口
  • transform.ts - AST 转换
  • codegen.ts - 代码生成
  • cacheStatic.ts - 静态优化

🏛️ 核心架构

compiler-core/
├── src/
│   ├── compile.ts       // 主编译入口
│   ├── parse.ts         // 模板解析器
│   ├── ast.ts           // AST 节点定义
│   ├── transform.ts     // AST 转换核心
│   ├── codegen.ts       // 代码生成器
│   └── transforms/      // 转换插件目录
│       ├── vIf.ts       // v-if 转换
│       ├── vFor.ts      // v-for 转换
│       ├── vOn.ts       // v-on 转换
│       ├── vBind.ts     // v-bind 转换
│       ├── vModel.ts    // v-model 转换
│       ├── cacheStatic.ts  // 静态提升
│       └── transformElement.ts // 元素转换

🔄 编译流程

Template String
      ↓
  [Parser] → 模板解析
      ↓
   AST (抽象语法树)
      ↓
 [Transform] → AST 转换 + 优化标记
      ↓
Transformed AST (带优化信息)
      ↓
 [Codegen] → 代码生成
      ↓
Render Function Code

三阶段流水线:Parse → Transform → Generate

⚔️ Vue 2 vs Vue 3 编译器

特性 Vue 2 Vue 3
编译产物 完整的渲染函数 优化的渲染函数
静态处理 每次重新创建 静态提升
更新标记 全量 diff Patch Flags
动态内容 递归比对 Block 树
事件处理 每次创建新函数 缓存优化

⚡ 编译时优化策略

1. 静态提升

  • 静态节点提升到 render 外
  • 避免每次渲染重新创建
  • 减少内存分配

2. Patch Flags

  • 标记动态内容类型
  • 运行时按需更新
  • 跳过静态内容

3. Block 树

  • 动态内容的边界
  • 扁平化 diff 范围
  • 快速定位变更

4. 缓存机制

  • 事件处理函数缓存
  • v-once 静态缓存
  • v-memo 条件缓存

🔧 baseCompile 函数

// compile.ts - 编译入口
export function baseCompile(
  source: string | RootNode,
  options: CompilerOptions = {},
): CodegenResult {
  // 1. 解析配置
  const prefixIdentifiers =
    !__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)

  // 2. 解析模板生成 AST
  const ast = isString(source) 
    ? baseParse(source, resolvedOptions) 
    : source

  // 3. 获取转换插件
  const [nodeTransforms, directiveTransforms] =
    getBaseTransformPreset(prefixIdentifiers)

  // 4. 执行 AST 转换
  transform(ast, extend({}, resolvedOptions, {
    nodeTransforms: [...nodeTransforms, ...options.nodeTransforms],
    directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms),
  }))

  // 5. 生成代码
  return generate(ast, resolvedOptions)
}

🔌 getBaseTransformPreset

// 获取基础转换插件集
export function getBaseTransformPreset(
  prefixIdentifiers?: boolean,
): TransformPreset {
  return [
    [ // NodeTransforms - 节点转换插件
      transformVBindShorthand,  // v-bind 简写
      transformOnce,            // v-once
      transformIf,              // v-if
      transformMemo,            // v-memo
      transformFor,             // v-for
      trackVForSlotScopes,      // v-for 作用域追踪
      transformExpression,      // 表达式转换
      transformSlotOutlet,      // slot 插槽
      transformElement,         // 元素转换 ⭐核心
      trackSlotScopes,          // 插槽作用域
      transformText,            // 文本合并
    ],
    { // DirectiveTransforms - 指令转换
      on: transformOn,      // v-on
      bind: transformBind,  // v-bind
      model: transformModel // v-model
    },
  ]
}

📦 TransformContext 接口

export interface TransformContext {
  // 编译选项
  root: RootNode
  prefixIdentifiers: boolean
  hoistStatic: boolean
  cacheHandlers: boolean
  
  // 状态管理
  helpers: Map<symbol, number>  // 运行时助手
  components: Set<string>       // 组件引用
  directives: Set<string>       // 指令引用
  hoists: JSChildNode[]         // 提升的节点
  cached: CacheExpression[]     // 缓存的表达式
  
  // 作用域追踪
  scopes: {
    vFor: number
    vSlot: number
    vPre: number
    vOnce: number
  }
  
  // 当前处理位置
  parent: ParentNode | null
  currentNode: RootNode | TemplateChildNode | null
  
  // 核心方法
  helper(name: symbol): symbol
  hoist(exp: JSChildNode): SimpleExpressionNode
  cache(exp: JSChildNode): CacheExpression
}

🛠️ TransformContext 核心方法

const context: TransformContext = {
  // 注册运行时助手
  helper(name) {
    const count = context.helpers.get(name) || 0
    context.helpers.set(name, count + 1)
    return name
  },
  
  // 静态提升:将表达式提升到 render 函数外
  hoist(exp) {
    context.hoists.push(exp)
    return createSimpleExpression(
      `_hoisted_${context.hoists.length}`,
      false, exp.loc, ConstantTypes.CAN_CACHE
    )
  },
  
  // 缓存表达式(用于 v-once 等)
  cache(exp, isVNode = false) {
    const cacheExp = createCacheExpression(
      context.cached.length, exp, isVNode
    )
    context.cached.push(cacheExp)
    return cacheExp
  },
  
  // 替换当前节点
  replaceNode(node) {
    context.parent.children[context.childIndex] = 
      context.currentNode = node
  }
}

🌲 traverseNode 函数

// 深度优先遍历 AST
export function traverseNode(
  node: RootNode | TemplateChildNode,
  context: TransformContext,
): void {
  context.currentNode = node
  
  // 1. 应用所有节点转换插件
  const { nodeTransforms } = context
  const exitFns = []
  
  for (let i = 0; i < nodeTransforms.length; i++) {
    const onExit = nodeTransforms[i](node, context)
    if (onExit) {
      exitFns.push(onExit)  // 收集退出回调
    }
    if (!context.currentNode) return  // 节点被移除
    node = context.currentNode  // 节点可能被替换
  }

  // 2. 递归遍历子节点
  switch (node.type) {
    case NodeTypes.IF:
      node.branches.forEach(b => traverseNode(b, context))
      break
    case NodeTypes.FOR:
    case NodeTypes.ELEMENT:
    case NodeTypes.ROOT:
      traverseChildren(node, context)
      break
  }

  // 3. 执行退出回调(后序处理)
  exitFns.forEach(fn => fn())
}

👶 traverseChildren 函数

// 遍历子节点
export function traverseChildren(
  parent: ParentNode,
  context: TransformContext,
): void {
  let i = 0
  
  // 节点移除时的回调
  const nodeRemoved = () => { i-- }
  
  for (; i < parent.children.length; i++) {
    const child = parent.children[i]
    if (isString(child)) continue  // 跳过纯文本
    
    // 更新上下文位置
    context.grandParent = context.parent
    context.parent = parent
    context.childIndex = i
    context.onNodeRemoved = nodeRemoved
    
    // 递归处理子节点
    traverseNode(child, context)
  }
}

关键点:转换器可以在遍历时修改节点,context 自动追踪当前位置

🌳 RootNode 接口

export interface RootNode extends Node {
  type: NodeTypes.ROOT
  source: string                    // 原始模板
  children: TemplateChildNode[]     // 子节点
  
  // 编译时收集的信息
  helpers: Set<symbol>              // 需要的运行时助手
  components: string[]              // 使用的组件
  directives: string[]              // 使用的指令
  
  // 优化相关
  hoists: JSChildNode[]             // 提升的静态节点
  cached: CacheExpression[]         // 缓存的表达式
  
  // 代码生成
  codegenNode?: TemplateChildNode   // 根节点代码生成入口
  transformed?: boolean             // 是否已转换
  
  temps: number                     // 临时变量数量
}

helpers 包含如 CREATE_VNODE, TO_DISPLAY_STRING 等运行时函数引用

🏷️ ElementNode 体系

// 元素类型枚举
export enum ElementTypes {
  ELEMENT,    // 普通元素 <div>
  COMPONENT,  // 组件 <MyComp>
  SLOT,       // 插槽 <slot>
  TEMPLATE,   // 模板 <template>
}

// 基础元素节点
export interface BaseElementNode extends Node {
  type: NodeTypes.ELEMENT
  tag: string
  tagType: ElementTypes
  props: Array<AttributeNode | DirectiveNode>
  children: TemplateChildNode[]
  codegenNode?: VNodeCall  // 生成的 VNode 调用
}

// 普通元素节点
export interface PlainElementNode extends BaseElementNode {
  tagType: ElementTypes.ELEMENT
  codegenNode: VNodeCall | SimpleExpressionNode
}

📞 VNodeCall 接口

// VNode 调用表达式(代码生成核心)
export interface VNodeCall extends Node {
  type: NodeTypes.VNODE_CALL
  
  tag: string | symbol | CallExpression  // 标签名
  props: PropsExpression | undefined     // 属性对象
  children: ChildrenType | undefined     // 子节点
  
  // 优化标记 ⭐
  patchFlag: PatchFlags | undefined      // 更新标记
  dynamicProps: string | undefined       // 动态属性名
  
  directives: DirectiveArguments | undefined
  
  // Block 相关
  isBlock: boolean          // 是否为 Block
  disableTracking: boolean  // 是否禁用追踪
  isComponent: boolean      // 是否为组件
}

VNodeCall 是转换阶段生成的中间表示,最终会转换为 createVNode() 调用

📊 ConstantTypes 枚举

// 静态常量类型(用于优化判定)
export enum ConstantTypes {
  NOT_CONSTANT = 0,     // 非常量(有动态内容)
  CAN_SKIP_PATCH,       // 可以跳过 patch
  CAN_CACHE,            // 可以缓存
  CAN_STRINGIFY,        // 可以字符串化(最静态)
}

// 判定逻辑:
// CAN_STRINGIFY > CAN_CACHE > CAN_SKIP_PATCH > NOT_CONSTANT
类型 含义 优化策略
CAN_STRINGIFY 纯静态内容 提升 + 字符串化
CAN_CACHE 可缓存内容 提升或缓存
CAN_SKIP_PATCH 无需 patch 跳过 diff
NOT_CONSTANT 动态内容 正常处理

📝 NodeTypes 枚举

export enum NodeTypes {
  // 模板节点
  ROOT,              // 根节点
  ELEMENT,           // 元素
  TEXT,              // 文本
  COMMENT,           // 注释
  INTERPOLATION,     // 插值 {{ msg }}
  ATTRIBUTE,         // 属性
  DIRECTIVE,         // 指令
  
  // 表达式节点
  SIMPLE_EXPRESSION,    // 简单表达式
  COMPOUND_EXPRESSION,  // 复合表达式
  
  // 结构节点
  IF,              // v-if
  IF_BRANCH,       // v-if 分支
  FOR,             // v-for
  TEXT_CALL,       // 文本调用
  
  // 代码生成节点
  VNODE_CALL,            // VNode 调用
  JS_CALL_EXPRESSION,    // JS 函数调用
  JS_OBJECT_EXPRESSION,  // JS 对象
  JS_ARRAY_EXPRESSION,   // JS 数组
  JS_FUNCTION_EXPRESSION,// JS 函数
  JS_CONDITIONAL_EXPRESSION, // 三元表达式
  JS_CACHE_EXPRESSION,   // 缓存表达式
}

🚀 cacheStatic 函数

// 静态提升和缓存的核心函数
export function cacheStatic(
  root: RootNode, 
  context: TransformContext
): void {
  walk(root, undefined, context, !!getSingleElementRoot(root))
}

function walk(
  node: ParentNode,
  parent: ParentNode | undefined,
  context: TransformContext,
  doNotHoistNode: boolean = false,
) {
  const { children } = node
  const toCache: (PlainElementNode | TextCallNode)[] = []
  
  for (let i = 0; i < children.length; i++) {
    const child = children[i]
    
    // 只处理普通元素和文本调用
    if (child.type === NodeTypes.ELEMENT && 
        child.tagType === ElementTypes.ELEMENT) {
      
      // 判定静态类型
      const constantType = doNotHoistNode
        ? ConstantTypes.NOT_CONSTANT
        : getConstantType(child, context)
      
      if (constantType >= ConstantTypes.CAN_CACHE) {
        // 标记为缓存节点
        child.codegenNode.patchFlag = PatchFlags.CACHED
        toCache.push(child)
        continue
      }
    }
    // ... 递归处理子节点
  }
}

🔍 getConstantType 函数

export function getConstantType(
  node: TemplateChildNode,
  context: TransformContext,
): ConstantTypes {
  const { constantCache } = context
  
  switch (node.type) {
    case NodeTypes.ELEMENT:
      // 检查缓存的判定结果
      const cached = constantCache.get(node)
      if (cached !== undefined) return cached
      
      // 1. Block 节点通常不是常量
      if (codegenNode.isBlock && 
          node.tag !== 'svg' && 
          node.tag !== 'foreignObject') {
        return ConstantTypes.NOT_CONSTANT
      }
      
      // 2. 检查属性
      const propsType = getGeneratedPropsConstantType(node, context)
      if (propsType === ConstantTypes.NOT_CONSTANT) 
        return ConstantTypes.NOT_CONSTANT
      
      // 3. 递归检查子节点
      for (const child of node.children) {
        const childType = getConstantType(child, context)
        if (childType === ConstantTypes.NOT_CONSTANT)
          return ConstantTypes.NOT_CONSTANT
      }
      
      return ConstantTypes.CAN_STRINGIFY
      
    case NodeTypes.TEXT:
    case NodeTypes.COMMENT:
      return ConstantTypes.CAN_STRINGIFY  // 纯文本总是静态
      
    case NodeTypes.INTERPOLATION:
      return getConstantType(node.content, context)
  }
}

🎯 静态提升判定规则

✅ 可以提升

  • 纯静态元素 <div>static</div>
  • 只有静态属性的元素
  • 静态子节点树
  • 纯文本节点

❌ 不能提升

  • 包含插值 {{ msg }}
  • 有动态绑定 :class="cls"
  • 有指令 v-if, v-for
  • 根节点(可能有属性透传)

提升效果:静态节点只创建一次,多次渲染复用同一引用

🔄 transformElement 转换器

export const transformElement: NodeTransform = (node, context) => {
  // 在退出时执行(子节点已处理)
  return function postTransformElement() {
    if (node.type !== NodeTypes.ELEMENT) return
    
    const { tag, props } = node
    const isComponent = node.tagType === ElementTypes.COMPONENT
    
    // 1. 解析组件类型
    let vnodeTag = isComponent
      ? resolveComponentType(node, context)
      : `"${tag}"`
    
    // 2. 处理属性 → 生成 props 对象
    const propsResult = buildProps(node, context)
    const { props: vnodeProps, patchFlag, dynamicPropNames } = propsResult
    
    // 3. 处理子节点
    const vnodeChildren = isComponent 
      ? buildSlots(node, context)
      : processChildren(node)
    
    // 4. 创建 VNodeCall 节点
    node.codegenNode = createVNodeCall(
      context, vnodeTag, vnodeProps, vnodeChildren,
      patchFlag,           // 优化标记
      dynamicPropNames,    // 动态属性
      undefined,           // directives
      shouldUseBlock,      // 是否为 Block
      false,               // disableTracking
      isComponent
    )
  }
}

🏗️ buildProps 函数

export function buildProps(
  node: ElementNode,
  context: TransformContext,
): {
  props: PropsExpression | undefined
  patchFlag: number
  dynamicPropNames: string[]
} {
  let patchFlag = 0
  const dynamicPropNames: string[] = []
  
  for (const prop of props) {
    if (prop.type === NodeTypes.ATTRIBUTE) {
      // 静态属性
      properties.push(createObjectProperty(prop.name, prop.value))
    } else {
      // 指令处理
      const directiveTransform = context.directiveTransforms[prop.name]
      if (directiveTransform) {
        const { props: dirProps, needRuntime } = directiveTransform(prop, node)
        properties.push(...dirProps)
        
        // PatchFlag 分析
        dirProps.forEach(analyzePatchFlag)
      }
    }
  }
  
  // 收集 PatchFlags
  if (hasClassBinding) patchFlag |= PatchFlags.CLASS
  if (hasStyleBinding) patchFlag |= PatchFlags.STYLE
  if (dynamicPropNames.length) patchFlag |= PatchFlags.PROPS
  
  return { props: propsExpression, patchFlag, dynamicPropNames }
}

🏁 PatchFlag 分析逻辑

const analyzePatchFlag = ({ key, value }: Property) => {
  if (isStaticExp(key)) {
    const name = key.content
    
    // 跳过缓存的事件处理器
    if (value.type === NodeTypes.JS_CACHE_EXPRESSION) return
    
    // 跳过常量值
    if (getConstantType(value, context) > 0) return
    
    // 标记不同的动态类型
    if (name === 'ref') {
      hasRef = true
    } else if (name === 'class') {
      hasClassBinding = true
    } else if (name === 'style') {
      hasStyleBinding = true
    } else if (name !== 'key') {
      dynamicPropNames.push(name)
    }
  } else {
    hasDynamicKeys = true  // 动态 key 需要 FULL_PROPS
  }
}

// 最终标记
if (hasDynamicKeys) patchFlag |= PatchFlags.FULL_PROPS
else {
  if (hasClassBinding) patchFlag |= PatchFlags.CLASS
  if (hasStyleBinding) patchFlag |= PatchFlags.STYLE
  if (dynamicPropNames.length) patchFlag |= PatchFlags.PROPS
}

📝 generate 函数

export function generate(
  ast: RootNode,
  options: CodegenOptions = {},
): CodegenResult {
  const context = createCodegenContext(ast, options)
  const { push, indent, deindent, newline } = context
  
  // 1. 生成前置代码(import 语句)
  if (mode === 'module') {
    genModulePreamble(ast, context)
  } else {
    genFunctionPreamble(ast, context)
  }
  
  // 2. 生成提升的静态节点
  genHoists(ast.hoists, context)
  
  // 3. 生成 render 函数
  push(`function render(_ctx, _cache) {`)
  indent()
  
  // 4. 生成返回的 VNode 树
  push(`return `)
  genNode(ast.codegenNode, context)
  
  deindent()
  push(`}`)
  
  return { code: context.code, ast }
}

📞 genVNodeCall 函数

function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  const { tag, props, children, patchFlag, dynamicProps, isBlock } = node
  
  // 生成 patchFlag 注释(开发模式)
  let patchFlagString = patchFlag
  if (__DEV__ && patchFlag) {
    const flagNames = Object.keys(PatchFlagNames)
      .filter(n => patchFlag & n)
      .map(n => PatchFlagNames[n])
    patchFlagString = `${patchFlag} /* ${flagNames.join(', ')} */`
  }
  
  // Block 包装
  if (isBlock) {
    push(`(${helper(OPEN_BLOCK)}(), `)
  }
  
  // VNode 创建调用
  const helper = isBlock 
    ? getVNodeBlockHelper(inSSR, isComponent)
    : getVNodeHelper(inSSR, isComponent)
    
  push(`${helper(helper)}(`)
  genNodeList([tag, props, children, patchFlagString, dynamicProps])
  push(`)`)
  
  if (isBlock) push(`)`)
}

🎈 genHoists 函数

function genHoists(
  hoists: JSChildNode[], 
  context: CodegenContext
) {
  if (!hoists.length) return
  
  context.pure = true  // 添加 PURE 注释
  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
}

// 生成的代码示例:
// const _hoisted_1 = /*#__PURE__*/ createElementVNode("div", null, "Static", -1)
// const _hoisted_2 = /*#__PURE__*/ createElementVNode("span", null, "Text", -1)

💾 genCacheExpression 函数

function genCacheExpression(
  node: CacheExpression, 
  context: CodegenContext
) {
  const { push, helper, indent, deindent, newline } = context
  const { needPauseTracking, needArraySpread } = node
  
  // 数组展开(避免内存泄漏)
  if (needArraySpread) {
    push(`[...(`)
  }
  
  // 缓存读取或初始化
  push(`_cache[${node.index}] || (`)
  
  if (needPauseTracking) {
    indent()
    push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
    newline()
    push(`(`)
  }
  
  // 缓存赋值
  push(`_cache[${node.index}] = `)
  genNode(node.value, context)
  
  if (needPauseTracking) {
    push(`).cacheIndex = ${node.index},`)
    newline()
    push(`${helper(SET_BLOCK_TRACKING)}(1),`)
    newline()
    push(`_cache[${node.index}]`)
    deindent()
  }
  
  push(`)`)
  if (needArraySpread) push(`)]`)
}

🚀 静态提升原理

模板

<div>
  <span class="static">
    Static Text
  </span>
  <p>{{ dynamic }}</p>
</div>

编译产物

// 静态节点提升到函数外
const _hoisted_1 = /*#__PURE__*/
  createElementVNode("span", 
    { class: "static" }, 
    "Static Text"
  )

function render(_ctx, _cache) {
  return createElementVNode("div", null, [
    _hoisted_1,  // 复用
    createElementVNode("p", null, 
      toDisplayString(_ctx.dynamic)
    )
  ])
}

🚩 Patch Flags 标记

// PatchFlags 定义(位运算标记)
export const enum PatchFlags {
  TEXT = 1,           // 动态文本内容
  CLASS = 2,          // 动态 class
  STYLE = 4,          // 动态 style
  PROPS = 8,          // 动态属性(非 class/style)
  FULL_PROPS = 16,    // 有动态 key 的属性
  HYDRATE_EVENTS = 32,// 事件监听器
  STABLE_FRAGMENT = 64,  // 稳定的 fragment
  KEYED_FRAGMENT = 128,  // 有 key 的 fragment
  UNKEYED_FRAGMENT = 256,// 无 key 的 fragment
  NEED_PATCH = 512,   // 需要 patch(ref 等)
  DYNAMIC_SLOTS = 1024, // 动态插槽
  DEV_ROOT_FRAGMENT = 2048, // 开发模式
  CACHED = -1,        // 缓存标记(v-once)
  Bail = -2           // 退出优化
}

优势:运行时通过位运算快速判断更新类型,跳过不必要的 diff

🧱 Block 树结构

什么是 Block?

  • 带有动态内容的 VNode
  • 收集所有动态子节点
  • 扁平化 diff 范围
// Block 节点
const block = {
  type: Fragment,
  dynamicChildren: [
    vnode1,  // 动态子节点
    vnode2
  ],
  patchFlag: PatchFlags.PROPS
}

Block 边界

  • v-if/v-else-if/v-else
  • v-for
  • 动态组件
  • 带有动态 key 的元素

每个 Block 维护 dynamicChildren 数组,直接跳过静态内容

⚡ 缓存机制

// 事件处理器缓存
<button @click="handleClick">

// 编译为:
return createElementVNode("button", {
  onClick: _cache[0] || (
    _cache[0] = (...args) => (
      _ctx.handleClick && _ctx.handleClick(...args)
    )
  )
})

// v-once 缓存
<div v-once>{{ msg }}</div>

// 编译为:
_cache[1] || (
  setBlockTracking(-1),
  _cache[1] = createElementVNode("div", null, 
    toDisplayString(_ctx.msg), 1 /* TEXT */
  ),
  setBlockTracking(1),
  _cache[1]
)

🔒 v-once 优化

// transformOnce.ts
export const transformOnce: NodeTransform = (node, context) => {
  if (findDir(node, 'once')) {
    context.inVOnce = true
    return () => {
      context.inVOnce = false
      
      // 为节点生成缓存表达式
      const codegenNode = node.codegenNode
      if (codegenNode) {
        node.codegenNode = context.cache(
          codegenNode,
          true,  // isVNode
          true   // inVOnce
        )
      }
    }
  }
}

// 缓存效果:
// - 只渲染一次
// - 后续渲染直接复用
// - 跳过整个子树的 diff

📝 v-memo 优化

// v-memo: 条件缓存
<div v-memo="[value]">
  {{ expensive() }}
</div>

// 编译为:
withMemo(
  [value],  // 依赖数组
  () => createElementVNode("div", null, 
    toDisplayString(expensive())
  ),
  _cache, 0
)

// transformMemo.ts
export const transformMemo: NodeTransform = (node, context) => {
  if (findDir(node, 'memo')) {
    return () => {
      const memoExp = dir.exp
      node.codegenNode = createCallExpression(
        WITH_MEMO,
        [
          memoExp,           // 依赖数组
          memoFactory,       // 工厂函数
          `_cache`,
          String(context.cached++)
        ]
      )
    }
  }
}

⏱️ 编译时 vs 运行时

优化 编译时 运行时
静态提升 识别静态节点 直接复用
Patch Flags 标记动态类型 按标记更新
Block 树 确定 Block 边界 收集动态子节点
事件缓存 生成缓存代码 复用处理器
v-once 生成缓存表达式 跳过更新

核心理念:将尽可能多的工作在编译时完成,减少运行时开销

🎭 访问者模式

// NodeTransform 就是一个访问者
export type NodeTransform = (
  node: RootNode | TemplateChildNode,
  context: TransformContext,
) => void | (() => void) | (() => void)[]

// 使用示例
const myTransform: NodeTransform = (node, context) => {
  // 进入节点时的处理
  if (node.type === NodeTypes.ELEMENT) {
    console.log('Entering:', node.tag)
  }
  
  // 返回退出回调
  return () => {
    // 退出节点时的处理(子节点已处理)
    console.log('Exiting:', node.tag)
  }
}

// 注册转换器
context.nodeTransforms.push(myTransform)

优点:解耦遍历逻辑和节点处理,支持插件化扩展

🔌 转换插件架构

// 两种类型的转换器

// 1. NodeTransform - 节点转换
export type NodeTransform = (
  node: RootNode | TemplateChildNode,
  context: TransformContext,
) => void | (() => void)

// 2. DirectiveTransform - 指令转换
export type DirectiveTransform = (
  dir: DirectiveNode,
  node: ElementNode,
  context: TransformContext,
) => {
  props: Property[]      // 生成的属性
  needRuntime?: boolean  // 是否需要运行时指令
}

// 插件组合示例
const preset = getBaseTransformPreset()
preset[0].push(customNodeTransform)        // 添加节点转换
preset[1].custom = customDirectiveTransform // 添加指令转换

transform(ast, {
  nodeTransforms: preset[0],
  directiveTransforms: preset[1]
})

📊 编译流程 UML

┌─────────────┐
│   Template  │
│   String    │
└──────┬──────┘
       │ baseParse()
       ▼
┌─────────────┐
│   RootNode  │──┬─ children
│    (AST)    │  ├─ helpers
└──────┬──────┘  └─ components
       │ transform()
       ▼
┌─────────────────────┐
│ Transformed AST     │──┬─ codegenNode
│                     │  ├─ hoists[]
└──────┬──────────────┘  └─ cached[]
       │ generate()
       ▼
┌─────────────────────┐
│   Render Function   │──┬─ imports
│       Code          │  ├─ hoisted vars
└─────────────────────┘  └─ render()

🚀 静态提升流程图

transform(ast, { hoistStatic: true })
         │
         ▼
    traverseNode()
         │
         ▼
   ┌──────────────────┐
   │ cacheStatic()    │
   └────────┬─────────┘
            │
            ▼
   ┌──────────────────┐
   │ getConstantType()│◀───────┐
   └────────┬─────────┘        │
            │                  │
   ┌────────▼─────────┐        │
   │ CAN_STRINGIFY?   │        │
   └────────┬─────────┘        │
      Yes   │   No             │
            │    └─────────────┘
            ▼                  (递归检查)
   ┌──────────────────┐
   │ context.hoist()  │
   └────────┬─────────┘
            │
            ▼
   ┌──────────────────┐
   │ 生成 _hoisted_N  │
   └──────────────────┘

📝 代码生成流程

generate(ast)
    │
    ├─ genModulePreamble()  // import 语句
    │
    ├─ genHoists()          // 静态提升变量
    │      │
    │      └─ const _hoisted_1 = ...
    │
    ├─ push('function render() {')
    │
    ├─ genNode(codegenNode)  // 生成 VNode 树
    │      │
    │      ├─ genVNodeCall()
    │      │      │
    │      │      └─ createVNode(...)
    │      │
    │      ├─ genCallExpression()
    │      │
    │      └─ genCacheExpression()
    │
    └─ push('}')
          │
          ▼
    { code: string, ast, map }

📊 性能对比

指标 Vue 2 Vue 3 提升
初始渲染 100% 55% ~2x
更新性能 100% ~20% ~5x
内存占用 100% ~50% ~2x
包体积 23KB 10KB Tree-shakable

关键优化:Patch Flags + Block 树 + 静态提升 = 极致性能

⚔️ 编译产物对比

Vue 2

function render() {
  return _c('div', [
    _c('span', {
      staticClass: 'static'
    }, [_v("Static")]),
    _c('p', [_v(_s(msg))])
  ])
}
// 每次渲染都创建所有 VNode

Vue 3

const _hoisted_1 = 
  createElementVNode("span", 
    { class: "static" }, 
    "Static"
  )

function render(_ctx, _cache) {
  return createElementVNode("div", null, [
    _hoisted_1,  // 复用
    createElementVNode("p", null, 
      toDisplayString(_ctx.msg), 
      1 /* TEXT */
    )
  ])
}
// 静态节点只创建一次

📈 优化效果分析

静态内容占比

静态程度 提升效果
90% 静态 ~90% 减少创建
50% 静态 ~50% 减少创建
10% 静态 ~10% 减少创建

更新性能

动态类型 Patch Flag 优化
仅文本 TEXT (1) 直接更新
仅 class CLASS (2) 跳过其他
仅 style STYLE (4) 跳过其他

✅ 最佳实践

利用静态提升

<!-- ✅ 好 -->
<div>
  <span>Static</span>
  {{ dynamic }}
</div>

<!-- ❌ 避免 -->
<div :class="cls">
  <span :id="id">Text</span>
</div>

合理使用 v-once

<!-- ✅ 适合 v-once -->
<div v-once>
  <h1>Static Header</h1>
</div>

<!-- ❌ 不适合 -->
<div v-once>
  {{ dynamicContent }}
</div>

⚠️ 反模式警示

1. 过度使用 v-once

<!-- ❌ 反模式:动态内容用 v-once 不会更新 -->
<div v-once>{{ counter }}</div>

2. 不必要的响应式

// ❌ 反模式
const staticData = ref({ type: 'static' })

// ✅ 正确
const staticData = { type: 'static' }

3. 复杂表达式

<!-- ❌ 难以优化 -->
<div :class="isA ? 'a' : isB ? 'b' : 'c'">

🔍 调试技巧

查看编译产物

// 使用 Vue Template Explorer
// https://vue-next-template-explorer.netlify.app/

// 或在代码中
import { compile } from 'vue'

const { code } = compile(template, {
  hoistStatic: true,
  cacheHandlers: true
})
console.log(code)

检查 Patch Flags

// 开发模式下查看
// VNode 上的 patchFlag 属性

import { PatchFlags } from 'vue'

// 检查标记
if (vnode.patchFlag & PatchFlags.TEXT) {
  console.log('Has dynamic text')
}

🔄 与其他框架对比

框架 编译策略 优化方式
Vue 3 AOT 编译 Patch Flags + Block + Hoist
React JSX 编译 Fiber + Reconciliation
Svelte AOT 到原生 JS 无运行时 + 细粒度更新
Angular AOT 编译 Ivy + 增量 DOM
Solid JSX 编译 细粒度响应式 + 无 VDOM

🔮 未来展望

编译器改进

  • 更激进的静态分析
  • 自动 v-memo 推断
  • SSR 优化增强
  • 更好的类型推断

新特性

  • Vapor Mode(无 VDOM)
  • 更细粒度的更新
  • 编译时水合
  • 响应式编译器

Vapor Mode:实验性的无虚拟 DOM 模式,性能接近原生 DOM 操作

📝 总结

核心优化

  • 静态提升 - 避免重复创建
  • Patch Flags - 精确更新标记
  • Block 树 - 扁平化 diff
  • 缓存机制 - 复用计算结果

关键文件

  • compile.ts - 入口
  • transform.ts - 转换
  • cacheStatic.ts - 静态优化
  • codegen.ts - 代码生成

核心思想:编译时尽可能多地完成工作,运行时只做必要的更新

📚 扩展阅读

官方文档

源码阅读

  • packages/compiler-core
  • packages/compiler-dom
  • packages/runtime-core

推荐工具:Vue Template Explorer - 实时查看编译产物

💚 感谢阅读

Vue 3 编译器优化策略深度解析

基于源码分析
2026-03-16 | 查克小助理