Skip to content

实现完整流程 - Compile 联调

本节对标 Vue 3 源码 vue 主包入口 + @vue/compiler-core 中的 compile.ts 源码位置:packages/vue/src/index.tspackages/compiler-core/src/compile.ts

全链路概览

至此,我们已经实现了 mini-vue 的所有核心模块。本节将它们串联起来,完成从模板字符串到页面渲染的完整流程:

template (字符串)

    ▼  ─── Parse ───

   AST (原始抽象语法树)

    ▼  ─── Transform ───

  AST' (转换后的 AST,带 codegenNode)

    ▼  ─── Codegen ───

  render function string (代码字符串)

    ▼  ─── new Function() ───

  render function (可执行的渲染函数)

    ▼  ─── 执行 render() ───

  VNode Tree (虚拟 DOM 树)

    ▼  ─── Renderer (patch) ───

  Real DOM (真实 DOM)

这条链路涉及两大系统的对接:

  • 编译系统:compiler-core(parse → transform → codegen)
  • 运行时系统:runtime-core + runtime-dom(createApp → render → patch → DOM)

baseCompile —— 编译器核心入口

ts
// 对标 packages/compiler-core/src/compile.ts
import { baseParse } from './parse'
import { transform } from './transform'
import { generate } from './codegen'
import { transformElement } from './transforms/transformElement'
import { transformText } from './transforms/transformText'
import { transformExpression } from './transforms/transformExpression'

// 编译器的核心入口函数:将模板字符串编译为 render 函数代码
// "base" 前缀意味着这是基础实现,不同平台可在此基础上扩展
export function baseCompile(template: string) {
  // Step 1: Parse — 将模板字符串解析为 AST(抽象语法树)
  const ast = baseParse(template)

  // Step 2: Transform — 对 AST 进行语义分析和转换
  // 通过插件化的 nodeTransforms 处理不同类型的节点
  transform(ast, {
    nodeTransforms: [
      transformExpression,  // 处理表达式(如添加 _ctx 前缀)
      transformElement,     // 处理元素(生成 codegenNode)
      transformText,        // 合并相邻的文本和插值
    ],
  })

  // Step 3: Codegen — 根据转换后的 AST 生成 render 函数代码字符串
  return generate(ast)
}

baseCompile 将三个阶段串联在一起。之所以叫 "base",是因为在 Vue 3 中,不同平台可以在此基础上扩展(如 SSR 编译器、SFC 编译器等)。

编译流程示例

ts
const { code } = baseCompile('<div>hi,{{ message }}</div>')

console.log(code)
// 输出:
// const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
//
// return function render(_ctx, _cache) {
//   return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
// }

compileToFunction —— 代码字符串转为可执行函数

编译器生成的是代码字符串,需要通过 new Function() 转换为真正可执行的函数:

ts
// 对标 packages/vue/src/index.ts - compileToFunction
import * as runtimeDom from '@mini-vue/runtime-dom'

// 将模板字符串编译为可执行的 render 函数
// 这是编译系统和运行时系统的桥梁
function compileToFunction(template: string) {
  // 调用编译器核心,得到 render 函数的代码字符串
  const { code } = baseCompile(template)

  // 使用 new Function 将代码字符串转为真正的函数
  // 第一个参数 "Vue" 定义了工厂函数的形参名
  // 第二个参数 code 是工厂函数的函数体(包含 const { ... } = Vue 和 return render)
  // 立即调用工厂函数,传入 runtimeDom 作为 "Vue" 参数的实际值
  const render = new Function('Vue', code)(runtimeDom)

  return render // 返回编译后的 render 函数
}

new Function 的工作原理

ts
// 生成的代码字符串
const code = `
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

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

// new Function('Vue', code) 等价于创建了如下工厂函数:
const factory = function(Vue) {
  // 从传入的 Vue(即 runtimeDom)中解构出辅助函数
  const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue

  // 返回 render 函数
  return function render(_ctx, _cache) {
    return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))
  }
}

// 立即调用工厂函数,传入运行时模块,获得 render 函数
const render = factory(runtimeDom)

通过这种方式,codegen 生成的代码中的 Vue 变量被绑定为 runtimeDom 模块,从而可以访问 toDisplayStringcreateElementVNode 等运行时辅助函数。

注册编译器:registerRuntimeCompiler

为了保持运行时和编译器的解耦,Vue 3 使用注册模式将编译器注入运行时:

ts
// 对标 packages/runtime-core/src/component.ts
// 用于存储编译器函数的变量,初始为 undefined
let compile: ((template: string) => any) | undefined

// 注册运行时编译器:允许外部将编译器函数注入到运行时
// 这种设计实现了运行时和编译器的解耦
export function registerRuntimeCompiler(_compile: any) {
  compile = _compile
}
ts
// 对标 packages/vue/src/index.ts
// 在应用入口注册编译器,将 compileToFunction 注入到运行时
import { registerRuntimeCompiler } from '@mini-vue/runtime-core'

// 注册后,运行时在遇到 template 选项时就能调用编译器
registerRuntimeCompiler(compileToFunction)

这种设计的好处:

  • Tree-shaking 友好:如果只用 render 函数(不用 template),编译器代码不会被打包
  • 运行时可独立使用@vue/runtime-dom 可以不依赖 @vue/compiler-core
  • 可替换编译器:理论上可以注册不同的编译器实现

在组件中集成编译器:finishComponentSetup

当组件没有提供 render 函数但有 template 选项时,需要在组件初始化阶段编译模板:

ts
// 对标 packages/runtime-core/src/component.ts - finishComponentSetup
// 组件初始化的最后一步:确保组件有 render 函数
function finishComponentSetup(instance: any) {
  const Component = instance.type // 获取组件选项对象

  // 优先使用组件自身已有的 render 函数(可能来自 setup 返回或用户直接定义)
  if (!instance.render) {
    // 如果没有 render 函数,但注册了编译器且组件有 template 选项
    if (compile && Component.template) {
      const template = Component.template
      // 编译 template 为 render 函数,并缓存到组件选项上
      // 缓存的好处:同一组件多次实例化时不需要重复编译
      Component.render = compile(template)
    }

    // 将 render 函数赋值给组件实例
    instance.render = Component.render
  }
}

调用时机在组件 setup() 执行之后:

ts
// 对标 packages/runtime-core/src/component.ts - setupComponent
// 组件初始化入口:初始化 props、slots,然后执行 setup
function setupComponent(instance: any) {
  const { props, children } = instance.vnode

  initProps(instance, props)    // 初始化组件的 props
  initSlots(instance, children) // 初始化组件的 slots

  // 执行有状态组件的 setup 逻辑
  setupStatefulComponent(instance)
}

// 执行组件的 setup 函数
function setupStatefulComponent(instance: any) {
  const Component = instance.type
  const { setup } = Component

  if (setup) {
    // 调用 setup,传入 props 和上下文对象
    const setupResult = setup(instance.props, {
      emit: instance.emit,
    })
    // 处理 setup 的返回值
    handleSetupResult(instance, setupResult)
  } else {
    // 没有 setup 函数,直接进入 finishComponentSetup
    finishComponentSetup(instance)
  }
}

// 处理 setup 函数的返回值
function handleSetupResult(instance: any, setupResult: any) {
  if (typeof setupResult === 'function') {
    // setup 返回函数 → 作为组件的 render 函数
    instance.render = setupResult
  } else if (typeof setupResult === 'object') {
    // setup 返回对象 → 作为模板中可访问的数据
    // proxyRefs 使得模板中使用 ref 时不需要 .value
    instance.setupState = proxyRefs(setupResult)
  }

  // 最后完成组件设置(如果还没有 render 函数,会在这里编译 template)
  finishComponentSetup(instance)
}

完整示例:从模板到页面

使用 template 选项

ts
import { createApp, ref } from '@mini-vue/vue'

// 使用 template 选项定义组件
// 编译器会在运行时将 template 编译为 render 函数
const App = {
  template: '<div>hi,{{ message }}</div>', // 模板字符串
  setup() {
    const message = ref('mini-vue') // 创建响应式数据
    return {
      message, // 暴露给模板使用
    }
  },
}

// 创建应用并挂载到 DOM
createApp(App).mount('#app')

执行流程

1. createApp(App)
   └── 创建应用实例

2. app.mount('#app')
   └── createVNode(App)           → 创建组件 VNode
   └── render(vnode, container)    → 开始渲染
   └── patch(null, vnode)          → 初次挂载
   └── processComponent()
       └── mountComponent()
           ├── createComponentInstance()    → 创建组件实例
           ├── setupComponent()
           │   ├── setup() 执行             → 返回 { message: ref('mini-vue') }
           │   └── finishComponentSetup()
           │       └── compile(template)    → 编译模板
           │           ├── baseParse()      → 生成 AST
           │           ├── transform()      → 转换 AST
           │           └── generate()       → 生成代码字符串
           │           └── new Function()   → 得到 render 函数

           └── setupRenderEffect()
               └── render.call(proxy)       → 执行 render 函数
                   └── createElementVNode('div', null, 'hi,' + toDisplayString(message))
                       └── VNode { type: 'div', children: 'hi,mini-vue' }
                           └── patch → mountElement → DOM

最终 DOM

html
<div id="app">
  <div>hi,mini-vue</div>
</div>

使用 render 函数(无需编译器)

ts
import { createApp, ref, h } from '@mini-vue/runtime-dom'

// 使用 render 函数代替 template(无需编译器)
// setup 返回一个函数时,该函数即为组件的 render 函数
const App = {
  setup() {
    const message = ref('mini-vue')
    // 直接返回 render 函数,使用 h() 创建 VNode
    return () => h('div', null, 'hi,' + message.value)
  },
}

createApp(App).mount('#app')

这种方式跳过了编译步骤,直接使用 h 函数创建 VNode,适合不需要编译器的场景(如组件库开发)。

Vue 3 编译优化概览

Vue 3 的完整编译器还包含以下高级优化,我们在 mini-vue 中不实现,但了解它们对于理解 Vue 3 的性能优势至关重要:

PatchFlags

ts
// 对标 packages/shared/src/patchFlags.ts
// PatchFlags 使用位掩码(bit flags)表示节点的动态绑定类型
// 编译器在编译时分析模板,为每个 VNode 标记哪些部分是动态的
export const enum PatchFlags {
  TEXT = 1,          // 动态文本内容(如 {{ msg }})
  CLASS = 1 << 1,    // 动态 class 绑定(如 :class="active")
  STYLE = 1 << 2,    // 动态 style 绑定(如 :style="{ color }")
  PROPS = 1 << 3,    // 动态非 class/style 的属性(如 :id="dynamicId")
  FULL_PROPS = 1 << 4, // 动态 key 属性(属性名也是动态的)
  NEED_HYDRATION = 1 << 5,   // SSR 水合标记
  STABLE_FRAGMENT = 1 << 6,  // 子节点顺序不变的 Fragment
  KEYED_FRAGMENT = 1 << 7,   // 带 key 的 Fragment
  UNKEYED_FRAGMENT = 1 << 8, // 不带 key 的 Fragment
  NEED_PATCH = 1 << 9,       // 非 props 的补丁标记(如 ref、指令)
  DYNAMIC_SLOTS = 1 << 10,   // 动态插槽
  HOISTED = -1,  // 已提升的静态节点,diff 时可完全跳过
  BAIL = -2,     // 退出优化模式,进行完整 diff
}

编译器在编译时分析每个节点的动态绑定,为 VNode 标记 PatchFlag。运行时 diff 时,只检查标记为动态的部分,跳过静态内容。

html
<div>
  <span>静态内容</span>
  <span>{{ dynamic }}</span>
</div>

编译后的 {{ dynamic }} 对应的 VNode 会带上 PatchFlags.TEXT,diff 时只比较文本内容,不比较 props、children 等。

Block Tree

ts
// Block Tree 编译后的代码(示意)
// _openBlock 创建一个 Block 容器,收集其中的动态子节点
function render(_ctx) {
  return (_openBlock(), _createElementBlock('div', null, [
    _createElementVNode('span', null, '静态'),  // 静态节点,不会进入 dynamicChildren
    _createElementVNode('span', null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
    // 最后的 1 就是 PatchFlags.TEXT,标记此节点有动态文本
    // 该节点会被自动收集到 Block 的 dynamicChildren 中
  ]))
}

Block Tree 将模板中的动态节点"扁平化"收集到一个 dynamicChildren 数组中。运行时 diff 不再需要递归遍历整棵 VNode 树,只需遍历 dynamicChildren

传统 diff:遍历整棵树 O(n)
Block diff:只遍历动态节点 O(dynamic nodes)

静态提升(Static Hoisting)

ts
// 提升前:每次执行 render 都会重新创建静态 VNode 对象
function render(_ctx) {
  return h('div', [
    h('span', '静态内容'),     // 每次 render 都创建新对象,浪费内存和 GC
    h('span', _ctx.dynamic),
  ])
}

// 提升后:静态 VNode 提升到模块顶层,只创建一次
const _hoisted_1 = h('span', '静态内容')  // 在模块加载时创建一次

function render(_ctx) {
  return h('div', [
    _hoisted_1,                 // 直接复用同一个对象引用,无需重新创建
    h('span', _ctx.dynamic),
  ])
}

静态提升减少了 VNode 的创建开销和 GC 压力。

缓存事件处理函数(Cache Event Handlers)

html
<button @click="handleClick">click</button>
ts
// 未缓存:每次 render 都创建新的内联事件处理函数
function render(_ctx) {
  return h('button', {
    onClick: (...args) => _ctx.handleClick(...args)  // 每次都是新引用 → 触发不必要的 props diff
  })
}

// 缓存后:利用 _cache 数组复用同一个函数引用
function render(_ctx, _cache) {
  return h('button', {
    // 如果 _cache[0] 已存在则复用,否则创建并缓存
    // 这样 diff 时 onClick 引用不变,不会触发 DOM 更新
    onClick: _cache[0] || (_cache[0] = (...args) => _ctx.handleClick(...args))
  })
}

缓存避免了因事件处理函数引用变化导致的不必要 patch。

优化效果总结

优化手段效果节省的开销
PatchFlags精确 diff跳过静态属性比较
Block Tree扁平化 diff跳过静态子树遍历
静态提升减少 VNode 创建GC 压力、内存分配
事件缓存避免不必要 patchprops 比较、DOM 操作

这些优化的核心思想是:利用编译时的静态分析,减少运行时的工作量。

整个 mini-vue 架构总结

模块依赖图

┌─────────────────────────────────────────────────┐
│                     vue                          │
│  (主入口:集成 compiler + runtime)                │
│  ┌──────────────────────────────────────────┐   │
│  │          registerRuntimeCompiler          │   │
│  │          compileToFunction                │   │
│  └──────────────────────────────────────────┘   │
└──────────┬───────────────────┬──────────────────┘
           │                   │
           ▼                   ▼
┌─────────────────┐  ┌──────────────────┐
│ compiler-core   │  │  runtime-dom     │
│                 │  │                  │
│ ┌─────────────┐│  │ ┌──────────────┐ │
│ │ parse.ts    ││  │ │ nodeOps.ts   │ │
│ │ transform.ts││  │ │ patchProp.ts │ │
│ │ codegen.ts  ││  │ │ index.ts     │ │
│ └─────────────┘│  │ └──────────────┘ │
└─────────────────┘  └───────┬──────────┘


                   ┌──────────────────┐
                   │  runtime-core    │
                   │                  │
                   │ ┌──────────────┐ │
                   │ │ renderer.ts  │ │
                   │ │ component.ts │ │
                   │ │ vnode.ts     │ │
                   │ │ scheduler.ts │ │
                   │ │ apiWatch.ts  │ │
                   │ └──────────────┘ │
                   └───────┬──────────┘


                   ┌──────────────────┐
                   │  reactivity      │
                   │                  │
                   │ ┌──────────────┐ │
                   │ │ reactive.ts  │ │
                   │ │ ref.ts       │ │
                   │ │ computed.ts  │ │
                   │ │ effect.ts    │ │
                   │ │ watch.ts     │ │
                   │ └──────────────┘ │
                   └──────────────────┘

各模块职责

模块职责核心 API
reactivity响应式系统reactive / ref / computed / effect / watch
runtime-core平台无关运行时createRenderer / createVNode / h / createApp
runtime-domDOM 平台运行时render / createApp / nodeOps / patchProp
compiler-core编译器核心baseParse / transform / generate / baseCompile
vue主入口createApp(集成编译器和运行时)

数据流向

用户代码

  │  createApp({ template, setup })

┌─────────────┐
│ setup() 执行 │ → 返回响应式数据
└──────┬──────┘


┌──────────────┐
│ 编译 template │ → render 函数 (如果没有直接提供 render)
└──────┬───────┘


┌──────────────────┐
│ render() 执行     │ → 生成 VNode 树
│ (依赖收集发生)     │
└──────┬───────────┘


┌──────────────────┐
│ patch(VNode, DOM) │ → 初次渲染 / 更新 DOM
└──────┬───────────┘


    Real DOM

       │  用户交互 → 修改响应式数据

┌──────────────────┐
│ trigger → effect │ → 调度器批量执行
│ → 重新 render()   │ → 新 VNode
│ → patch(新旧)     │ → Diff → 最小化 DOM 更新
└──────────────────┘

性能优化策略总结

编译时优化

策略实现位置说明
PatchFlagscompiler-core (transform)标记动态绑定类型
Block Treecompiler-core (codegen)收集动态节点,扁平化 diff
静态提升compiler-core (transform)静态 VNode 提升到模块顶层
事件缓存compiler-core (transform)缓存 inline 事件处理函数
SSR 优化compiler-ssr生成字符串拼接代码

运行时优化

策略实现位置说明
响应式精确追踪reactivity (effect)只有依赖变化才触发更新
异步批量更新runtime-core (scheduler)合并同一 tick 内的多次更新
最长递增子序列runtime-core (renderer)最小化 DOM 移动操作
组件级更新runtime-core (renderer)只更新受影响的组件子树
位运算类型判断shared (ShapeFlags)高效的类型判断和组合

用户层优化

策略说明
v-once只渲染一次,跳过后续 diff
v-memo条件缓存子树
key 属性帮助 diff 算法正确识别和复用节点
shallowRef / shallowReactive减少深层响应式追踪开销
computed缓存计算结果,避免重复计算

对比 React JSX 编译流程

Vue 3 模板编译

<div>{{ msg }}</div>

     ↓ Vue Compiler

const { toDisplayString, createElementVNode } = Vue
return function render(_ctx) {
  return createElementVNode('div', null, toDisplayString(_ctx.msg), 1)
}

React JSX 编译

jsx
<div>{msg}</div>

     ↓ Babel / SWC

import { jsx as _jsx } from 'react/jsx-runtime'
function Component() {
  return _jsx('div', { children: msg })
}

全链路对比

维度Vue 3React
模板语法HTML-like TemplateJSX(JavaScript 扩展)
编译工具Vue Compiler(自研)Babel / SWC / esbuild
编译产物render 函数字符串JSX → jsx() 调用
运行时编译✅ 支持(vue 完整版)❌ 不支持
构建时编译✅ 推荐(@vue/compiler-sfc✅ 必须
优化方式编译时静态分析 + 运行时响应式React Compiler(实验性)
更新粒度组件级(响应式精确追踪)组件级(自顶向下 reconcile)
Diff 策略双端 + LIS单向遍历
编译产物大小带 PatchFlags,较大较小(无优化标记)
学习曲线模板语法直观JSX 需了解 JS 表达式

核心理念差异

Vue 的编译时优化:通过编译器静态分析模板,在编译时就确定了哪些是动态的、哪些是静态的。运行时 diff 时只处理动态部分。

React 的运行时优化:传统上依赖 memouseMemouseCallback 等手动优化。React Compiler(2024+)正在尝试自动化编译时优化,但由于 JSX 的灵活性,优化空间不如 Vue 模板。

测试用例

ts
import { baseCompile } from '../src/compile'

describe('compile', () => {
  // 测试:纯文本模板应正确编译
  it('should compile text', () => {
    const { code } = baseCompile('hello')
    // 断言:生成的代码包含文本字面量和 render 函数
    expect(code).toContain("'hello'")
    expect(code).toContain('function render')
  })

  // 测试:插值表达式应正确编译
  it('should compile interpolation', () => {
    const { code } = baseCompile('{{ msg }}')
    // 断言:包含 toDisplayString 辅助函数和变量名
    expect(code).toContain('toDisplayString')
    expect(code).toContain('msg')
  })

  // 测试:元素节点应正确编译
  it('should compile element', () => {
    const { code } = baseCompile('<div>hello</div>')
    // 断言:包含 createElementVNode 调用和标签名
    expect(code).toContain('createElementVNode')
    expect(code).toContain("'div'")
  })

  // 测试:元素内包含插值的复杂模板应正确编译
  it('should compile element with interpolation', () => {
    const { code } = baseCompile('<div>hi,{{ msg }}</div>')
    // 断言:同时包含元素创建、字符串转换、文本和 + 连接符
    expect(code).toContain('createElementVNode')
    expect(code).toContain('toDisplayString')
    expect(code).toContain("'hi,'")
    expect(code).toContain(' + ')
    expect(code).toContain('msg')
  })

  // 测试:生成的代码应是合法的 JavaScript
  it('should generate valid JavaScript', () => {
    const { code } = baseCompile('<div>{{ message }}</div>')
    // 断言:new Function 不会抛出语法错误
    expect(() => new Function(code)).not.toThrow()
  })

  // 测试:生成的代码应能执行并返回正确的 VNode 结构
  it('should generate executable render function', () => {
    const { code } = baseCompile('<div>hello</div>')

    // 模拟运行时辅助函数
    const toDisplayString = (val: any) => String(val)
    const createElementVNode = (tag: any, props: any, children: any) => ({
      tag,
      props,
      children,
    })

    // 通过 new Function 创建工厂函数,传入模拟的运行时模块
    const render = new Function('Vue', code)({
      toDisplayString,
      createElementVNode,
    })

    // 执行 render 函数并验证返回的 VNode
    const vnode = render({ message: 'world' })
    expect(vnode.tag).toBe('div')
    expect(vnode.children).toBe('hello')
  })

  // 测试:动态数据应正确反映在 render 结果中
  it('should compile and execute with dynamic data', () => {
    const { code } = baseCompile('<div>{{ message }}</div>')

    // 模拟运行时辅助函数
    const toDisplayString = (val: any) => String(val)
    const createElementVNode = (tag: any, props: any, children: any) => ({
      tag,
      props,
      children,
    })

    const render = new Function('Vue', code)({
      toDisplayString,
      createElementVNode,
    })

    // 传入不同的数据,验证 render 输出随数据变化
    const vnode = render({ message: 'hello world' })
    expect(vnode.tag).toBe('div')
    expect(vnode.children).toBe('hello world')
  })
})

describe('full integration', () => {
  // 端到端测试:模板 → 编译 → render → VNode 的完整流程
  it('should work end to end: template → compile → render → vnode', () => {
    const template = '<div>hi,{{ name }}</div>'
    const { code } = baseCompile(template)

    // 模拟运行时辅助函数
    const helpers = {
      toDisplayString: (val: any) => String(val ?? ''),
      createElementVNode: (type: any, props: any, children: any) => ({
        type,
        props,
        children,
      }),
    }

    // 编译并执行 render 函数
    const render = new Function('Vue', code)(helpers)
    const ctx = { name: 'mini-vue' } // 模拟组件上下文
    const vnode = render(ctx)

    // 断言:VNode 的结构和内容正确
    expect(vnode.type).toBe('div')
    expect(vnode.children).toBe('hi,mini-vue')
  })

  // 测试:重新渲染时应反映数据变化
  it('should reflect data changes on re-render', () => {
    const template = '<p>count: {{ count }}</p>'
    const { code } = baseCompile(template)

    const helpers = {
      toDisplayString: (val: any) => String(val ?? ''),
      createElementVNode: (type: any, props: any, children: any) => ({
        type,
        props,
        children,
      }),
    }

    const render = new Function('Vue', code)(helpers)

    // 第一次渲染:count = 0
    const vnode1 = render({ count: 0 })
    expect(vnode1.children).toBe('count: 0')

    // 第二次渲染:count = 42,验证数据变化正确反映
    const vnode2 = render({ count: 42 })
    expect(vnode2.children).toBe('count: 42')
  })
})

本节小结

  1. baseCompile — 编译器核心入口,串联 parse → transform → codegen 三个阶段
  2. compileToFunction — 通过 new Function() 将代码字符串转为可执行的 render 函数
  3. registerRuntimeCompiler — 注册模式,运行时与编译器解耦
  4. finishComponentSetup — 组件初始化时编译 template,获取 render 函数
  5. 编译优化 — PatchFlags / Block Tree / 静态提升 / 事件缓存(Vue 3 完整版)
  6. 模块架构 — reactivity → runtime-core → runtime-dom → vue,层次清晰、职责明确

全系列总结

至此,我们完成了 mini-vue 的全部实现。回顾整个系列:

响应式系统(第 1-7 节)

Proxy 代理到 effect 依赖收集,从 ref/computedwatch/watchEffect,构建了 Vue 3 的响应式核心。核心思想:通过自动追踪依赖关系,实现数据变化时精确触发更新。

运行时系统(第 8-12 节)

从 VNode 数据结构到 renderer 的 mount/update 流程,从双端 Diff + LIS 算法到跨平台 createRenderer,构建了 Vue 3 的渲染核心。核心思想:声明式的 VNode 描述 UI,命令式的 renderer 操作 DOM。

编译系统(第 21-24 节)

从 parse 递归下降解析,到 transform 插件化转换,到 codegen 代码生成,再到与运行时的联调。核心思想:将人类友好的模板语法,编译为机器高效的 render 函数。

贯穿全局的设计原则

原则体现
关注点分离响应式 / 运行时 / 编译器 三大系统独立
依赖倒置runtime-core 不依赖 runtime-dom,通过 options 注入
开闭原则transform 插件化,可扩展不可修改
单一职责parse 只管解析,transform 只管转换,codegen 只管生成
编译时优化尽可能在编译时完成分析,减少运行时工作量
渐进式架构按需使用:只用响应式 / 运行时 / 完整版
Vue 3 = Reactivity + Runtime + Compiler
      = 精确的变化追踪 + 高效的 DOM 操作 + 智能的编译优化

用心学习,用代码说话 💻