认识 Monorepo:为什么多个包要放进一个仓库
本节目标
- 说清楚 Monorepo 解决的是什么痛点
- 区分 Monorepo 与 Multirepo(多仓)的本质差异
- 知道「什么时候该用、什么时候别用」
先从一个真实痛点说起。假设你有 3 个前端包:web(官网)、admin(后台)、ui(组件库)。如果用 Multirepo(多个独立仓库),会出现三件糟心事:
- 依赖版本地狱:
web装了lodash@4.17.20,admin装了4.17.21,同一个bug在两个仓库表现不一样,排查到怀疑人生。 - 改一处要发三个 PR:
ui组件库改了个Button的 props,你得先在ui发版,再去web、admin各提一个「升级 ui 版本」的 PR,等它们各自 CI 跑完——一整天就这么没了。 - 类型/工具无法共享:公共 TS 类型、ESLint 配置、构建脚本,每个仓库复制一份,改规则时要同步 N 个仓库。
Monorepo 就是反过来:把「多个相关包/应用」放在「同一个仓库」里统一管理。上面的问题迎刃而解——改 ui 的 Button,在同一个仓库里顺手改 web/admin 的调用,一次 commit 全搞定;依赖只有一份,版本天然统一。
// Monorepo 的目录长这样(一个仓库,多个包)
// my-monorepo/
// ├─ package.json // 根:管理 workspace
// ├─ pnpm-workspace.yaml // 声明哪些目录是「包」
// └─ packages/
// ├─ web/ // 应用 A
// ├─ admin/ // 应用 B
// └─ ui/ // 共享组件库但要注意:Monorepo 不是银弹。它带来一个代价——「仓库很大、CI 很慢、工具链更复杂」。小团队只有一个包时,上 Monorepo 纯属自找麻烦。我的判断线很清晰:当你的代码需要「跨包复用 + 频繁联动修改」时,才值得上 Monorepo;否则一个普通仓库 + 一个 npm 私有包就够了。
名词解释
Monorepo(单体仓库):把多个相关项目放在「同一个 Git 仓库」里管理,共享同一套依赖、配置与 CI。注意它和「巨石应用(Monolith)」不同——仓库是一个,但里面仍然是多个可独立构建/发布的包。
Multirepo(多仓库):每个项目一个独立 Git 仓库,各自有独立的依赖、版本与发版流程。隔离性好,但跨项目改动成本高。
Workspace(工作区):包管理器(pnpm/npm/yarn)提供的「把仓库内多个子包当成一个整体来安装、链接」的能力。它是 Monorepo 的底层机制。
幽灵依赖(Phantom Dependency):本该只在 A 包 dependencies 里的包,因为被 B 包提升到了仓库根 node_modules,导致 C 包没声明也能 import 到它。一旦 A 改了依赖,C 就莫名其妙崩了。Monorepo 工具优劣很大程度取决于能不能杜绝它。
课后练习
练习 1:团队只有一个 web 应用、没有共享组件库,要不要上 Monorepo?
答案:不要。Monorepo 的价值来自「多包复用与联动」,单包场景下它只会增加工具链复杂度,用一个普通仓库足矣。
练习 2:Monorepo 和「把所有代码塞进一个巨石应用」是一回事吗?
答案:不是。Monorepo 是「一个仓库、多个独立包」,包之间仍边界清晰、可单独构建发布;巨石应用是「一个仓库、一个打包产物」,所有代码耦合在一起。前者治的是「协作与复用」,后者往往是技术债。
总结
我的立场很明确:Monorepo 是「协作规模」的解药,不是「项目大小」的解药。它用「统一仓库 + 统一依赖 + 原子化改动」换来了跨包联动的丝滑,代价是工具链与 CI 的复杂度。判断要不要上,只看一件事——你的包之间是否「经常要一起改、一起复用」。是,就上;否,别凑热闹。下一节我们直接动手,用 pnpm workspace 从零搭一个能跑的 Monorepo。