Skip to main content

Monorepo

Monorepo(单仓多包)= 一个 git 仓库管理多个项目/包。与之对应的是 Polyrepo(多仓多包)。

# Monorepo
my-org/
├─ apps/
│ ├─ web/ # 主应用
│ └─ admin/ # 后台
└─ packages/
├─ ui/ # 组件库
├─ utils/ # 工具
└─ sdk/ # SDK
# Polyrepo
my-org/
├─ web/ # 独立仓库
├─ admin/ # 独立仓库
├─ ui/ # 独立仓库
└─ utils/ # 独立仓库

优缺点

维度MonorepoPolyrepo
代码复用✅ 内部包直接引用⚠️ 走 npm 发包,依赖链路长
依赖管理✅ 顶层统一装,节省磁盘❌ 每个仓都装一遍
跨包重构✅ 原子提交❌ 要发 PR 到多个仓
版本管理⚠️ 复杂(changesets 解决)✅ 各包独立
权限控制❌ 所有人能看所有包✅ 仓库级权限
CI 速度⚠️ 需要增量构建(turborepo)✅ 各自独立
适用规模中小团队、多包复用大厂、多业务线隔离

经验法则:包之间有 30% 以上复用率就值得 monorepo

适用场景

  • ✅ 组件库 + 业务应用(Vercel、字节、京东内部都在用)
  • ✅ 工具库 + 多个使用方
  • ✅ 同一团队维护多个紧密关联的项目
  • ❌ 业务完全独立、团队也不重叠的多产品(建议多仓)

二、主流方案对比

1. 包管理 + Workspace

方案特点推荐度
pnpm workspace硬链接节省磁盘、速度快、原生支持⭐⭐⭐⭐⭐
yarn workspacesyarn 1.x 支持;v2+ berry 改用 PnP(争议)⭐⭐⭐
npm workspacesnpm 7+ 内置支持,够用但弱⭐⭐

pnpm 是 2024+ 事实标准,monorepo 场景几乎都是它。

2. 构建编排工具

方案定位特点
Turborepo任务编排 + 远程缓存Vercel 出品、配置简单、增量构建
Nx完整工程化平台nrwl 出品、CLI 强、依赖图分析
Lerna版本管理 + 发包已被 nx 团队接管、单独用变少
Rush大型 monorepo 工具微软出品、配置复杂、企业级

2024+ 推荐pnpm + turborepo(中小团队);pnpm + nx(大型企业)。

3. 版本管理 + Changelog

方案特点
Changesets现代推荐,配置文件驱动
Lerna version老牌方案,已被 nx 整合
手动小项目勉强能用

Changesets 是当前事实标准,pnpm / nx / turborepo 都推荐配合它。

三、推荐组合

场景推荐
个人 / 小团队(< 10 人)pnpm workspace + Changesets
中小团队(10~50 人)pnpm + Turborepo + Changesets
大厂 / 多业务线pnpm + Nx + Changesets
微软系 / 企业级Rush

四、pnpm workspace 实战

1. 初始化

mkdir my-monorepo && cd my-monorepo
pnpm init

2. 配置 workspace

pnpm-workspace.yaml

packages:
- "apps/*"
- "packages/*"
- "!**/test/**"

pnpm 会自动把 apps/*packages/* 下的每个目录识别成独立的 workspace 包。

3. 目录结构

my-monorepo/
├─ apps/
│ ├─ web/ # 主应用(package.json: "web")
│ └─ admin/ # 后台(package.json: "admin")
├─ packages/
│ ├─ ui/ # 组件库(package.json: "@my/ui")
│ ├─ utils/ # 工具(package.json: "@my/utils")
│ └─ sdk/ # SDK(package.json: "@my/sdk")
├─ pnpm-workspace.yaml
├─ package.json
├─ .npmrc
└─ pnpm-lock.yaml

4. 跨包引用(workspace protocol)

apps/web/package.json

{
"dependencies": {
"@my/ui": "workspace:*",
"@my/utils": "workspace:*"
}
}
  • workspace:*始终使用工作区最新代码,不发包也能用
  • workspace:^1.0.0:发包后的版本约束(用得少)

执行 pnpm install 后,会自动在 apps/web/node_modules/@my/ui 创建符号链接指向 packages/ui

// apps/web/src/App.tsx
import { Button } from "@my/ui";

修改 packages/ui 源码,apps/web 立即生效(HMR 支持需要看工具链配置)。

5. 全局安装共享依赖

把所有包都用到的依赖(如 react)装到根目录,避免重复安装。

package.json

{
"devDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.0.0"
}
}

子包如果要覆盖版本:

// packages/ui/package.json
{
"dependencies": {
"react": "^18.3.0" // 覆盖根的版本
}
}

6. 常用命令

# 在指定包运行命令
pnpm --filter web dev
pnpm --filter @my/ui build

# 所有包执行
pnpm -r build
pnpm -r --parallel test

# 给指定包加依赖
pnpm --filter web add axios
pnpm --filter @my/ui add -D vitest

# 跑某个包及其依赖
pnpm --filter web... build # web 及其依赖包
pnpm --filter ...web build # web 及依赖它的包

五、Turborepo 实战

1. 安装

pnpm add -D turbo

2. 配置

turbo.json

{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
},
"lint": {
"outputs": []
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"clean": {
"cache": false
}
}
}

关键点:

  • dependsOn: ["^build"]:先构建所有依赖
  • outputs:构建产物路径,用于缓存
  • cache: false:不缓存的任务(dev server)

3. 根 package.json scripts

{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel",
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean && rm -rf node_modules"
}
}

4. 远程缓存(团队提速)

# 登录 Vercel
npx turbo login
# 关联项目
npx turbo link

开启远程缓存后,同一团队成员跑过的任务会命中缓存,跳过执行。

六、版本管理:Changesets

1. 初始化

pnpm add -D @changesets/cli
pnpm changeset init

2. 工作流

# 1. 改完代码后,告诉 changeset 改了哪些包
pnpm changeset
# 交互式选择:哪些包、什么类型(major/minor/patch)、写 changelog

# 2. 版本自动 bump + 写 CHANGELOG.md
pnpm changeset version

# 3. 发包
pnpm changeset publish

3. CI 集成

.github/workflows/release.yml

name: Release
on:
push:
branches: [main]

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build
- run: pnpm changeset version
- run: pnpm changeset publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: pnpm/action-setup@v4
- run: git push --follow-tags

七、避坑指南

  1. 不要让子包独立装大版本:react、vue 等核心依赖全仓统一版本,避免运行时多实例
  2. 避免循环依赖:a 依赖 b、b 依赖 a,会导致构建死循环
  3. 避免 workspace:* 发包:发包前要改回具体版本号(用 pnpm changeset version 自动处理)
  4. 锁定 Node 版本:根目录加 .nvmrc + engines,否则不同环境行为不一致
  5. 公共依赖 hoist:用 .npmrcpublic-hoist-pattern[] 把所有子包共享的工具提到根
  6. CI 用 --frozen-lockfile:保证 lock 文件不被修改
  7. 避免巨型 monorepo:单个仓超过 50 个包就该考虑拆分

八、面试高频

  • Q:Monorepo 最大的好处是什么? A:代码复用 + 跨包重构原子化。Polyrepo 改一个工具库要在多个仓提 PR,链路长;Monorepo 一次 commit 改完所有调用方。

  • Q:pnpm 为什么比 npm/yarn 适合 monorepo? A:pnpm 用硬链接 + 内容寻址存储,所有子包共享同一份依赖磁盘占用,速度比 npm/yarn 快 2~3 倍。同时 workspace:* 原生支持。

  • Q:Turborepo 怎么做到增量构建? A:基于 hash 的缓存:每次跑任务前计算输入文件 + 环境变量的 hash,命中本地/远程缓存就跳过执行。配合 Vercel 远程缓存,团队成员复用彼此的构建结果。

  • Q:Lerna 现在还有用吗? A:单独用 Lerna 的人越来越少,基本被 Changesets 取代版本管理部分。Lerna 团队(nrwl)现在主推 Nx。

  • Q:什么时候不应该上 Monorepo? A:业务完全独立 + 团队不重叠 + 跨包复用率低 + 需要细粒度权限控制。强行 monorepo 反而拖慢效率。

九、相关文档