前言 为啥想学习源码?
在这个AI触手可及的时代,解决问题变得前所未有的简单------提问、复制、粘贴、完成。作为CRUD开发者,我们熟练地搬运代码,却很少追问它们为何这样工作。久而久之,技术变成了黑盒,好奇心也在重复业务中逐渐褪去。
一次偶然的机会,接触到远方 os的源码课程,开始学习Vue3的源码实现。希望把这段时间的疑问和收获整理成笔记,作为自己这段学习旅程的一份纪念。
环境搭建
Vue3 源码本身就是采用 Monorepo 架构管理,所有核心模块(如 reactivity、runtime-core、compiler-core 等)都放在 packages 目录下。
核心配置
- Monorepo:单仓库管理多个子包,能更方便地管理模块间的依赖、统一版本
- pnpm workspace :pnpm 内置的 Monorepo 工具,特点是子包本地软链、命令轻量高效。
初始化项目
- 本地创建一个Vue3文件夹,执行
pnpm init - 新建
pnpm workspace.yaml
yaml
packages:
- "packages/*"
- 创建
packages目录统一管理子项 - 执行
pnpm install typescript -D -w, 安装到根目录 - 初始化
ts配置,执行npx tsc --init
json
// 配置
{
"compilerOptions": {
// 编译目标:使用最新的 ECMAScript 版本
// 生成的 JavaScript 代码会包含最新的 ES 特性
"target": "ESNext",
// 模块系统:使用 ES 模块格式(import/export)
// 适用于现代浏览器和打包工具
"module": "ESNext",
// 模块解析策略:使用 Node.js 风格的模块解析
// 会在 node_modules 中查找依赖
"moduleResolution": "node",
// 输出目录:编译后的 JavaScript 文件将放在 dist 文件夹中
"outDir": "dist",
// 允许导入 JSON 文件作为模块
"resolveJsonModule": true,
// 严格模式:设置为 false,关闭所有严格类型检查
// 提供更宽松的类型检查,适合快速开发
"strict": false,
// 包含的库文件:ESNext(最新 ES 特性)和 DOM(浏览器 API)
"lib": ["ESNext", "DOM"]
}
}
- 根目录下新建一个
scripts/dev.js
js
/**
* 打包开发环境
*
* node script/dev.js --format or cjs
*/
import { parseArgs } from 'node:util'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import esbuild from 'esbuild'
import { createRequire } from 'node:module'
/**
* 解析命令行参数
*/
const { values: { format }, positionals } = parseArgs({
allowPositionals: true,
options: {
format: {
type: 'string',
short: 'f',
default: 'esm',
},
},
})
// 创建 esm 的 __filename
const __filename = fileURLToPath(import.meta.url)
// 创建 esm 的 __dirname
const __dirname = dirname(__filename)
const require = createRequire(import.meta.url)
const target = positionals.length ? positionals[0] : 'vue'
// 构建入口文件路径:../packages/包名/src/index.ts
const entry = resolve(__dirname, `../packages/${target}/src/index.ts`)
/**
* --format cjs or esm
* 构建输出文件路径:../packages/包名/dist/包名.格式index.js
* 例如:packages/vue/dist/vue.cjsindex.js 或 packages/vue/dist/vue.esmindex.js
*/
const outfile = resolve(
__dirname,
`../packages/${target}/dist/${target}.${format}index.js`
)
// 读取目标包的package.json文件
const pkg = require(`../packages/${target}/package.json`)
// 使用esbuild创建构建上下文并启动监听模式
esbuild.context({
entryPoints: [entry], // 指定入口文件
outfile, // 指定输出文件
format, // 打包格式:'cjs' 或 'esm'
platform: format === 'cjs' ? 'node' : 'browser', // 根据格式选择平台:cjs用node,esm用browser
sourcemap: true, // 生成sourcemap文件,便于调试
bundle: true, // 将依赖打包到一个文件中
globalName: pkg.buildOptions?.name // 从package.json中读取全局变量名(用于UMD格式)
}).then(ctx => ctx.watch()) // 启动监听模式,文件变化时自动重新构建
packages目录新建reactivity、shared、vue文件夹reactivity:响应式模块shared:工具函数模块vue:vue核心包
json
// package.json
{
"name": "@vue/reactivity",
"version": "1.0.0",
"description": "响应式模块",
"main": "iist/reactivity.cjs.js",
"module": "dist/reactivity.esm.js",
"files": [
"index.js",
"dist"
],
"sideEffects": false,
"buildOptions": {
"name": "VueReactivity",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
}
}
json
// package.json
{
"name": "@vue/shared",
"version": "1.0.0",
"description": "工具函数",
"main": "dist/shared.cjs.js",
"module": "dist/shared.esm.js",
"files": [
"index.js",
"dist"
],
"sideEffects": false,
"buildOptions": {
"name": "VueShared",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
}
}
json
{
"name": "vue",
"version": "1.0.0",
"description": "vue核心包",
"main": "dist/vue.cjs.js",
"module": "dist/vue.esm.js",
"files": [
"dist"
],
"sideEffects": false,
"buildOptions": {
"name": "Vue",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
}
}
- 根目录
package.json添加node scripts/dev.js reactivity --format cjs
json
{
"name": "vue3源码",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"dev": "node scripts/dev.js reactivity --format cjs"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^24.3.0",
"esbuild": "^0.25.9",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
},
"dependencies": {
"vue": "^3.5.21"
}
}
验证输出
- 新建
reactivity/src/index.ts
js
// 测试函数
export function fn(a,b) {
return a + b
}
- 执行
pnpm dev,在reactivity/dist目录输出如下

到这一步,项目搭建完成!
- 项目结构
md
Vue3/
├── node_modules/ # Node.js 依赖模块
├── packages/ # 多包工作区
│ ├── reactivity/ # 响应式系统包
│ │ ├── dist/ # 构建输出目录
│ │ ├── src/ # 源代码目录
│ │ │ └── index.ts # 响应式系统入口文件
│ │ └── package.json # reactivity 包配置
│ ├── shared/ # 共享工具包
│ │ ├── src/ # 共享代码源代码
│ │ └── package.json # shared 包配置
│ └── vue/ # Vue 主包
│ ├── src/ # Vue 源代码
│ └── package.json # vue 包配置
├── scripts/ # 构建脚本目录
│ ├── dev.js # 开发构建脚本
│ └── .prettierrc # Prettier 代码格式化配置
├── package.json # 项目根配置
├── pnpm-lock.yaml # pnpm 依赖锁文件
├── pnpm-workspace.yaml # pnpm 工作区配置
├── tsconfig.json # TypeScript 配置
├── External Libraries/ # 外部库(IDE 生成)
└── Scratches and Consoles/ # 临时文件和终端(IDE 功能)
相关链接:
(注:本文为学习笔记,如有理解不当之处,欢迎指正交流。)