Skip to content

Vue 底层库与编译器生态:@vue/compiler-sfc、runtime、reactivity 分别在做什么

很多人学 Vue 时,最熟的是:

  1. ref
  2. reactive
  3. computed
  4. watch
  5. defineProps

但一旦看到这些包名,就会开始发懵:

  1. @vue/reactivity
  2. @vue/runtime-core
  3. @vue/runtime-dom
  4. @vue/compiler-core
  5. @vue/compiler-dom
  6. @vue/compiler-sfc

这时候最容易出现的误解是:Vue 不就是一个框架吗,为什么还要拆这么多包?

更贴近实际的情况是:Vue3 不只是一个“单块框架”,而是一套被拆成多个职责模块的运行时与编译器生态。

1. 为什么 Vue 要拆成这么多底层包

如果把 Vue 只看成“页面模板 + 响应式 API”,很多东西会连不起来。

实际上一个 Vue 应用至少要完成这几类工作:

  1. 管状态和依赖追踪
  2. 创建组件实例
  3. 渲染虚拟 DOM
  4. 把虚拟 DOM 挂到浏览器真实 DOM
  5. 把模板编译成渲染函数
  6. .vue 单文件组件拆开处理

这些事情如果全塞进一个大包里,确实也能工作,但:

  1. 职责不清
  2. 复用性差
  3. 工具链对接不自然
  4. 不利于不同运行时复用核心能力

所以 Vue3 的一个重要工程化方向就是:把响应式、运行时、平台适配层、编译器、单文件组件处理拆成职责更清晰的包。

2. 先用一句话区分这几层

这几层可以分开看:

  1. @vue/reactivity:响应式核心
  2. @vue/runtime-core:与平台无关的运行时核心
  3. @vue/runtime-dom:浏览器 DOM 平台适配层
  4. @vue/compiler-core:平台无关的编译器核心
  5. @vue/compiler-dom:面向浏览器模板的编译器扩展
  6. @vue/compiler-sfc:处理 .vue 单文件组件的编译器

如果只压缩成一句话:reactivity 管数据联动,runtime 管组件运行,compiler 管模板变代码,compiler-sfc 管 .vue 文件拆解和编译。

3. @vue/reactivity 是什么

@vue/reactivity 可以看成 Vue3 响应式系统最核心的一层独立能力

它主要负责:

  1. 依赖收集
  2. 触发更新
  3. ref
  4. reactive
  5. computed
  6. effect

也就是说,当你写:

ts
const count = ref(0)
const doubled = computed(() => count.value * 2)

背后最核心的响应式逻辑,主要就是这层在提供能力。

3.1 它解决什么问题

它解决的是 状态变化后,依赖这份状态的逻辑怎么自动重新执行

这层本身并不直接关心:

  1. 组件模板长什么样
  2. DOM 怎么更新
  3. 页面怎么挂载

这里的边界要看清:@vue/reactivity 是 Vue 的响应式基础设施,但它本身不等于完整 Vue 框架。`

4. @vue/runtime-core 是什么

@vue/runtime-core 可以看成 Vue 运行时里不直接依赖浏览器平台的一层核心能力

它主要负责:

  1. 组件实例模型
  2. 组件生命周期调度
  3. 虚拟 DOM 节点组织
  4. 渲染流程的核心抽象
  5. 应用实例的一部分核心逻辑

这层最重要的意义是:它更偏框架内核,但尽量不把自己绑死在浏览器 DOM 上。

也就是说,它描述的是:

  1. 组件怎么跑
  2. vnode 怎么组织
  3. 更新怎么调度

而不是直接去操作浏览器里的真实元素。

5. @vue/runtime-dom 是什么

如果说 runtime-core 更像抽象内核,那 @vue/runtime-dom 就可以看成:

把 Vue 运行时接到浏览器 DOM 上的那一层。

它主要负责:

  1. DOM 节点创建
  2. 属性、样式、事件更新
  3. 浏览器平台挂载细节
  4. createApp 在 DOM 环境里的落地

把分工拉开看:

  1. runtime-core 更偏平台无关
  2. runtime-dom 更偏浏览器平台实现

这也是为什么 Vue3 不是只能理解成“浏览器模板框架”,而是一套:核心运行时 + 平台适配层

的设计。

6. @vue/compiler-core 是什么

@vue/compiler-core 可以看成 Vue 编译器里与具体平台无关的通用核心

它主要在做:

  1. 模板解析
  2. AST 转换
  3. 代码生成

如果换一种更容易理解的话说,它负责把:模板描述

逐步变成:渲染函数代码

但这层还尽量不去关心浏览器特有的 DOM 细节。

7. @vue/compiler-dom 是什么

@vue/compiler-dom 是建立在 compiler-core 之上的浏览器模板编译扩展。

可以看成 让 Vue 模板真正适配 DOM 场景的一层编译能力

它主要负责把浏览器模板相关语法和平台细节补进去,例如:

  1. DOM 节点相关处理
  2. 指令在 DOM 语境下的编译
  3. 浏览器平台特有优化

所以这两层的关系可以记成:

  1. compiler-core:通用编译器骨架
  2. compiler-dom:浏览器模板场景的具体扩展

8. @vue/compiler-sfc 是什么

这是你刚提到的重点,也是很多人最容易混的一个包。

@vue/compiler-sfc 可以看成 专门处理 .vue 单文件组件的一层编译器

所谓 SFC,就是 Single File Component,也就是:

vue
<template>
  <div>{{ msg }}</div>
</template>

<script setup lang="ts">
const msg = 'hello'
</script>

<style scoped>
div { color: red; }
</style>

这种把模板、脚本、样式放在一个文件里的组件形式。

8.1 它主要负责什么

@vue/compiler-sfc 最核心的职责通常包括:

  1. 解析 .vue 文件结构
  2. 拆出 templatescriptstyle
  3. 处理 script setup
  4. 生成对应的编译结果和中间信息
  5. 配合构建工具完成热更新和源码映射

这里的边界也要看清:compiler-sfc 不是单纯把模板编译一下,它是在处理 .vue 文件这种特殊文件格式。

8.2 它和 compiler-dom 的关系

很多人容易把这两个混成同一个东西。

更准确的关系是:

  1. compiler-sfc.vue 文件拆开
  2. 其中 template 部分再交给模板编译链路处理
  3. 这条模板编译链路里会用到 compiler-corecompiler-dom

也就是说:compiler-sfc 更像总入口,compiler-dom 更像模板部分的具体编译器。

如果把 .vue 文件从源码到运行时的过程单独画出来,可以这样看:

mermaid
flowchart TD
    A[App.vue] --> B[@vue/compiler-sfc 解析]
    B --> C[拆出 template]
    B --> D[拆出 script 或 script setup]
    B --> E[拆出 style]

    C --> F[@vue/compiler-dom]
    F --> G[render 函数代码]

    D --> H[组件逻辑代码]
    E --> I[样式处理链路]

    G --> J[@vue/runtime-core]
    H --> J
    J --> K[@vue/runtime-dom]
    K --> L[浏览器真实 DOM]
    I --> L

9. 一张总图看清包之间的关系

mermaid
flowchart TD
    A[.vue 单文件组件] --> B[@vue/compiler-sfc]
    B --> C[template]
    B --> D[script script setup]
    B --> E[style]
    C --> F[@vue/compiler-dom]
    F --> G[@vue/compiler-core]
    G --> H[渲染函数代码]
    H --> I[@vue/runtime-core]
    I --> J[@vue/runtime-dom]
    K[@vue/reactivity] --> I

这张图最重要的作用是把三条线拆开:

  1. .vue 文件处理线
  2. 模板编译线
  3. 运行时执行线

10. .vue 文件到底是怎么变成页面的

这是最值得系统讲清楚的一条主线。

大致过程就是这样:

  1. 你写一个 .vue 文件
  2. 构建工具调用 @vue/compiler-sfc 解析它
  3. template 被编译成渲染函数
  4. script / script setup 被转成组件逻辑代码
  5. style 被提取、作用域处理或注入
  6. 最终生成可被打包器处理的 JavaScript 模块
  7. 运行时再交给 runtime-coreruntime-dom 去创建组件、生成 vnode、更新真实 DOM

如果压缩成一句话:compiler-sfc 负责把 .vue 文件拆成运行时能理解的代码,runtime 再负责把这些代码真正跑起来。

11. vue 这个包和这些底层包是什么关系

这也是一个高频混淆点。

日常业务开发时,我们通常写的是:

ts
import { ref, reactive, createApp } from 'vue'

这里的 vue 更像一个面对应用开发者的统一入口包。

也就是说,很多底层能力最后会通过 vue 暴露给你使用。

放回原位看,这层关系是:

  1. vue 是开发者最常直接依赖的入口
  2. 底层实际能力由多个内部包协同完成

12. 为什么理解这些底层包有价值

这不是为了背 npm 包名,而是为了把很多工程问题讲通。

例如:

  1. script setup 为什么需要编译阶段支持
  2. .vue 文件为什么离不开构建工具
  3. 为什么有些能力属于运行时,有些能力属于编译时
  4. 为什么 Vue3 能做更细粒度的编译优化

如果不理解这条线,很多问题就会停留在:感觉 Vue 会自动处理。

13. 和 Vite、插件生态是什么关系

在真实工程里,你通常不是直接手动调用 @vue/compiler-sfc

更常见的情况是:

  1. Vite
  2. @vitejs/plugin-vue

这类工具在底层帮你接好了 .vue 文件处理链路。

也就是说:

  1. 业务开发者写 .vue
  2. 构建工具和插件调用 Vue 编译器生态
  3. 最终产出浏览器可运行代码

所以 compiler-sfc 这类底层包虽然平时不常直接手写调用,但它在工程里非常关键。

14. 工程里最容易踩的坑

14.1 把 Vue 理解成纯运行时框架

这样就很难理解:

  1. 模板为什么能变代码
  2. script setup 为什么能工作
  3. .vue 为什么必须配合构建工具

14.2 把 compiler-sfccompiler-dom 混为一谈

前者处理的是 .vue 文件结构,后者处理的是模板编译到 DOM 渲染函数的那条线。

14.3 把 reactivity 当成完整 Vue

它是核心能力之一,但它本身并不等于整个框架。

14.4 不区分编译时和运行时

这会让很多 Vue3 的优化点、宏能力和工具链问题看起来像黑盒。

15. 一份检查清单

当你在理解 Vue 底层生态时,至少可以问自己:

  1. 这项能力属于响应式、运行时,还是编译器
  2. 这是在 .vue 文件解析阶段发生,还是在浏览器运行阶段发生
  3. 这个包更偏平台无关,还是 DOM 平台实现
  4. 这个能力最终是通过 vue 入口暴露,还是主要给工具链使用

16. 小结

如果把这篇压缩成一句话,可以记住:Vue3 之所以能同时拥有响应式、组件运行时、模板编译和 .vue 单文件组件体验,是因为它背后并不是一个单块框架,而是一组分工明确的底层包在协同工作。

最值得先记住的分工是:

  1. @vue/reactivity 管响应式
  2. @vue/runtime-core 管运行时核心
  3. @vue/runtime-dom 管浏览器 DOM 落地
  4. @vue/compiler-core / @vue/compiler-dom 管模板编译
  5. @vue/compiler-sfc.vue 单文件组件编译

基于 VitePress 构建的个人技术笔记。