Skip to content

Vue 编译器帮助我们做了什么

如果你已经在 play.vuejs.org 演练场 看过编译结果,大概会发现:Vue 模板并不是直接运行在浏览器里的。

接下来这篇会从源代码和产物角度,建立一张 Vue 编译器的基础地图。

先看结论

Vue 编译器不是一个“可有可无”的附属品,而是把开发者写的高层语法转成浏览器可执行代码的连接层。

如果只看运行时,你能看到 Vue 最后怎么执行;如果把编译阶段一起看进来,你才知道 Vue 为什么能提供这些写法,以及为什么它能做这些优化。

依赖项介绍

从包职责看,Vue 编译与运行大致会涉及这些主要模块:

  • @vue/compiler-sfc:用于解析和编译 .vue 单文件组件,将这些组件转换成可执行的 JavaScript 模块。
  • @vue/compiler-dom:负责将 Vue 模板编译成适用于浏览器环境的渲染函数。
  • @vue/compiler-core:承载更通用的模板解析、转换和代码生成能力。
  • @vue/runtime-dom:包含浏览器运行时能力,包括虚拟 DOM、组件生命周期和 DOM 操作。
  • @vue/runtime-core:承载与平台无关的组件、VNode、调度和渲染逻辑。
  • @vue/server-renderer:提供服务端渲染(SSR)能力。
  • @vue/shared:包含多个 Vue 包共享的工具函数和常量。

这些模块不是孤立存在的。SFC 编译、模板编译、运行时渲染和 SSR 会在不同场景里组合起来工作。

主要作用

这里我们重点介绍 @vue/compiler-sfc 的主要作用:

  1. 将模板(template)编译为渲染函数(Render Function)
  2. 将样式和预处理语言再次处理,编译成新的样式结果
  3. scriptscript setup 编译、组合,再与 Render Function 拼装

所以可以这样理解,一个 .vue 文件最后通常会被拆解并转换成:

  • 一个组件逻辑模块
  • 一个渲染函数模块
  • 零个或多个样式模块

最后再由构建工具把这些内容重新组织成浏览器能够消费的模块图。

编译阶段

编译过程的三个阶段:

  1. 解析(Parse):将模板字符串转换为抽象语法树(AST)
  2. 转换(Transform):对 AST 做各种优化与转换(如静态提升、PatchFlag 标记、编译时指令处理等)
  3. 代码生成(Codegen):生成最后的渲染函数代码

其中转换阶段最该细看,大量的编译优化都发生在这里:

  • 静态提升(Static Hoisting):将不会变化的 VNode 提升到渲染函数外部,避免每次渲染都重新创建
  • PatchFlag:为动态节点标记变化类型(文本、class、style 等),让运行时 diff 可以跳过静态部分
  • Block Tree:通过 openBlock / createElementBlock 将动态节点收集到 block 中,实现扁平化 diff
  • 事件缓存:将事件处理函数缓存起来,避免每次渲染创建新函数

这些优化是 Vue 模板相比手写 h() 函数的重要优势。编译器能自动分析模板结构,生成比手写更高效的代码。

可以把 Vue 编译链路粗略记成三层

第一层:SFC 解析

先把 .vue 文件拆成 templatescriptstyle 等块。

第二层:模板编译

把模板 AST 转成渲染函数代码,并在这个阶段加入各种优化信息。

第三层:运行时消费

最后由 runtime-dom / runtime-core 去执行渲染函数、创建 VNode、挂载 DOM、处理更新。

把这三层分清楚,再去看 script setupv-modelscoped style、SSR,就不容易混乱。

跑一跑

fork 这个项目,git clone 下来之后执行 pnpm install

然后在 apps/fully-compiled/scripts/index.ts 中打上断点,再启动 VS Code 调试功能,观察 Vue 单文件组件的编译过程。

阅读时看什么

读到这里时,不要只记住名词,建议顺手验证三件事:

  1. 一个简单 .vue 文件会被拆成哪些请求或产物
  2. 一个模板表达式最后会落成怎样的渲染函数
  3. 一个看似普通的语法糖,究竟属于哪一层处理

如果这三件事能逐步看清,后面的章节会顺很多。

Released under the CC BY-NC-SA 4.0 License.