pnpm -r 递归执行 workspace 子包命令
场景
在当前 pnpm-workspace 下,有很多子包,我们想把每个子包的 node_modules 或者 dist 进行删除。
monorepo 里子包一多,逐个 cd 进去执行命令就很难受了。十个包意味着十次目录切换,还容易漏掉某一个。而且这类批量操作不止清理一种,构建、测试、lint 都有同样的需求。
pnpm 对这个场景的解法是 -r 参数,它会遍历工作空间里所有子包,在每个包里执行同名脚本。工作空间的范围由根目录的 pnpm-workspace.yaml 定义:
packages:
- 'packages/*'
- 'apps/*'
方法
思路是「子包各自定义怎么做,根目录负责统一调度」。每个子包知道自己要清理什么目录,根目录只管把命令广播下去。
我们给每个子包的 package.json 添加如下 scripts
{
"scripts": {
"clean": "rimraf node_modules"
}
}
然后在根目录的 package.json 添加如下 scripts
{
"scripts": {
"clean": "pnpm run -r clean"
}
}
这个命令中的 -r 就是执行子包任务的关键 pnpm run -r clean
-r(--recursive): 此参数能够执行当前工作空间所有子目录 package.json 中的命令
有几个行为细节值得知道。
没有定义该脚本的子包会被跳过,不报错。所以不必给每个包都补上 clean,只在需要的包里定义就行。这点和直接遍历目录执行 shell 命令不一样,容错性好很多。
默认按依赖拓扑顺序串行执行。如果包 A 依赖包 B,pnpm 会先在 B 里跑再在 A 里跑。这对 build 至关重要——被依赖的包必须先产出 dist,依赖它的包才能构建成功。clean 这类无依赖关系的任务顺序无所谓。
想并行加 --parallel,完全忽略拓扑顺序同时开跑,适合 clean、lint 这些互不干扰的任务,速度快很多。但 build 千万别用 --parallel,依赖关系一乱就会出现「构建 A 时 B 的产物还不存在」。介于两者之间的选项是 --workspace-concurrency=N,在遵守拓扑顺序的前提下限制并发数。
用 --filter 缩小范围。不想全量跑的时候,pnpm --filter @scope/foo run build 只跑指定包,pnpm --filter @scope/foo... run build 会带上它依赖的所有包一起跑(注意末尾三个点),pnpm --filter ...@scope/foo run build 则是跑所有依赖它的包。改了某个底层包想验证下游有没有崩,后面这个写法很好用。
想要同时在根目录也执行
-r 只遍历子包,根目录本身不算在内。但根目录也有自己的 node_modules(放着所有子包提升上来的公共依赖,体积往往比子包还大),清理时不能漏掉它。
这里有个陷阱:不能直接在根目录的 clean 里写 pnpm run -r clean && rimraf node_modules,因为根目录的脚本名如果也叫 clean,pnpm run -r clean 在遍历时不会回到根包(-r 排除根目录),这倒没问题;但如果把清理逻辑直接拼在同一条命令里,rimraf node_modules 会把 rimraf 自己所在的目录删掉,后续命令就找不到可执行文件了。所以要拆成两个脚本,靠 && 顺序执行。
可以在根目录的 package.json 中也添加一个命令去执行该操作,再执行完子目录后再去执行根目录的命令
{
"scripts": {
"clean": "pnpm run -r clean && pnpm run clean:modules",
"clean:modules": "rimraf node_modules"
}
}
顺序很关键:先清子包再清根目录。反过来的话根 node_modules 里的 rimraf 先被删掉,子包的 clean 立刻就报命令找不到了。
用 rimraf 而不是 rm -rf 是为了跨平台,Windows 下没有 rm。另外 rm -rf 在 Windows 上删深层嵌套目录还会撞上路径长度限制,rimraf 用 Node API 实现,这些问题都绕过去了。
清完 node_modules 之后要重新 pnpm install。pnpm 的 store 在全局(默认 ~/.pnpm-store),包本身还在磁盘上,重装基本只是重建硬链接和符号链接,很快,不用重新下载。真要连 store 一起清理才用 pnpm store prune,那个会删掉 store 里没被任何项目引用的包,但重装就得重新下载了。

还没有留言,来发表第一条吧~