pnpm-workspace.yaml packages 通配符配置详解
pnpm-workspace.yaml 配置安装问题
配置示例
packages:
- 'common'
- 'common/*'
- 'common/*/*'
- 'common/**'
common:common/package.json的包会安装,底下的任何子目录都不会安装common/*:common下第一级子目录包会安装,子目录下的任何子目录不会安装,其本身下的common/package.json也不会安装common/*/*:common下第二级子目录包会安装,其他任何不安装common/**:common下所有package.json被安装,包含其本身
为什么会有这些差别
我一开始也觉得奇怪:common 和 common/* 看着差不多,为什么一个装本体不装子目录,另一个反过来?搞清楚一点就通了——packages 里写的每一项都是目录的 glob 匹配模式,不是「以这个目录为根往下递归找」。pnpm 拿这些模式去匹配仓库里的目录路径,凡是被命中的目录,如果里面有 package.json,就登记成一个 workspace package。
按 glob 的语义再看这四条就很直白:
common只能匹配到common这一个路径本身,所以只有common/package.json被登记。*匹配一层路径片段且不跨越/,所以common/*匹配common/utils、common/ui,但匹配不到common自己,也匹配不到common/ui/theme。common/*/*同理,只命中恰好两层深的目录。**是跨层匹配,common/**命中common以及它下面任意深度的目录,所以是「全都要」。
因为是逐条求并集,想同时要本体和所有子包,最省事的写法就是直接 common/**;要是只想要本体加第一级,写两条 common 和 common/* 就行。
排除某些目录
glob 前面加 ! 表示排除,这在 monorepo 里比通配符本身更常用。比如仓库里有一堆测试固件目录也带 package.json,不想被当成工作区包:
packages:
- 'packages/**'
# 排除所有 test 目录下的包
- '!**/test/**'
# 排除历史归档
- '!packages/legacy/**'
排除项和包含项没有顺序要求,pnpm 会先把包含项匹配出来的集合算好,再把排除项命中的剔掉。所以 ! 那几行放前面放后面都一样。
几个容易踩的坑
node_modules 里的包不会被匹配。 这个不用自己写排除规则,pnpm 内部就跳过了,不然依赖里那几千个 package.json 全都成工作区包了。
改完 pnpm-workspace.yaml 必须重新 pnpm install。 工作区成员列表是在 install 阶段解析并写进 pnpm-lock.yaml 的 importers 段的,光改 yaml 不装,pnpm -r 之类的递归命令还是按老的列表走。我调整完通配符之后如果发现某个新包死活不被识别,第一反应就是回去补一次 install。
pnpm-workspace.yaml 必须在仓库根目录,且文件名不能写成 .yml。 pnpm 是从当前目录往上找这个精确文件名来确定工作区根的,写成 pnpm-workspace.yml 会被完全忽略,表现就是所有 -w、--filter 相关的命令都报「不在工作区里」。
包名和目录名是两件事。 --filter 过滤的是 package.json 里的 name 字段,不是目录名。所以 common/ui 目录里如果写的是 "name": "@app/ui",过滤时要用 pnpm --filter @app/ui build。
验证匹配结果
改完配置我一般不靠猜,直接列一下 pnpm 实际认到了哪些包:
# 列出当前工作区所有包(名称 + 版本 + 路径)
pnpm list -r --depth -1
# 只看包名,确认数量对不对
pnpm list -r --depth -1 --json | grep '"name"'
--depth -1 的意思是不展开任何依赖树,只输出包本体信息,正好用来核对工作区成员。如果输出里少了你期望的包,就是通配符层级没对上;如果多了不该有的,就补一条 ! 排除。

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