pnpm + Workspace:中小团队 monorepo 落地手记
事故回顾:运维后台和商户后台各 copy 了一份"共享"组件库,A 项目修了表格组件的分页 bug,B 项目没人同步,上周商户反馈数据错乱。CTO 拍板:合并 monorepo。
调研一天后选了 pnpm workspace(对比了 yarn workspaces 和 lerna),两周完成迁移。这篇是实战记录。
为什么是 pnpm
三个候选的对比:
- npm workspaces:起步晚,功能基础够用但安装慢
- yarn workspaces:经典但安装速度一般,node_modules 依然扁平化的坑
- pnpm:硬链接 + 符号链接的方案,装得快、省磁盘、依赖隔离严格
让我下决心的是 pnpm 的"严格":默认不允许访问未声明的依赖(幽灵依赖),这个特性在迁移时帮我们揪出了 7 处隐式依赖的埋雷代码。
目标结构
apps/
admin/ # 运维后台
merchant/ # 商户后台
h5/ # C 端 H5
packages/
ui/ # 共享组件库
utils/ # 共享工具函数
config/ # 共享 eslint/tsconfig
pnpm-workspace.yaml
package.json
pnpm-workspace.yaml 声明工作区范围:
packages:
- 'apps/*'
- 'packages/*'
根 package.json 只放全局工具和脚本:
{
"private": true,
"scripts": {
"dev:admin": "pnpm --filter admin dev",
"build": "pnpm -r --filter './apps/*' build",
"test": "pnpm -r test"
},
"devDependencies": {
"typescript": "^4.8.0"
}
}
包之间怎么引用
ui 包的 package.json 起好名字:
{
"name": "@company/ui",
"version": "1.0.0",
"main": "src/index.ts"
}
应用里引用 workspace 内的包,用 workspace:* 协议:
{
"name": "admin",
"dependencies": {
"@company/ui": "workspace:*",
"@company/utils": "workspace:*"
}
}
workspace:* 表示"永远用工作区里的本地版本",发布时 pnpm 会自动替换成真实版本号。本地开发改 ui 的代码,admin 里立即生效,不用发版不用 link——这就是 monorepo 的核心爽点。
高频命令速查
pnpm install # 根目录一次安装所有包的依赖
pnpm --filter admin dev # 只跑 admin 的 dev
pnpm --filter @company/ui build # 只构建 ui 包
pnpm -r build # 所有包都 build(-r = recursive)
pnpm -r --parallel test # 所有包并行跑测试
pnpm add lodash --filter merchant # 给指定包装依赖
pnpm why lodash # 谁依赖了 lodash
--filter 是日常用得最多的参数,支持包名、目录、git 变更范围等多种写法:
# 只构建"依赖了 ui 的包"(ui 改了,谁要重新构建?)
pnpm -r --filter '...@company/ui' build
迁移中的四个坑
1. 幽灵依赖现形记
老代码里有 import dayjs from 'dayjs',但 package.json 没声明——它一直靠的是某个依赖的依赖恰好把 dayjs 装进了扁平的 node_modules。pnpm 的严格隔离让这些全部报错。
解法:逐个补声明。虽然烦,但这是在排除定时炸弹。
2. 版本统一
admin 用 lodash 4.17.20,merchant 用 4.17.15,合并后以哪个为准?
我们借机统一到最新版,并用根目录的 pnpm.overrides 强制兜底:
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21"
}
}
}
3. 构建顺序
ui 没构建,admin 就起不来。pnpm 会根据依赖关系自动排拓扑序(topological order),只要依赖声明正确,pnpm -r build 就是对的顺序。这也是"依赖必须正确声明"的另一个理由。
4. CI 缓存 key
GitHub Actions 里缓存 pnpm store:
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
安装时间从 3 分钟降到 40 秒。注意用 --frozen-lockfile 保证 CI 严格按 lock 装。
配套工具建议
- changesets:多包版本管理和 changelog 生成,发版自动化必备
- turborepo:构建缓存 + 任务编排,包多了以后(10+)再上,现在 5 个包 pnpm 原生够用
小结
- pnpm workspace 的核心:workspace:* 引用本地包,改代码立即生效
- 严格依赖隔离是特性不是麻烦,幽灵依赖迟早要还
- --filter 是日常核心,-r 递归跑命令,拓扑序自动保证构建顺序
- 版本冲突用 pnpm.overrides 统一,CI 记得缓存 pnpm store
- 版本发布上 changesets,构建编排复杂了再上 turborepo
迁移一个月后的效果:组件 bug 修一处全平台生效、新功能组件一次开发三端复用、依赖总数下降了 30%。那次事故的教训,值了。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~