Skip to content

Monorepo

定义

Monorepo 是一种将多个项目代码存储在同一个代码仓库中的开发策略,而不是将每个项目分散到不同的代码仓库中。

主要优势

  1. 代码共享和复用
    • 更容易共享公共代码
    • 统一的依赖管理
    • 便于抽取公共模块
  2. 依赖管理
    • 统一的版本控制
    • 避免依赖冲突
    • 简化依赖更新
  3. 工程化管理
    • 统一的构建流程
    • 统一的代码规范
    • 统一的测试策略

Vue3源码目录结构

packages/
├── compiler-core/          # 编译器核心代码
├── compiler-dom/          # 浏览器平台编译器
├── compiler-sfc/          # 单文件组件编译器
├── compiler-ssr/          # 服务端渲染编译器
├── reactivity/            # 响应式系统
├── runtime-core/          # 运行时核心代码
├── runtime-dom/           # 浏览器运行时
├── runtime-test/          # 测试相关运行时
├── server-renderer/       # 服务端渲染
├── shared/               # 共享工具代码
└── vue/                  # 完整版本入口

开始搭建项目

1. 创建项目文件夹

bash
mkdir vue3-source-code && cd vue3-source-code

2. 安装pnpm

bash
# 安装 pnpm
npm install pnpm -g

安装完成后执行:

bash
pnpm -v # 打印出版本号证明安装成功

3. 初始化pnpm项目

bash
pnpm init

这会生成一个 package.json 文件。

4. 配置 pnpm workspace

vue3-source-code根目录下创建pnpm-workspace.yaml文件:

yaml
packages:
  - 'packages/*' # 所有子包存放在 packages 目录

5. 创建一个packages目录,在此目录下创建一个reactivityvue目录,作为响应式的子包。目录结构为:

vue3-source-code/
├── package.json
├── pnpm-workspace.yaml
├── packages/
│   └── reactivity/
│   └── vue/

6. 安装依赖

在 monorepo 中安装依赖和普通项目有所不同,由于每个子包都可以有自己的依赖,所以我们在安装依赖时,需要指定安装到那个包里面,比如我们要安装 typescript

在非 monorepo 项目中:

bash
pnpm install typescript -D

但是如果我们在 monorepo 项目中这样安装,会报错,错误如下:

bash
 ERR_PNPM_ADDING_TO_ROOT  Running this command will add the dependency to the workspace root, which might not be what you want - if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). If you don't want to see this warning anymore, you may set the ignore-workspace-root-check setting to true.

这个错误信息告诉我们,如果你希望将依赖添加到项目根目录中,需要添加标记 -w 或者 --workspace-root 我们根据提示安装依赖:

bash
pnpm install typescript -D -w

注:以上问题,在pnpm的10版本以上,已经不存在,优化了。 不加-w也能下载。pnpm会自动判断,自动寻找。

配置 typescript

json
{
  "compilerOptions": {
    "target": "ESNext", // 指定 ECMAScript 目标版本
    "module": "ESNext", // 指定模块代码生成规范
    "moduleResolution": "node", // 指定模块解析策略
    "outDir": "dist", // 指定编译输出的目录
    "resolveJsonModule": true, // 允许导入 JSON 文件
    "strict": false, // 关闭严格模式
    "lib": ["ESNext", "DOM"], // 指定要使用的库文件
    "paths": {
      "@vue/*": ["packages/*/src"]
    },
    "baseUrl": "./"
  }
}

配置 esbuild

在根目录下创建scripts/dev.js:

js
import { parseArgs } from 'node:util'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import esbuild from 'esbuild'
// 打包开发环境
/**
 * 解析命令行参数
 */
const {
    values: { format },
    positionals,
} = parseArgs({
    allowPositionals: true, // 位置参数
    options: {
        format: {
            type: 'string',
            short: 'f',
            default: 'esm',
            description: '打包格式',
        },
    },
})

console.log('开始打包', format, positionals)

// 创建esm的filename
const __filename = fileURLToPath(import.meta.url)
// 创建esm的__dirname
const __dirname = dirname(__filename)
// 创建require
const require = createRequire(import.meta.url)

const target = positionals.length ? positionals[0] : 'vue'

const entry = resolve(__dirname, `../packages/${target}/src/index.ts`)
console.log('打包入口文件', entry)

/**
 * -format cjs or esm
 * cjs => reactive.cjs.js
 * esm => reactive.esm.js
 */
const outfile = resolve(
    __dirname,
    `../packages/${target}/dist/${target}.${format}.js`,
)

const pkg = require(`../packages/${target}/package.json`)
console.log('pkg', pkg)
esbuild
    .context({
        entryPoints: [entry], // 入口文件
        outfile, // 输出文件
        format, // 输出格式, cjs esm iife
        platform: format === 'cjs' ? 'node' : 'browser', // 平台
        sourcemap: true, // 开启sourcemap
        bundle: true, // 把所有的依赖打包到一个文件中
        globalName: pkg.buildOptions.name, // 全局变量名
    })
    .then(ctx => {
        ctx.watch()
    })

package.json中,新增脚本命令:

json
{
    "name": "vue3-source-code",
    "version": "1.0.0",
    "description": "",
    "type": "module",
    "main": "index.js",
    "scripts": {
        "dev": "node scripts/dev.js reactivity --format esm", 
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "packageManager": "pnpm@10.12.4",
    "devDependencies": {
        "@types/node": "^24.0.10",
        "esbuild": "^0.25.6",
        "prettier": "^3.6.2",
        "typescript": "^5.8.3",
        "vue": "^3.5.17"
    }
}

Released under the MIT License.