Vue3 源码

Vue3 源码课程简介与调试环境搭建

了解为什么要读 Vue3 源码,掌握 vuejs/core 仓库的目录结构与本地调试环境的搭建方法。

🎯 引言

这是「Vue3 源码」课程的第一篇。这门课不讲 API 怎么用,而是一起打开 Vue3 的源代码,看看你每天调用的 reactiverefwatchEffect 背后到底发生了什么。别担心跟不上,源码里绕口的概念我们都会翻译成大白话,一次只讲一个点。

学完本篇,你将能独立搭建一套 Vue3 源码的本地调试环境,能在浏览器里对源码打断点、单步跟踪,并且对 vuejs/core 仓库的目录结构有一张清晰的地图,知道去哪一层找你关心的问题。


🧱 为什么要读 Vue3 源码

读源码要花时间,值不值?这一节先回答这个问题。结合日常开发,收益主要在这三件事上。

排查问题更有方向。 你一定遇到过这种情况:改了一个对象的属性,视图却不更新;或者 watch 的回调莫名其妙多触发了一次。只熟悉 API 用法时,这类问题只能靠猜和试。读过响应式原理后,你会知道这些「怪现象」背后的机制,排查时就有明确方向。

写出更合理的代码。 比如理解了数据更新是「攒一批再统一渲染」之后,你就明白为什么连续多次赋值页面只刷新一次。这些不是背下来的规范,而是知道原理后的自然选择。

理解 API 的设计动机。 Vue3 的很多设计(比如 ref 为什么要 .value)初看有些别扭,读懂源码后会明白它们是为了解决什么具体问题而存在的。这种理解也能迁移到其他框架的学习上。

读源码不是为了面试背题,也不是要把每个函数都看懂。带着具体问题去读,读懂主干逻辑就足够受益了。

✨ 版本说明

动手之前,先交代清楚我们读的是哪个版本、放在哪里。本课程基于 Vue 3.5.x 的源码讲解,调试环境为 Node.js v22 + pnpm

Vue3 的源码放在 GitHub 的 vuejs/core 仓库里。打开仓库你会发现它不是一个项目,而是很多个独立的小项目放在一起,这种组织方式叫 monorepo(单仓库多包):响应式、渲染、编译等功能各自是一个独立的包,由 pnpm 统一管理。

这种拆分对读源码很友好:想看响应式,直接进 reactivity 这个包,不用在 Vue2 那样的大仓库里跨目录翻找。如果你之前读过 Vue2 源码课程,会发现核心思想(响应式、虚拟 DOM、模板编译)是相通的,这门课会侧重 Vue3 的变化点。


🧭 源码目录地图

版本清楚了,接下来的疑问是:这么多文件夹,我想看的东西在哪个里面?打开 vuejs/core 仓库的 packages 目录,你会看到按职责拆分的各个包。下面这张目录地图标出了每个包的关键文件,点开文件就能看到它的职责,后面每篇都会深入其中的一部分:

每个包的职责也可以用一张表速览:

目录职责
packages/vueVue 的入口组装,把运行时和编译器打包成你在浏览器里用的 Vue
packages/runtime-dom浏览器平台渲染:DOM 操作、事件绑定、createApp 的 web 版本
packages/runtime-core平台无关的运行时核心:组件实例、虚拟 DOM、调度器
packages/reactivity响应式系统:reactive / ref / effect / computed 都在这个包
packages/compiler-core平台无关的编译器:把模板解析成 AST,再生成 render 函数代码
packages/compiler-dom浏览器端编译:在 compiler-core 之上补充 DOM 相关的处理
packages/shared各包共享的工具函数:类型判断、字符串处理等

其中 runtime-corereactivity 是本课程的主战场:前者管组件和渲染,后者管整个响应式系统,其他包知道职责即可。最后把主线串起来:你写的模板先由 compiler(编译器) 转成 render 函数,runtime-core(运行时核心) 执行 render 函数把页面渲染出来,数据变化时由 reactivity(响应式系统) 通知页面重新渲染。后面每篇都会深入这条主线上的一个环节。


🛠 调试环境搭建

光说不练效果有限。这一节我们解决一个很实际的问题:怎么在自己电脑上把源码跑起来、还能打断点?整个过程只需要几分钟,之后每篇文章你都可以在真实的源码里验证。

本课程在 Node.js v22 + pnpm 环境下验证通过。先把源码拉下来并切换到 v3.5.13:

# 克隆仓库(本地 Node.js 版本:v22)
git clone https://github.com/vuejs/core.git vue3-source
cd vue3-source

# 切换到 v3.5.13,保证你看到的代码和课程一致
git checkout v3.5.13

# 安装依赖(monorepo 必须用 pnpm,包版本以仓库 pnpm-lock.yaml 为准)
pnpm install
为什么必须用 pnpm 而不是 npm?vuejs/core 是 pnpm monorepo,内部各包通过 workspace: 协议互相引用(可以简单理解为「从本仓库内部找依赖」),用 npm 安装会无法识别。
monorepo 仓库依赖较多,如果遇到依赖安装报错,可以试试加 --legacy-peer-deps 之类的宽松参数,不影响后续调试。

安装完成后,构建一份带 sourcemap 的开发版本:

# 构建开发版 vue.js(带完整警告和 sourcemap,适合调试)
pnpm run dev

这里出现一个新名词 sourcemap(源码映射)。构建会把许多源码文件打包合并成一个文件,代码位置全都对不上了。sourcemap 就像一张「对照表」,浏览器靠它把打包后的代码还原回原始源码,你打断点时看到的就是源码文件本身。

构建完成后,产物在 packages/vue/dist/vue.global.js。接着在 packages/vue/examples 目录下新建一个调试页面,比如 packages/vue/examples/debug/01-hello.html

01-hello.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <title>Vue3 源码调试</title>
</head>
<body>
    <div id="app"></div>

    <script src="../../dist/vue.global.js"></script>
    <script>
        const { createApp, ref } = Vue;

        const app = createApp({
            setup() {
                const count = ref(0);

                // 两秒后修改数据,方便观察更新流程
                setTimeout(() => {
                    // debugger 是 JS 自带的关键字:打开开发者工具时,代码执行到这一行会自动暂停
                    debugger;
                    count.value++;
                }, 2000);

                return { count };
            },
            template: `<div>count: {{ count }}</div>`,
        });

        app.mount('#app');
    </script>
</body>
</html>
为什么不用平时熟悉的 <script setup>?它需要构建工具参与编译,而这个纯 HTML 调试页没有编译环节,直接手写 setup() 函数反而更贴近源码真实处理的逻辑。

用浏览器直接打开这个 HTML 文件,打开开发者工具(F12),页面会在 debugger 处自动停住。然后你就可以:

  1. 在 Sources 面板中找到 packages/reactivity/src/ref.ts,sourcemap 已经把构建产物映射回了源码。
  2. ReactiveEffecttriggerRefValue 等函数里打上断点(点一下行号即可)。
  3. 单步执行,观察 count.value 修改后触发的调用链。

这就是你未来读源码的标准动作:找到关键函数 → 打断点 → 单步看数据和调用栈

pnpm run dev 构建的是带 sourcemap 的开发版,适合调试。生产构建(pnpm run build)会压缩代码、去掉警告,不适合学习源码。

💡 阅读方法建议

环境搭好了,怎么读才不迷路?这一节分享几条实际读源码时比较有用的习惯。

  1. 带着具体问题去读。 不要从第一行开始通读。比如「改了 ref.value,视图是怎么更新的?」带着这个问题,你自然会找到对应的那条函数调用链。
  2. 从入口往下追。 先找到 createApp 的入口(packages/runtime-dom/src/index.ts),再顺着挂载、渲染的流程一层层深入,比漫无目的地翻文件高效得多。
  3. 配合手写迷你实现验证。 读到一个机制后,自己动手写一个几十行的简化版(比如迷你响应式),能写对才是真的懂了。本课程多篇都会安排这样的练习。
  4. 跳过 TS 类型与边缘分支。 Vue3 源码是 TypeScript 写的,也就是给变量和函数标注了类型信息,阅读时可以跳过不看。课程中的源码引用也做了教学简化,去掉了类型注解和非核心分支。

🗺 课程路线图

环境和方法都齐了,最后看一眼完整路线。本课程共 8 篇,按「先响应式、再运行时、最后编译」的顺序展开:

  1. 简介与环境搭建(本篇):目录地图与调试环境。
  2. createApp 与挂载流程:从 createApp 到组件渲染到页面上,发生了什么。
  3. Proxy 响应式原理reactive 如何拦截你对数据的读写。
  4. effect 与依赖收集触发:Vue 如何记住「谁用到了这个数据」,数据变了又如何通知。
  5. ref 与 computed 原理.value 的拦截机制与 computed 的懒计算。
  6. 调度器与 nextTick:批量更新、异步排队是怎么实现的。
  7. 虚拟 DOM 与 patch:页面更新时,Vue 如何只改动变化的部分。
  8. 模板编译:模板 → AST → render 函数的三段式流程。
本课程假设你已经熟悉 Vue3 的日常使用(Composition API、<script setup>、响应式 API)。如果还没系统学过,建议先完成 Vue3 使用课程。读过 Vue2 源码课程 更佳,可以对比理解两代的差异。

🧾 小节总结

  • 读 Vue3 源码的价值在于:排查问题更有方向、写出更合理的代码、理解 API 设计动机。
  • 课程基于 Vue 3.5.x,源码在 vuejs/core 仓库,是 pnpm 管理的 monorepo(一个仓库里放多个独立包),调试环境为 Node.js v22 + pnpm。
  • 源码目录中,runtime-corereactivity 是主线,其他包按需了解即可。
  • 调试环境三步走:克隆仓库并切到 v3.5.13 → pnpm run dev 构建开发版 → 在 packages/vue/examples 里写页面用浏览器断点调试(sourcemap 会让断点停在原始源码上)。
  • 读源码的有效方法是:带着具体问题、从入口往下追、配合手写迷你实现验证、跳过 TS 类型与边缘分支。

❓ 知识问答

Q1:为什么 Vue3 源码要拆成多个 package?

A:拆包后每个模块职责清晰、边界明确。比如响应式系统独立成 reactivity 包后,阅读时只需专注这一个包,不用关心其他部分的实现。

Q2:我不会用浏览器的断点调试,影响学习吗?

A:不影响读文章本身,但建议花几分钟熟悉 Chrome DevTools 的 Sources 面板,学会打断点和单步执行。这是读源码的基本工具,之后会频繁用到。

Q3:pnpm run devpnpm run build 有什么区别?

A:pnpm run dev 构建带 sourcemap 的未压缩版本,保留完整警告信息,适合调试学习。pnpm run build 构建压缩后的生产版本,去掉了警告,不适合读源码。

Q4:课程中引用的源码和仓库里的代码完全一样吗?

A:做了教学简化。去掉了 TypeScript 类型注解和一些边缘分支,保留核心逻辑。每处引用都会标注「简化自」哪个文件,方便你对照原始代码。

Q5:读过 Vue2 源码课程,这门课会重复吗?

A:核心思想(响应式、虚拟 DOM、模板编译)是相通的,但实现差异很大。这门课侧重这些变化点,相同的地方只做简要回顾。


🧪 小练习

搭建好环境后,试着完成这个练习:在 packages/vue/examples/debug/01-hello.html 中,给页面再加一个 ref 字段 message 并渲染到页面上,然后在浏览器断点中观察修改 .value 时触发的调用链。

const { createApp, ref } = Vue;

const app = createApp({
    setup() {
        const count = ref(0);
        // 请在这里编写代码:声明一个名为 message 的 ref

        setTimeout(() => {
            debugger;
            // 请在这里编写代码:修改 message 的值
        }, 2000);

        return { count };
    },
    // 请在这里编写代码:把 message 也渲染到模板里
    template: `<div>count: {{ count }}</div>`,
});

完成后在 Sources 面板中找到 packages/reactivity/src/ref.ts,在 triggerRefValue 处打断点,观察一次修改触发了哪些函数调用。


🎉 恭喜你已经搭好 Vue3 源码的调试环境,也有了完整的课程地图!下一篇我们从 createApp 开始,一步步跟踪应用从创建到挂载的完整流程。