◆

pnpm 指定工作目录执行命令(-C / --dir / -w)

2023-03-03 01:520

场景

当前我们在根目录,想要执行子目录中 package.json 中的 scripts 命令,或者我们在某个很深的子目录想要执行根目录的命令

解决方法

指定目录执行命令

pnpm -C play run clean
pnpm --dir single/child/childs clean

配置项:-C <path>, --dir <path> 在 <path> 中启动 pnpm ,而不是当前的工作目录。

执行主目录命令

pnpm clean -w

-w(--workspace-root): 在工作空间的根目录中启动 pnpm ,而不是当前的工作目录。

这三个参数的差别

-C / --dir 和 -w 看起来都是「换个目录执行」,但机制不一样:

  • -C <path> 是纯粹的目录切换,效果等同于 cd <path> && pnpm ...。路径可以是任意相对或绝对路径,跟工作区配置无关,甚至可以指向一个不属于当前 monorepo 的项目。
  • -w 是语义化的定位,pnpm 会从当前目录往上找 pnpm-workspace.yaml,找到的那一层就是根。所以不管我现在钻到多深的子目录,-w 都能准确回到根,不用数几个 ../。

顺带说一句,-w 在 pnpm add 里还有一层含义:把依赖装到工作区根的 package.json。pnpm 默认禁止在根目录直接 pnpm add,会报 Running this command will add the dependency to the workspace root, which might not be what you want,必须显式加 -w 表态:

# 把 eslint 装到工作区根的 devDependencies
pnpm add -Dw eslint

参数位置有讲究

上面示例里 pnpm -C play run clean 把 -C 放在了子命令前面,而 pnpm clean -w 把 -w 放在后面。这两种写法 pnpm 都能解析,但放在子命令前面更稳妥。原因是 pnpm 会把子命令后面的参数往下透传给被执行的脚本,位置靠后的标志有时会被当成脚本自己的参数。真正麻烦的是给脚本传参数的场景:

# 想给 vite 传 --port,必须用 -- 分隔
pnpm -C play run dev -- --port 3000

-- 之后的所有内容 pnpm 一概不解析,原样交给 dev 脚本。没有这个分隔符的话,--port 有可能被 pnpm 自己吞掉。

run 关键字能不能省

能省,但有前提。pnpm clean 在 clean 不是 pnpm 内置命令时,会被当作 pnpm run clean 处理。问题在于脚本名撞上内置命令时就出事了——如果 package.json 里有个叫 install 或 add 的脚本,pnpm install 执行的是 pnpm 的安装逻辑,不是你的脚本。所以我写文档和 CI 配置时一律带上 run,避免这种歧义。

用 --filter 替代 -C

-C 是按路径定位,--filter 是按包名定位,在 monorepo 里后者往往更合适:

# 按包名执行(不用关心目录在哪)
pnpm --filter @app/play run clean

# 该包 + 它依赖的所有包一起执行
pnpm --filter @app/play... run build

# 依赖该包的所有包(反向)
pnpm --filter ...@app/play run build

# 按目录过滤,效果接近 -C 但支持通配
pnpm --filter "./packages/**" run lint

... 那三个点的位置决定方向:跟在包名后面是「带上它的依赖」,写在包名前面是「带上依赖它的包」。改了某个基础库想验证下游有没有炸,用 ...@app/utils 这种反向过滤一次跑完最省事。

和 -C 最本质的区别是:-C 一次只能进一个目录,--filter 可以一次命中多个包并批量执行;而且 --filter 走的是包依赖图,pnpm 会按拓扑序排好执行顺序,被依赖的包先构建完再构建依赖方。-C 没有这层保证。

💬留言板0

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

PAGE LOAD / LIVEpnpm 指定工作目录执行命令(-C / --dir / -w)加载中