pnpm workspace 进阶技巧:过滤执行、依赖治理、缓存与 CI 稳定性
pnpm workspace 很容易“会用”,但要用到真正省时间、省成本,通常取决于三件事:
- 你能不能让命令只跑需要的部分(过滤执行)
- 你能不能把依赖问题从“玄学”变成可治理(版本策略、overrides、peer)
- 你能不能把 CI 做到快且稳定(缓存、锁文件策略、可重复构建)
本文按“你真实会遇到的问题”来组织内容,读完你应该能把 pnpm 用成团队的生产力工具。
1. workspace 的正确打开方式:显式、可追踪、可复用
1.1 pnpm-workspace.yaml
最基础但也最关键:
packages:
- "packages/*"
- "apps/*"
建议:
- 不要把不相关的目录(例如历史模板、巨型数据目录)纳入 workspace
- 目录边界越清晰,安装与扫描越快
1.2 workspace: 协议
在 monorepo 内部依赖,推荐:
{
"dependencies": {
"@acme/shared": "workspace:*"
}
}
它的意义是:让“内部依赖”变得显式,并且跟随 workspace 的版本/链接策略。
2. 过滤执行:把 monorepo 的命令从“全量”变成“增量”
2.1 --filter:你最该掌握的能力
只对某个包执行:
pnpm --filter landing devpnpm --filter hola lint
只对某一类包执行(按包名 pattern):
pnpm --filter "@acme/*" build
只对某个目录下的包执行(当你包名不统一时很有用):
pnpm --filter "./packages/*" lint
2.2 递归执行:-r / --recursive
全仓统一跑:
pnpm -r lintpnpm -r typecheck
注意:全仓跑适合做“基线”,但不适合做“常态”。
3. 依赖治理:把版本冲突从“踩雷”变成“可控”
3.1 pnpm.overrides:仓库级版本强制
当你需要:
- 修复某个依赖的安全漏洞
- 统一多包重复依赖的版本
可以在根 package.json 写:
{
"pnpm": {
"overrides": {
"minimist": "1.2.8"
}
}
}
建议:
- overrides 要有原因(最好写在 PR 描述/变更记录里)
- 不要过度使用(否则升级会变困难)
3.2 peerDependencies:别把它当成“麻烦”,它是“边界声明”
典型场景:
- UI 组件库通常对
vue/react用 peerDependencies - 这样可以避免同一个应用里出现多份框架实例
当你看到 peer 相关告警时,不要第一反应“关掉告警”,而是:
- 确认 peer 是否该由 consumer 提供
- 确认版本范围是否合理
4. 缓存与安装速度:让 CI 变快的关键细节
4.1 pnpm store 与缓存
pnpm 的性能优势之一是内容寻址的 store 机制。
在 CI 中建议做两件事:
- 使用 Node setup 的缓存(cache: pnpm)
- 使用
pnpm install --frozen-lockfile确保可重复
示意(GitHub Actions):
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
4.2 为什么要 --frozen-lockfile
它能保证:
- 依赖解析不漂移
- 你不会在 CI 里“悄悄更新 lockfile”
如果 CI 失败,说明:
- lockfile 没提交
- 或本地与 CI 的安装策略不一致
5. 常见坑与排障:pnpm 用久了必遇到
5.1 幽灵依赖(phantom deps)
现象:
- 本地能跑,CI 报
Cannot find module
原因:
- 某个包偷用别的包依赖,没写入自己的
package.json
解决思路:
- 严格化 import(lint + 规则)
- 减少 hoist(不要把一切都提升到根)
- 让 CI 在干净环境全量安装并跑检查
5.2 lockfile 冲突
现象:多人同时改依赖后,pnpm-lock.yaml 合并冲突频繁。
建议:
- 尽量把依赖升级集中到少数 PR
- 合并冲突时优先使用 pnpm 的合并策略(必要时重新安装生成)
- 避免手工编辑 lockfile
5.3 macOS EMFILE: too many open files
现象:开发时 watch 报文件句柄过多。
思路:
- 提高
ulimit -n - 减少 watcher 范围(隔离大目录、排除 build 输出)
6. 把 pnpm 用成“工程化能力”:推荐的脚本设计
一个实用建议:把脚本按“检查/修复/构建”拆开,并统一命名。
示例:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"typecheck": "tsc -p tsconfig.json --noEmit",
"build": "nuxt build"
}
}
根目录再用 -r 或 --filter 编排:
pnpm -r lintpnpm --filter landing build
结语:pnpm 的“进阶”= 可执行的协作效率
当你把 pnpm 的过滤执行、版本治理与 CI 缓存组合起来,你会获得一个很现实的收益:
- PR 检查更快
- 失败更可定位
- 团队协作更稳定
如果你希望我继续推进“写作计划未完成文章”,下一篇我建议优先补:
- 代码质量门禁与 CI 集成(把 lint/typecheck/test 变成流程)
- monorepo 工程化完整方案(边界 + 工具链 + CI)
- CLS 问题排查与修复大全(指标到修复手段)


