
monorepo 里想不动窝操作某个子包,npm --prefix packages/app run build 是最常见的写法。有人担心脚本拿到的工作目录不对,有人担心依赖装错地方。下面几个问题都是围绕这件事的,答案全部来自本机实测(node v26.8.1 / npm 11.19.0),脚本在 tools/verify-pkgmgr-cwd.sh。
--prefix 会不会把脚本的工作目录也换掉?
会。在 repo 根上执行 npm --prefix packages/app run where,where 脚本只有一行 console.log(process.cwd()),输出是:
===== npm --prefix:脚本看到的目录,以及退出码 =====
> app@1.0.0 where
> node -e 'console.log("cwd=", process.cwd())'
cwd= /private/tmp/pkgmgr-cwd/repo/packages/app
npm run exit=0
脚本拿到的 cwd 就是 prefix 指的那一层,不是执行命令时所在的 repo 根。也就是说,脚本里写相对路径读文件,读的是子包目录下的文件。这一点对依赖 npm 生命周期脚本的工具同样成立。
装依赖呢,node_modules 会出现在哪一层?
npm --prefix packages/app install left-pad 之后找了一遍:
===== npm --prefix 装依赖:node_modules 出现在哪一层 =====
repo 根这一层:
没有
packages/app 这一层:
packages/app/node_modules
===== 对照:cd 进去再装,落点一致 =====
packages/app/node_modules
落点在 prefix 指的包那一层,跟 cd 进去再装完全一致。没有装到 repo 根去,也没有因为上层有 package.json 就往上漂。
那依赖提升(hoisting)还生效吗?
npm 对普通目录树的提升逻辑照走:装的时候它照样会看上层有没有可以共享的依赖。这一点没实测深挖,只确认了落点:node_modules 建在子包那层。monorepo 用 workspace 协议的话另说,workspace 有自己的一套提升规则,--prefix 是绕开 workspace 直接按目录操作的,两者别混用。
有什么场景不该这么用?
CI 里想在根上一条命令依次构建多个子包,--prefix 拼循环确实省事,但每个子包的脚本假设未必一样,脚本假设的环境变量、.npmrc 都跟着 cwd 走。子包多了之后,一条根命令里的隐含假设会越积越多。包少、脚本统一的时候用着舒服;包一多,老老实实进目录跑,或者交给专门的 monorepo 工具,出问题好定位。
另外注意 --prefix 的值写 . 的时候行为和不写一样,这种写法在脚本里容易让人误以为有特殊处理,其实没有,别为了好看加它。