Monorepo 实战:pnpm workspace + Turborepo 的最佳实践
以 pnpm 的硬链接依赖管理与 Turborepo 的增量任务调度为核心,搭建一套可落地的 Monorepo 工程规范。
Monorepo 不是简单地把多个项目放进一个仓库,而是一套关于依赖管理、任务编排与代码边界的工程方法论。本文以最主流的 pnpm + Turborepo 组合为例,给出可直接复制的实践。
为什么选 pnpm
pnpm 相对 npm/yarn 有两个决定性优势:
- 硬链接存储:所有依赖统一存放在全局 store,项目通过硬链接引用,不重复占用磁盘,
node_modules结构也更为规范; - 严格的幽灵依赖隔离:pnpm 默认按包隔离依赖,
package.json里没声明的包无法被require到,倒逼你把依赖写清楚。
# 初始化 workspace
pnpm init
# 新建 pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Turborepo 的增量调度
Turborepo 解决的是「任务怎么跑得快」:它缓存每个任务的输入输出,只有依赖变化时才真正执行。
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": { "dependsOn": ["build"] }
}
}
^build 表示先构建所有被依赖的包,配合输出缓存,二次构建几乎瞬时完成。远程缓存(Remote Cache)还能让 CI 之间共享构建结果。
代码边界的设计原则
Monorepo 最怕的坑是「边界模糊」。建议:
- packages/ 放可复用的库,明确语义化版本;
- apps/ 放可部署的应用,彼此零依赖;
- 用 ESLint 的
import/no-restricted-imports禁止应用间互相引用; - 通过 changesets 管理版本发布,避免手动改版本号。
常见陷阱
- 锁文件冲突:统一用
pnpm-lock.yaml,禁用 npm/yarn; - 幽灵依赖回潮:升级依赖时用
pnpm why追踪来源; - 构建顺序混乱:依赖
dependsOn而非手动编排脚本。
结语
Monorepo 的价值在于降低跨包改动的成本,而不是追求仓库规模的宏大。工具只是手段,清晰的边界与可预测的构建才是目的。