zhangdizhangdi

Vue3 源码分析

环境准备

版本:

  • vue: 3.5.17
  • vue-router: 4.5.1
.
├── pnpm-workspace.yaml
└── debug/
    └── vue3-source/
bash
git clone git@github.com:vuejs/core.git debug/vue3-source # debug/vue3-source 本地文件夹名字
cd vue3-source
pnpm i
报错

root 下执行 pnpm i 不行
ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC  No catalog entry ‘@babel/parser’ was found for catalog ‘default’.

dev 与 build

dev

  • -p/--prod
  • -f/--format
  • -i/--inline:将依赖代码直接打包进输出文件中
    • -if esm-bundler 打包的文件 3w+ 行
    • -f esm-bundler 70+ 行
json
// vue package.json scripts
"dev": "node scripts/dev.js",
/** 生成两个文件:
- packages/vue/dist/vue.global.js
- packages/vue/dist/vue.global.js.map
**/

"dev": "node scripts/dev.js -if esm-bundler -p",
/**  生成的是
- packages/vue/dist/vue.global.prod.js
- packages/vue/dist/vue.global.prod.js.map

此时 __DEV__=false ,且此文件不是压缩的
**/

build 输出文件类型

json
"build": "node scripts/build.js",
"build": "node scripts/build.js -s", // -s 或者 --sourceMap ,可以生成 sourcemap 文件
前缀 含义
.prod.js 生产环境构建,压缩优化过 ,不包含 debugger
.js 开发环境构建,未压缩,__DEV__=true,包含 debugger
.global UMD 格式,可直接通过 <script> 引入
.esm-browser ESM 格式,用于浏览器原生模块加载
.esm-bundler ESM 格式,用于打包工具(如 Vite/Webpack)
.cjs CommonJS 格式,用于 Node.js 环境
.runtime 表示不带编译器(模板编译功能)
  1. vue.global.js / vue.global.prod.js

    • 类型:UMD(Universal Module Definition)
    • 用途:
      • 用于通过 <script> 标签直接在浏览器中引入 Vue。
    • 特点:
      • 包含完整的 Vue 运行时和编译器(可以编译模板字符串)。
      • 会挂载全局变量 Vue。
  2. vue.runtime.global.js / vue.runtime.global.prod.js

    • 类型:UMD
    • 用途:
      • 仅包含运行时版本(Runtime-only),不包含模板编译器。
    • 适用场景:
      • 配合构建工具(如 Vite/Webpack)使用,模板在构建时已预编译为 render 函数。
    • 注意:
      • 如果你尝试传入字符串模板(如 template: '<div>...</div>'),会抛出错误。
  3. vue.esm-browser.js / vue.esm-browser.prod.js

    • 类型:ES Module(ESM)
    • 用途:
      • 用于现代浏览器原生支持 ES 模块的环境。
      • 支持通过 <script type="module"> 引入。
    • 特点:
      • 可以在浏览器中直接使用 import 语法。
      • 包含完整 Vue 功能(运行时 + 编译器)。
  4. vue.runtime.esm-browser.prod.js / vue.runtime.esm-browser.js

    • 类型:ESM
    • 用途:
      • 浏览器端使用的运行时版本(无编译器)。
    • 适用场景:
      • 与构建工具配合使用,模板已在构建阶段编译为 render 函数。
  5. vue.esm-bundler.js

    • 类型:ES Module
    • 用途:
      • 专为打包工具(如 Webpack、Rollup、Vite)设计。
      • 不包含编译器(默认是 runtime-only),但可以通过配置启用。
    • 特点:
      • 使用打包工具时自动选择合适的构建版本(如 dev/prod)。
      • 支持 Tree-shaking。
  6. vue.runtime.esm-bundler.js

    • 类型:ESM
    • 用途:
      • 同上,但明确为 runtime-only 版本。
    • 建议使用场景:
      • 在构建工具中使用,并确保模板已预编译。
  7. vue.cjs.prod.js / vue.cjs.js

    • 类型:CommonJS
    • 用途:
      • 用于 Node.js 环境或旧版打包工具(如 Browserify)。

1. html文件调试

  1. 创建html文件
  2. 源文件中相应位置加debugger打断点
  3. 浏览器调试
html
<!-- packages/vue/examples/zd/createApp.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>createApp</title>
    <script src="../../dist/vue.global.prod.js"></script>
  </head>
  <body>
    <div id="app">
      <div>{{ msg }}</div>
    </div>

    <script>
      const { createApp } = Vue
      debugger
      createApp({
        setup() {
          debugger
          return {
            msg: 'Hello Vue.js',
          }
        },
      }).mount('#app')
    </script>
  </body>
</html>

2. vue项目中调试

创建vue项目

bash
npm create vue@latest
cd vue3-demo
npm install
npm run dev
json
// 根目录下的 package.json
"dev:vue3-demo": "pnpm -F vue3-source -F vue3-demo run dev"
ts
// vite.config.ts
export default defineConfig({
  resolve: {
    alias: {
      vue: fileURLToPath(
        new URL(
          '../vue3-source/packages/vue/dist/vue.esm-bundler.prod.js',
          import.meta.url,
        ),
      ),
      'vue-router': fileURLToPath(
        new URL(
          '../vue-router/packages/router/dist/vue-router.esm-bundler.js',
          import.meta.url,
        ),
      ),
    },
  },
  build: {
    sourcemap: true,
  },
})

只引用本地安装的 vue 文件,vue-router 切换页面无法生效,所以:
像 clone、build vue 一样,clone、build,在 demo 的vite.config.ts 中添加 alias 指向本地的文件

Source Map

source map 文件(.map)中记录了 编译后的代码行号与原始源代码行号之间的映射关系,浏览器通过这个映射关系找到并展示原始源码。

json
{
  "version": 3,
  "file": "app.min.js",
  "sourceRoot": "",
  "sources": [
    "webpack://project/src/index.js",
    "webpack://project/src/utils.js"
  ],
  "names": ["button", "add", "console"],
  "mappings": "AAAAA,QAAQC,MAAMC,QAAQC,MAAMC,SAASC,CAAC;;...",
  "sourcesContent": ["源码内容1", "源码内容2"]
}
  • version source map 版本
  • file 生成的打包文件名
  • sources 所有原始源文件路径
  • names 源码中变量、函数名(可选)
  • mappings base64 编码的映射信息,记录了打包文件与源码的对应关系
  • sourcesContent 源文件内容(可选),调试时浏览器可以直接展示

浏览器查找源码的优先级如下:

  • sourcesContent(如果存在,直接展示)
  • sources + sourceRoot(拼接出路径,尝试从服务器加载)
  • 通过 devtools 网络面板加载源文件(如 Chrome DevTools)

Chrome Sources

Chrome Sources调试按钮
图:Chrome Sources调试按钮

  1. Resume/Pause script excution
    • 跳到下一个断点位置/继续执行脚本
  2. Step over next function call
    • 当前行若有函数调用,不会进入该函数
  3. Step into next function call
    • 当前行若有函数调用,则会进入该函数
  4. Step out of current function
    • 继续执行当前函数内的剩余代码,并暂停在调用当前函数的下一行代码处
  5. Step

遇到的问题

git origin 问题

clone 到本地后,origin 指向的远程 vue 的仓库地址就不是我的,git add 报错。

报错

hint: You’ve added another git repository inside your current repository.

bash
rm -rf debug/vue3-source/.git # 删除子仓库的 .git 目录

compiler 编译器

  • compiler-dom
  • compiler-core
  • compiler-ssr
  • compiler-sfc

compiler-sfc

compiler-sfc 是 Vue 的单文件组件(Single File Component, SFC)编译器,负责将 .vue 文件编译成可以在浏览器中运行的 JavaScript 代码。

  • 解析(Parse):将 .vue 文件解析成结构化的描述对象
  • 编译模板(Compile Template):将 <template> 部分编译成渲染函数
  • 编译脚本(Compile Script):处理 <script><script setup>
  • 编译样式(Compile Style):处理 <style> 部分,包括作用域样式

@vitejs/plugin-vue

工作流程:

  • .vue 文件请求
  • Vite 开发服务器
  • @vitejs/plugin-vue 拦截处理
  • 调用 @vue/compiler-sfc
    • parse
    • compileScript
    • compileTemplate
    • compileStyle
  • 生成可执行的 JavaScript 模块
  • 浏览器加载执行