选择 Monorepo 还是 Polyrepo(多仓库),本质上是一个工程权衡(Trade-off)问题:
Monorepo 的核心权衡公式:
用“更复杂的构建/权限治理成本” 换取 “极致的代码共享、跨模块重构效率与统一的开发体验(DX)”。
它既不是银弹,也不是越崇高越好。以下为你梳理一套架构师在做选型时的决策模型与 Trade-off 框架:
一、 什么时候【坚决选择】Monorepo?
当你的项目具备以下 4 个特征中的 2 个以上时,Monorepo 是绝对的最优解:
1. 存在强契约 / 强依赖关系(High Coupling / Shared Types)
- 典型场景:
- 主应用 + 壳子(例如我们刚才讨论的
H5 + 小程序外壳)。 - 前端 + B 端后台 + 共享 UI 库(如
apps/web+apps/admin+packages/ui)。 - 前端 + 全栈 Node.js/tRPC 后端(前端直接 import 后端的类型定义)。
- 主应用 + 壳子(例如我们刚才讨论的
- 收益:修改了一个共享类型(如 API 接口定义),所有依赖它的子应用在编译期立刻报错。再也不用靠口头通知或 Postman 文档去同步接口改动。
2. 需要频繁跨模块重构,且希望支持“原子提交(Atomic Commit)”
- 场景:你在修改公共 UI 组件库(
packages/ui)的同时,需要同步修改使用该组件的 3 个业务应用(apps/app1,apps/app2)。 - 收益:
- Polyrepo 做法:修改 UI 库 -> 提交 -> 发发布 NPM 包 -> 去 App1 升级版本 -> 去 App2 升级版本(极度繁琐,且极其容易版本漂移)。
- Monorepo 做法:在一个 Git PR 里,同时修改 UI 库和 3 个应用的代码,一次性提交、测试、上线(原子级重构)。
3. 想消除内部私有 NPM 包的维护成本(Zero NPM Publish Overhead)
- 场景:团队想提炼一些公共工具库(如
utils、hooks、design-tokens),供内部多个项目使用。 - 收益:不用搭建和维护内部 Veraccio/Nexus 私有 NPM 仓库,也不用经历
npm publish->npm update的折腾,直接利用pnpm workspace的workspace:*依赖本地源码,修改即生效。
4. 追求单命令联调体验(DX)与统一工程规范
- 场景:团队希望新员工 Clone 代码后,输入一条命令(如
pnpm dev),就能同时把前端、后台、Mock 服务全部跑起来。 - 收益:统一的
.cursorrules、ESLint、Biome、TypeScript 配置,全仓库共享,防止不同项目各自一套代码风格。
二、 什么时候【坚决不用】Monorepo?
如果在以下场景硬套 Monorepo,它会变成团队的噩梦:
1. 业务完全独立,且权限/安全隔离要求极高(Security & Access Limits)
- 场景:公司既有“核心财务结算系统”,又有外部供应商用的“营销 H5”。
- 原因:标准 Git 无法像 SVN 那样做细粒度的文件夹级权限控制。一旦拉到 Monorepo 仓库,外包或普通员工就能看到核心财务系统的代码。这种场景必须做 Polyrepo 物理隔离。
2. 团队发布周期与部署节奏彻底脱钩(Independent Release Cadence)
- 场景:团队 A 负责的项目一天部署 50 次,团队 B 负责的项目是一个月审核一次的硬件固件。两者没有任何共享代码。
- 原因:强行绑在同一个 Monorepo 里,只会让 CI/CD 流水线变得极其混乱,且容易因为团队 A 的误操作导致团队 B 的构建失败。
3. 代码量达到“百万行级”且缺乏专业 DevOps/架构团队支持
- 场景:代码库体量极其庞大,但团队没有精力和能力配置 Turborepo / Nx 的远程缓存(Remote Caching)、
miniprogram-ci按路径触发等构建优化。 - 原因:由于代码太多,每次
git clone需要几分钟,全量运行一次pnpm build需要半小时,CI/CD 严重堵塞,开发体验骤降。
4. 极度简单的孤立项目
- 场景:只是做个一次性的活动 H5,或者逻辑极度简单的独立小应用。
- 原因:没必要增加
pnpm-workspace.yaml或工程配置开销,单仓库(Polyrepo)最快最省事。
三、 核心 Trade-off(权衡决策矩阵)
当你犹豫不决时,用下面这个矩阵来做权衡:
| 评估维度 | Monorepo 方案 | Polyrepo (多仓库) 方案 |
|---|---|---|
| 代码复用与共享成本 | 极低(直接 import 本地源码) |
高(必须通过 NPM 包发布或 Git Submodule) |
| 跨模块重构效率 | 极高(一个 PR 搞定全链路修改) | 极低(需要跨多个仓库切分支、发包、升级) |
| 构建与 CI/CD 复杂度 | 较高(需要按路径过滤构建,配置 Turbo/Nx 缓存) | 极低(每个仓库拥有独立简易的 CI/CD) |
| 代码权限与隔离性 | 差(仓库代码全员可见,难以限制文件夹权限) | 极强(天然的仓库级权限物理隔离) |
| 依赖版本一致性 | 强制一致(全局统一 React/TS/Tailwind 版本) | 容易漂移(项目 A 用 React 17,项目 B 用 React 19) |
| Git 历史与体积 | 历史混杂,体积增长快 | 历史清晰,单个仓库体积小 |
四、 架构师的“4 步快速决策树”
如果用一句话来指导未来的架构选型,你可以问自己这 4 个问题:
text
[是否有多个子项目/包 需要互相引用或共享类型?]
│
┌────────────────┴────────────────┐
▼ (否) ▼ (是)
【坚决选用 Polyrepo】 [这些子项目之间是否有严格的权限隔离需求?]
│
┌────────────────┴────────────────┐
▼ (是) ▼ (否)
【选用 Polyrepo + [团队是否有能力配置轻量工程化?]
私有 NPM 发包】 │
┌────────────────┴────────────────┐
▼ (否) ▼ (是)
【选用 Polyrepo】 【坚决选用 Monorepo】
(如 pnpm + Turborepo)回看我们之前的选型:
在你的“业务工作台”项目中:
apps/web(H5) 与apps/mp-shell(小程序壳) 存在强类型与 URL 契约依赖。- 团队内部无权限隔离障碍。
- 需要单命令一键启动本地联调。
所以,为你选择 轻量级 Monorepo 是一个完美符合上述决策逻辑的正确选择!