主题
搭建项目架构
本节对标 Vue 3 源码仓库
vuejs/core的整体架构设计
项目定位
我们要实现一个 mini 版的 Vue 3,目标是覆盖 Vue 3 的核心原理,而不是实现一个功能完整的框架。
项目特点:
- 库,而不是业务项目
- Monorepo 管理多个子包
- TypeScript 编写
- 希望打包产物可读性高
项目结构设计
Vue 3 源码采用 Monorepo 架构,将不同职责拆分为独立的 package:
mini-vue/
├── packages/
│ ├── reactivity/ # 响应式系统
│ │ └── src/
│ │ ├── reactive.ts
│ │ ├── effect.ts
│ │ ├── ref.ts
│ │ ├── computed.ts
│ │ └── index.ts
│ ├── runtime-core/ # 运行时核心
│ │ └── src/
│ │ ├── vnode.ts
│ │ ├── renderer.ts
│ │ ├── component.ts
│ │ ├── scheduler.ts
│ │ └── index.ts
│ ├── runtime-dom/ # DOM 平台渲染
│ │ └── src/
│ │ ├── nodeOps.ts
│ │ ├── patchProp.ts
│ │ └── index.ts
│ ├── compiler-core/ # 编译器核心
│ │ └── src/
│ │ ├── parse.ts
│ │ ├── transform.ts
│ │ ├── codegen.ts
│ │ └── index.ts
│ └── shared/ # 公共工具
│ └── src/
│ └── index.ts
├── pnpm-workspace.yaml
├── tsconfig.json
├── rollup.config.js
└── package.json为什么要这样拆分?
Vue 3 源码最大的设计亮点之一是 模块解耦:
reactivity可以独立于 Vue 使用(比如在 Node.js 中单独用响应式能力)runtime-core不依赖具体平台(DOM / Canvas / Native 都可以)runtime-dom是 DOM 平台的具体实现compiler-core负责模板到 render 函数的编译,与运行时完全分离
这种设计和 React 的 react + react-dom + react-reconciler 分离思想一致。
Monorepo 技术选型
pnpm workspace
与 mini-react 项目一致,使用 pnpm workspace 管理 monorepo。
初始化:
bash
npm install -g pnpm
pnpm init创建 pnpm-workspace.yaml:
yaml
packages:
- 'packages/*' # 声明所有子包都在 packages 目录下,pnpm 会自动识别并管理它们之间的依赖关系各子包的 package.json
以 reactivity 为例:
json
{
"name": "@mini-vue/reactivity", // 包名使用 @mini-vue 作用域,与 monorepo 中其他包保持一致的命名空间
"version": "0.0.1",
"main": "index.js", // CommonJS 入口,供 require() 方式引用
"module": "dist/reactivity.esm-bundler.js", // ESM 入口,供 import 方式引用,打包工具会优先使用此字段
"types": "dist/reactivity.d.ts", // TypeScript 类型声明文件入口
"scripts": {
"build": "rollup -c" // 使用 rollup 进行打包,-c 表示读取 rollup.config.js 配置文件
}
}shared 包作为公共依赖:
json
{
"name": "@mini-vue/shared",
"version": "0.0.1",
"main": "src/index.ts" // 直接指向源码入口,因为 shared 包在开发阶段不需要单独打包,其他子包会直接引用源码
}在 reactivity 中引用 shared:
bash
pnpm add @mini-vue/shared --filter @mini-vue/reactivity --workspace开发规范
TypeScript 配置
json
{
"compilerOptions": {
"target": "ESNext", // 编译目标为最新的 ES 标准,保留所有现代语法特性
"module": "ESNext", // 模块系统使用 ESM,支持 import/export 语法
"lib": ["ESNext", "DOM"], // 包含 ESNext 和 DOM 的类型定义,因为我们需要操作浏览器 DOM API
"moduleResolution": "Node", // 使用 Node.js 风格的模块解析策略(查找 node_modules、index 文件等)
"strict": true, // 启用所有严格类型检查选项,提高代码质量
"sourceMap": true, // 生成 source map 文件,方便调试时定位到 TypeScript 源码
"resolveJsonModule": true, // 允许导入 JSON 文件并自动推断类型
"esModuleInterop": true, // 允许 ESM 和 CJS 模块之间的互操作,支持 import x from 'cjs-module' 语法
"noUnusedLocals": true, // 报告未使用的局部变量,减少冗余代码
"noUnusedParameters": true, // 报告未使用的函数参数
"noImplicitReturns": false, // 允许函数中不是所有路径都有返回值(Vue 源码中部分函数如此)
"skipLibCheck": true, // 跳过第三方库的类型检查,加快编译速度
"baseUrl": ".", // 设置项目根目录为基准路径,配合 paths 使用
"paths": {
"@mini-vue/*": ["packages/*/src"] // 路径别名映射,使 @mini-vue/reactivity 等导入路径解析到对应子包的 src 目录
}
},
"include": ["packages/*/src"] // 只编译 packages 下各子包的 src 目录中的 TypeScript 文件
}paths 配置是关键,它让我们可以用 @mini-vue/reactivity 这样的路径引用其他子包。
测试框架
使用 vitest,与 mini-react 项目保持一致:
bash
pnpm add vitest -D -w配置 vitest.config.ts:
ts
// 从 vitest 中导入 defineConfig,用于获得配置的类型提示和智能补全
import { defineConfig } from 'vitest/config'
// 导入 path 模块,用于处理文件路径
import path from 'path'
export default defineConfig({
test: {
globals: true, // 全局注入 describe/it/expect 等测试 API,无需每个测试文件手动导入
},
resolve: {
// 配置模块别名,使测试中 import '@mini-vue/xxx' 能正确解析到对应子包的源码目录
alias: {
'@mini-vue/reactivity': path.resolve(__dirname, 'packages/reactivity/src'),
'@mini-vue/runtime-core': path.resolve(__dirname, 'packages/runtime-core/src'),
'@mini-vue/runtime-dom': path.resolve(__dirname, 'packages/runtime-dom/src'),
'@mini-vue/compiler-core': path.resolve(__dirname, 'packages/compiler-core/src'),
'@mini-vue/shared': path.resolve(__dirname, 'packages/shared/src'),
},
},
})打包工具
同样选择 rollup,原因与 mini-react 一致:
- 库项目,不是业务项目
- 打包产物可读性高
- 原生支持 ESM
- Vue 3 源码也使用 rollup
安装:
bash
pnpm add rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-commonjs -D -w基础 rollup 配置:
js
// 导入 rollup 插件:TypeScript 编译支持
import typescript from 'rollup-plugin-typescript2'
// 导入 rollup 插件:解析 node_modules 中的第三方模块
import resolve from '@rollup/plugin-node-resolve'
// 导入 rollup 插件:将 CommonJS 模块转换为 ES 模块,使其能被 rollup 处理
import commonjs from '@rollup/plugin-commonjs'
export default {
// 打包入口文件,指定 reactivity 包的源码入口
input: 'packages/reactivity/src/index.ts',
output: [
{
file: 'packages/reactivity/dist/reactivity.cjs.js',
format: 'cjs', // 输出 CommonJS 格式,供 Node.js 环境下 require() 使用
},
{
file: 'packages/reactivity/dist/reactivity.esm-bundler.js',
format: 'es', // 输出 ES Module 格式,供现代打包工具(webpack/vite)使用
},
],
plugins: [
resolve(), // 解析第三方依赖
commonjs(), // 转换 CJS 模块为 ESM
typescript(), // 编译 TypeScript 为 JavaScript
],
}本节小结
本节完成了:
- 项目结构设计 — 采用 Monorepo,拆分为 5 个核心子包
- 开发规范 — TypeScript + vitest + rollup
- 模块依赖关系 — shared → reactivity → runtime-core → runtime-dom / compiler-core
下一节开始进入 Vue 3 最核心的模块 — 响应式系统。