补充:pnpm 10 自己也可以管理项目指定的 pnpm 版本
这是一个很重要的补充:即使 Corepack 没有启用,现代 pnpm 也可以根据项目 package.json 中的 packageManager 自动选择并运行指定版本的 pnpm。
1. 我的实际环境
Mac 上全局安装的 pnpm 是:
which pnpm
# /opt/homebrew/bin/pnpm
pnpm --version
# 在普通目录下:10.28.0
这个 executable 实际指向:
/opt/homebrew/bin/pnpm
↓
/opt/homebrew/lib/node_modules/pnpm/bin/pnpm.cjs
也就是说:
Global pnpm = 10.28.0
Corepack = disabled
但是进入一个项目以后,如果 package.json 有:
{
"packageManager": "pnpm@9.12.0"
}
执行:
pnpm --version
得到:
9.12.0
而离开项目:
cd ~
pnpm --version
又得到:
10.28.0
所以这是 pnpm 自己的版本管理功能,不是 Corepack。
2. 实际工作流程
可以理解成:
/opt/homebrew/bin/pnpm
Global pnpm 10.28.0
↓
启动
↓
检查当前 project
↓
读取 package.json
↓
发现:
"packageManager": "pnpm@9.12.0"
↓
查找 pnpm 自己管理的 9.12.0
↓
运行 pnpm 9.12.0
因此:
which pnpm
仍然可能显示:
/opt/homebrew/bin/pnpm
但是:
pnpm --version
却显示:
9.12.0
这两个结果并不矛盾。
which pnpm 告诉我:
Shell 最开始启动的是哪个 executable。
pnpm --version 告诉我:
针对当前项目,最终实际运行的是哪个 pnpm 版本。
3. 项目指定版本到底下载到哪里?
已经在我的 Mac 上确认,pnpm 自己管理的版本放在:
~/Library/pnpm/.tools/pnpm/
实际看到:
~/Library/pnpm/.tools/pnpm/
10.4.1 -> 10.4.1_tmp_86136
10.4.1_tmp_86136/
9.12.0 -> 9.12.0_tmp_67505
9.12.0_tmp_67505/
所以当前机器上至少已经有:
pnpm-managed tools
├── pnpm 9.12.0
└── pnpm 10.4.1
这些版本:
- 不是安装在项目的
node_modules - 不是
/opt/homebrew/bin/pnpm那个 global installation - 不是 Corepack cache
- 是 pnpm 自己管理的 user-level package-manager tools
同一个版本可以被多个项目共用,不需要每个 project 下载一份。
4. Global pnpm 和 project pnpm 的关系
例如:
Global installation
/opt/homebrew/bin/pnpm
↓
pnpm 10.28.0
│
│ reads package.json
↓
"packageManager": "pnpm@9.12.0"
│
↓
~/Library/pnpm/.tools/pnpm/9.12.0
│
↓
实际执行 pnpm 9.12.0
另一个项目如果有:
{
"packageManager": "pnpm@10.4.1"
}
则:
/opt/homebrew/bin/pnpm
↓
pnpm 10.28.0
↓
packageManager says 10.4.1
↓
~/Library/pnpm/.tools/pnpm/10.4.1
↓
实际执行 pnpm 10.4.1
所以可以同时存在:
Global launcher:
pnpm 10.28.0
Project A:
pnpm 9.12.0
Project B:
pnpm 10.4.1
而不需要不断执行:
npm install -g pnpm@...
5. 这和 Corepack 非常像,但不是一回事
Corepack enabled
pnpm command
↓
Corepack shim
↓
读取 packageManager
↓
选择对应 pnpm
这时候:
which pnpm
可能看到:
~/.nvm/versions/node/v22.11.0/bin/pnpm
↓
corepack/dist/pnpm.js
Corepack disabled + pnpm 10
我的当前情况:
pnpm command
↓
/opt/homebrew/bin/pnpm
↓
global pnpm 10.28.0
↓
读取 packageManager
↓
pnpm自己的 version management
↓
~/Library/pnpm/.tools/pnpm/<version>
所以最终效果很相似:
每个 project 可以使用自己指定的 pnpm version。
但是负责这件事的工具不同。
6. 怎么确认 Corepack 到底有没有参与?
不能只看:
pnpm --version
因为看到:
9.12.0
并不代表 Corepack 一定参与。
应该看:
which pnpm
ls -l "$(which pnpm)"
我的 Corepack disabled 状态是:
/opt/homebrew/bin/pnpm
→ ../lib/node_modules/pnpm/bin/pnpm.cjs
这说明:
Corepack 没有参与
如果看到:
~/.nvm/versions/node/v22.11.0/bin/pnpm
→ ../lib/node_modules/corepack/dist/pnpm.js
才说明:
Corepack 正在接管 pnpm
7. 怎么查看 pnpm 自己下载了哪些版本?
我的实际目录:
cd ~/Library/pnpm/.tools/pnpm
ls -la
例如:
10.4.1 -> 10.4.1_tmp_86136
10.4.1_tmp_86136
9.12.0 -> 9.12.0_tmp_67505
9.12.0_tmp_67505
也可以通过 PNPM home 找:
pnpm bin -g
然后检查:
ls -la "$(pnpm bin -g)/.tools/pnpm"
如果要确认某个 managed version:
cat ~/Library/pnpm/.tools/pnpm/9.12.0/package.json | grep '"version"'
8. 对我现在环境的实际意义
目前没有必要为了 per-project pnpm version 而专门启用 Corepack。
当前环境已经可以做到:
nvm
└── Node 22.11.0
Global pnpm
└── 10.28.0
Corepack
└── disabled
Project A
packageManager: pnpm@9.12.0
→ 实际 pnpm 9.12.0
Project B
packageManager: pnpm@10.4.1
→ 实际 pnpm 10.4.1
普通目录 / 没有指定版本的项目
→ global pnpm 10.28.0
这已经实现了最重要的目标:
不同项目可以固定并使用不同 pnpm version。
9. 一个容易误判的地方
曾经在指定:
"packageManager": "pnpm@9.12.0"
的项目中,似乎看到过:
pnpm --version
# 10.28.0
目前无法重现。
现在稳定、可重复的结果是:
项目内: pnpm 9.12.0
项目外: pnpm 10.28.0
因此不应该根据之前一次无法重现的结果推断原因。
如果以后再次出现,不要关闭 Terminal,立即记录:
pwd
which node
node -v
which pnpm
type -a pnpm
pnpm --version
grep packageManager package.json
pnpm config get manage-package-manager-versions
这样可以确定当时到底是:
- PATH 不同
- Node/nvm 环境不同
- pnpm executable 不同
- project root 不同
- pnpm version management 被关闭
- 或其他 shell environment 差异
在没有重新出现之前,把那次 10.28.0 视为 unconfirmed transient state,而不是当前配置的正常行为。
10. 最终记住这三条
which pnpm
回答:
Shell 首先启动哪个 pnpm executable?
pnpm --version
回答:
在当前 project context 下,最终使用哪个 pnpm version?
packageManager
例如:
"packageManager": "pnpm@9.12.0"
对于现代 pnpm,可以告诉 pnpm:
这个项目应该使用 pnpm 9.12.0。
因此完全可能出现:
which pnpm
# /opt/homebrew/bin/pnpm
pnpm --version
# 9.12.0
这是正常的。
它表示:
global pnpm 10.28.0
↓
作为入口启动
↓
读取 project packageManager
↓
从 ~/Library/pnpm/.tools/pnpm/
选择项目指定版本
↓
实际运行 pnpm 9.12.0
整个过程不需要 Corepack。
