编译器是什么意思呢?和我们之前讲过的响应式系统有什么关联呢?
编译器只会生成“访问响应式数据的代码”,执行收集依赖在运行时。
编译 .vue 文件 -> render -> 访问 _ctx.count -> proxy.set -> track而本文我们着重讲讲编译这个过程。
一、Vue 编译器整体架构
Vue3 把编译器拆分为三个部分:
@vue/compiler-sfc → 处理 .vue 文件 @vue/compiler-dom → DOM 平台相关编译 @vue/compiler-core → 核心编译逻辑(平台无关)90% 的“原理”,都在 compiler-core
核心目标是把 template 转成“尽量少 diff 的 render 函数”
最终产物不是字符串,而是:
二、编译流水线
Vue 编译是一个标准三段式编译器:
学过 babel 的同学可以发现,这个过程非常像 babel 。
Template ↓ parse Template AST ↓ transform JavaScript AST(增强) ↓ generate render function源码入口(compiler-core):
baseCompile(template, options)三、第一阶段:parse(模板 → AST)
3.1 AST 节点结构
interface Node { type: NodeTypes loc }常见节点类型:
enum NodeTypes { ROOT, ELEMENT, TEXT, INTERPOLATION, SIMPLE_EXPRESSION, ATTRIBUTE, DIRECTIVE }3.2 parse 的本质
依次遍历解析每一个节点,把 template 字符串解析成一棵 AST
示例:
<div>{{ count }}</div>生成 AST(简化):
ROOT └─ ELEMENT(div) └─ INTERPOLATION └─ SIMPLE_EXPRESSION(count)四、第二阶段:transform(AST → 优化 AST)
transform 阶段做的不是“改结构”,而是:给 AST 打“运行时优化标记”
4.1 transform 的执行模型
function transform(root, options) { traverseNode(root, context) }遍历 AST,对每个节点:
- 执行nodeTransforms
- 收集依赖
- 标记 PatchFlag
- 构建 BlockTree
4.2 transformContext
interface TransformContext { helpers components directives currentNode parent }transform 不是“纯函数”,而是有上下文的编译过程
4.3 表达式分析
{{ count }}会被转成:
toDisplayString(_ctx.count)并且:
- 标记该节点依赖响应式数据
- 未来会生成 PatchFlag
4.4 PatchFlag 的来源
PatchFlag 是在 transform 阶段生成的:
PatchFlags.TEXT PatchFlags.CLASS PatchFlags.PROPS PatchFlags.STYLE例如:
<div>{{ count }}</div>最终标记:
1 /* TEXT */该魔法注释表示:这个节点只需要 diff 文本
4.5 Block Tree
openBlock() createElementBlock(...)Block 的作用:收集所有“动态子节点”
transform 阶段会判断:
- 哪些节点是静态的
- 哪些是动态的
动态的才进入 block:
block.children.push(node)diff 时只遍历 block,不全树 diff
五、第三阶段:generate(AST → render 函数)
5.1 generate 的目标
输出一个 JS 函数 AST,最后 stringify:
function render(_ctx, _cache) { return ... }5.2 helper 的注入机制
transform 阶段收集:
context.helpers.add(CREATE_ELEMENT_VNODE)generate 时生成:
import { createElementVNode } from "vue"5.3 生成代码示例
<div class="a">{{ count }}</div>生成 render:
function render(_ctx, _cache) { return openBlock(), createElementBlock( "div", { class: "a" }, toDisplayString(_ctx.count), 1 ) }六、compiler-sfc:.vue 文件是怎么来的?
<template /> <script setup /> <style scoped />compiler-sfc 做的事:
- 拆块
- script setup → 普通 setup
- CSS scopeId 注入
- template 交给 compiler-dom
七、为什么 template 性能比 JSX 更稳定?
一句话总结:JSX 直接写 render 函数,绕过了 compiler(绕过了 PatchFlag、BlockTree、精准 diff)。