PNPM
快速、节省磁盘空间的包管理器
常用命令
多个 filter
pnpm --filter pkgA ---filter pkgB buildWARNING
在 pnpm@8 中工作需要额外的参数设置,参考 #6300
不修改 pnpm-lock.yaml
有的时候,拉新项目本地安装依赖的时候,由于 PNPM 版本不对,不启用 corepack 的情况下。会修改 pnpm-lock.yaml 的内容。这个修改通常是意外且无意义的
pnpm install --frozen-lockfile仅安装 dependencies
我们知道,当安装一个 NPM 包的时候,不会安装其 devDependencies。可以通过指定 -P 参数,实现本地 install 不安装 devDependencies
pnpm install -Ppnpm-lock.yaml
该文件中包含着 pnpm 对于 lockfile 的版本描述。一个典型的 pnpm-lock.yaml 文件的顶层结构如下:
lockfileVersion: '9.0'
setting:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
vitepress:
hash: 88f19f649e07bbf1073c1f0655939619ebc587927c9545a07b2e4aa7cd85b60d
path: patches/vitepress.patch
importers:
.:
packages:
snapshots:lockfileVersion
用于标识锁文件本身的格式版本,使用不同版本的 pnpm 生成的锁文件,其格式版本也大概率不同。
lockfileVersion: '9.0'settings
首次执行 pnpm install 时,会根据项目中的 pnpm 相关的配置,例如 .npmrc 文件,将关键的,影响依赖解析的配置写入 settings 中
settings:
# 是否自动安装 peer 依赖(pnpm 7+ 新增)
autoInstallPeers: true
# 是否严格校验 peer 依赖版本
strictPeerDependencies: false
# 是否兼容 npm 的 peer 依赖解析逻辑
legacyPeerDeps: false
# 依赖安装的并发数(部分版本会记录)
maxConcurrentTasks: 16patchedDependencies
当通过 pnpm patch 命令对某个依赖进行了修改,即 package.json 中存在 pnpm.patchedDependencies 字段:
{
"name": "docs",
"pnpm": {
"patchedDependencies": {
"vitepress": "patches/vitepress.patch"
}
}
}安装依赖后,会在 pnpm-lock.yaml 中记录下所有依赖的 hash 值,以及对应的 patch 文件路径
patchedDependencies:
vitepress:
hash: 88f19f649e07bbf1073c1f0655939619ebc587927c9545a07b2e4aa7cd85b60d
path: patches/vitepress.patchimporters
importers 字段下通常会有一个 . 键,对应着项目根目录的依赖解析信息,例如:
importers:
.:
dependencies:
'@netlify/functions':
specifier: ^2.4.0
version: 2.5.1
'@unocss/postcss':
specifier: ^66.5.10
version: 66.5.10([email protected])
devDependencies:
'@antfu/eslint-config':
specifier: ^6.2.0
version: 6.2.0(@vue/[email protected])([email protected]([email protected]))([email protected])([email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected])([email protected]))
vue:
specifier: ^3.5.25
version: 3.5.25([email protected])上面的 dependencies 和 devDependencies 就是当前项目的直接依赖,和 package.json 中的 dependencies 和 devDependencies 是完全对应的。
常规情况
通过观察,我们可以看到声明的版本和实际安装版本的区别 ,例如上面声明的 @netlify/functions 是 ^2.4.0 版本,实际安装的版本是 2.5.1。满足 SemVer 规范。
'@netlify/functions':
specifier: ^2.4.0
version: 2.5.1顶层 peer
除此之外,部分包后面带有小括号,例如这里的 ([email protected])
'@unocss/postcss':
specifier: ^66.5.10
version: 66.5.10([email protected])这说明这个 @unocss/postcss 声明了 peerDependencies 为 postcss 并且实际的版本为 8.5.6
可以通过访问 https://npmx.dev/package/@unocss/postcss/v/66.5.10 证明这一点

深度 peer
如果我们按照相同的思路去看这个包:https://npmx.dev/package/netlify-cli/v/17.16.1
netlify-cli:
specifier: ^17.10.2
version: 17.16.1(@types/[email protected])([email protected])就会发现不对,[email protected] 这个包根本没有直接声明 peerDependencies,哪来的 @types/node 和 picomatch 呢?
这其实是因为括号里列出的,有可能是其子依赖的 peerDependencies。那这里又有疑问了,netlify-cli 应该是一个很上层的包,如果按照递归的说法,下面多层子依赖,肯定会有很多 peer,不止这里的两个
答案是: 只有“未满足”且需要“向上层索要”的 Peer Dependency 才会出现在这里
Peer Dependency 的解析遵循就近原则。
- 子依赖自己满足了:如果
netlify-cli依赖了A,A依赖B,B有 peer dependencyC如果netlify-cli或者A的dependencies里直接声明了C,那么这个 peer dependency 就在内部被“消化”解决了。这种情况下,它不需要向上层(也就是你的项目根目录)索要C。因此,这个C不会 出现在netlify-cli的括号里 - 向上层索要(Unmet Peer Dependency): 只有当
netlify-cli及其所有子依赖都 没有 在其dependencies中提供某个 Peer Dependency,而这个 Peer Dependency 必须由你的项目(root)来提供时,它才会出现在netlify-cli的版本号后缀里
嵌套 peer
来看这个例子:
splitpanes:
specifier: ^3.2.0
version: 3.2.0([email protected]([email protected]))这种情况说明:
- splitpanes 依赖树缺少了 [email protected] 和 [email protected],需要向上层索要
- 这两个 peer 存在层级关系,即 [email protected] 是 [email protected] 的 peer 依赖或者子依赖
packages
这里列出了项目中所有依赖包,你可以发现这和 node_modules/.pnpm 中列出的包数量和版本都是一致的
packages:
'@aashutoshrathi/[email protected]':
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
engines: {node: '>=0.10.0'}
'@adobe/[email protected]':
resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==}
'@antfu/[email protected]':
resolution: {integrity: sha512-ksasd+mJk441HHodwPh3Nhmwo9jSHUmgQyfTxMQM05U7SjDS0fy2KnXnPx0Vhc/CqKiJnx8wGpQCCJibyASX9Q==}
hasBin: true
peerDependencies:
'@eslint-react/eslint-plugin': ^2.0.1
'@next/eslint-plugin-next': '>=15.0.0'除了包名和版本,其下还会声明一些属性,让我们逐个看下
resolution
resolution.integrity 是 npm registry 在返回包信息时提供的
'@aashutoshrathi/[email protected]':
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
engines: {node: '>=0.10.0'}当你下次安装这个包时,pnpm 会计算下载文件的哈希值,并与这个值进行比对。如果不一样,说明文件被篡改了或者下载出错了,安装就会报错
engines
如果包中的 package.json 中声明了 engines 属性,pnpm 会在安装时检查当前环境是否满足这些要求。如果不满足,安装就会报错
hasBin
根据包的 package.json 中的 bin 字段分析得出,如果包中声明了 bin 字段,那么 hasBin 就为 true
peerDependencies/peerDependenciesMeta
即对应包的 package.json 中的 peerDependencies 和 peerDependenciesMeta 字段,原封不动的拿过来了
当两个字段组合用代表着,这个 peer 依赖是可选的,但是如果要用,必须满足指定的版本范围
cpu/os
即对应包的 package.json 中的 cpu 和 os 字段,原封不动的拿过来了
告诉包管理器: “我这个包,只支持在这些系统上运行。
snapshots
在上面的 packages,记录了所有依赖包的信息,包括包名、版本。而在 snapshots 中,将会记录他们之间的依赖关系
packages 里面的数量和 snapshots 里面的数量也是一致的,会对每个包进行说明
空依赖
下面的 @adobe/[email protected] 这个包没有任何依赖,所以在 snapshots 中为空,可以访问 https://npmx.dev/package/@adobe/css-tools/v/4.4.3 证实这一点
snapshots:
'@adobe/[email protected]': {}嵌套依赖
和 importers 中的依赖的命名类似,会包含并列/嵌套的缺失的 peer 依赖,并且展示命中的 peer 依赖
比如在 @antfu/eslint-config 中,声明的 peer 里面 eslint 的版本是 ^9.10.0。这里命中的是 [email protected]
'@antfu/[email protected](@vue/[email protected])([email protected]([email protected]))([email protected])([email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected])([email protected]))':
dependencies:
'@antfu/install-pkg': 1.1.0
'@clack/prompts': 0.11.0dependencies
在 dependencies 中,记录了当前包的直接 dependencies 依赖,并且展示了 dependencies 下面的包的,真实的 peer 依赖递归关系
比如这一条:
'@eslint-community/eslint-plugin-eslint-comments': 4.5.0([email protected]([email protected]))通过下面的链接可以看到,jiti 是 eslint 的 peer 依赖。 eslint 是 @eslint-community/eslint-plugin-eslint-comments 的 peer 依赖
- https://npmx.dev/package/@eslint-community/eslint-plugin-eslint-comments/v/4.5.0
- https://npmx.dev/package/eslint/v/9.39.1
- https://npmx.dev/package/jiti/v/2.6.1
transitivePeerDependencies
这个字段是 pnpm 生成的,是一个 补丁列表 。它列出了那些“中间层忘了声明,但底层确实需要”的 Peer 依赖,确保深层子依赖能正常工作
transitivePeerDependencies:
- '@eslint/json'
- '@vue/compiler-sfc'
- supports-color
- typescript
- vitest在 pnpm 的严格隔离机制下,一个包只能访问它显式声明的依赖。 如果子孙依赖 B 需要 Peer,但中间的父级 A 没有把这个需求“透传”上来,B 就拿不到根项目的 Peer。
transitivePeerDependencies 就是 pnpm 偷偷开的一个后门:它允许根项目的 @eslint/json 穿透 @antfu/eslint-config 的外壳,直接输送给深层的那个子依赖 B
依赖关联
以这个为例子,看到如下的 version 中 有 shiki 和 vue
shiki-magic-move:
specifier: ^1.0.1
version: 1.0.1([email protected])([email protected]([email protected]))说明一定能在当前文件找到 [email protected] 和 [email protected]([email protected]) 的声明:
[email protected]:
dependencies:
'@shikijs/core': 3.20.0
'@shikijs/engine-javascript': 3.20.0
'@shikijs/engine-oniguruma': 3.20.0
'@shikijs/langs': 3.20.0
'@shikijs/themes': 3.20.0
'@shikijs/types': 3.20.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
[email protected]([email protected]):
dependencies:
'@vue/compiler-dom': 3.5.25
'@vue/compiler-sfc': 3.5.25
'@vue/runtime-dom': 3.5.25
'@vue/server-renderer': 3.5.25([email protected]([email protected]))
'@vue/shared': 3.5.25
optionalDependencies:
typescript: 5.3.3由于 [email protected]([email protected]) 还有一层嵌套,所以我们也能找到 [email protected] 这个声明:
到这里应该就能看懂 pnpm-lock.yaml 的内容结构了
pnpm why
pnpm 提供了 pnpm why 命令来查看某个子依赖包是怎么被哪些包引入的
$ pnpm why vite
Legend: production dependency, optional only, dev only
docs /Users/peterroe/Desktop/i/fe-book (PRIVATE)
dependencies:
vite 7.2.7
vite-plugin-inspect 0.7.42
└── vite 7.2.7 peer
vitest 3.1.3
├─┬ @vitest/mocker 3.1.3
│ └── vite 6.2.1 peer
├── vite 6.2.1
└─┬ vite-node 3.1.3
└── vite 6.2.1
devDependencies:
@antfu/eslint-config 6.2.0
└─┬ @vitest/eslint-plugin 1.4.3
└─┬ vitest 3.1.3 peer
├─┬ @vitest/mocker 3.1.3
│ └── vite 6.2.1 peer
├── vite 6.2.1
└─┬ vite-node 3.1.3
└── vite 6.2.1
unocss 66.5.10
├─┬ @unocss/astro 66.5.10
│ ├─┬ @unocss/vite 66.5.10
│ │ └── vite 7.2.7 peer
│ └── vite 7.2.7 peer
├─┬ @unocss/vite 66.5.10
│ └── vite 7.2.7 peer
└── vite 7.2.7 peer
vitepress 2.0.0-alpha.15
├─┬ @vitejs/plugin-vue 6.0.3
│ └── vite 7.2.7 peer
└── vite 7.2.7排除 peer 依赖 --exclude-peers
pnpm why vite --exclude-peers
Legend: production dependency, optional only, dev only
docs /Users/peterroe/Desktop/i/fe-book (PRIVATE)
dependencies:
vite 7.2.7
vitest 3.1.3
├── vite 6.2.1
└─┬ vite-node 3.1.3
└── vite 6.2.1
devDependencies:
@antfu/eslint-config 6.2.0
└─┬ @vitest/eslint-plugin 1.4.3
└─┬ vitest 3.1.3 peer
├── vite 6.2.1
└─┬ vite-node 3.1.3
└── vite 6.2.1
vitepress 2.0.0-alpha.15
└── vite 7.2.7json 模式展示 --json,可以将结果输出为 json 格式,更加清晰
默认情况下,pnpm why 命令只会查找当前目录所属的包的依赖树,这意味着在 monorepo 中,无法查看所有子包的依赖树
使用可以通过 --recursive 来扫描所有子包的依赖树