Skip to content
/ Guide/pnpm
2/9/2026
12.6m
AI 摘要

PNPM 是快速、节省磁盘空间的包管理器。支持 --filter 多包操作、--frozen-lockfile 防止锁文件修改、-P 仅安装 dependencies。pnpm-lock.yaml 包含 lockfile 版本、设置、patchedDependencies、importers、packages、snapshots 等结构,用于精确管理依赖关系。

PNPM

快速、节省磁盘空间的包管理器

常用命令

多个 filter

pnpm --filter pkgA ---filter pkgB build

WARNING

在 pnpm@8 中工作需要额外的参数设置,参考 #6300

不修改 pnpm-lock.yaml

有的时候,拉新项目本地安装依赖的时候,由于 PNPM 版本不对,不启用 corepack 的情况下。会修改 pnpm-lock.yaml 的内容。这个修改通常是意外且无意义的

pnpm install --frozen-lockfile

仅安装 dependencies

我们知道,当安装一个 NPM 包的时候,不会安装其 devDependencies。可以通过指定 -P 参数,实现本地 install 不安装 devDependencies

pnpm install -P

pnpm-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: 16

patchedDependencies

当通过 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.patch

importers

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])

上面的 dependenciesdevDependencies 就是当前项目的直接依赖,和 package.json 中的 dependenciesdevDependencies 是完全对应的。

常规情况

通过观察,我们可以看到声明的版本和实际安装版本的区别 ,例如上面声明的 @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 声明了 peerDependenciespostcss 并且实际的版本为 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/nodepicomatch 呢?

这其实是因为括号里列出的,有可能是其子依赖的 peerDependencies。那这里又有疑问了,netlify-cli 应该是一个很上层的包,如果按照递归的说法,下面多层子依赖,肯定会有很多 peer,不止这里的两个

答案是: 只有“未满足”且需要“向上层索要”的 Peer Dependency 才会出现在这里

Peer Dependency 的解析遵循就近原则。

  1. 子依赖自己满足了:如果 netlify-cli 依赖了 AA 依赖 BB 有 peer dependency C 如果 netlify-cli 或者 Adependencies 里直接声明了 C ,那么这个 peer dependency 就在内部被“消化”解决了。这种情况下,它不需要向上层(也就是你的项目根目录)索要 C 。因此,这个 C 不会 出现在 netlify-cli 的括号里
  2. 向上层索要(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]))

这种情况说明:

  1. splitpanes 依赖树缺少了 [email protected][email protected],需要向上层索要
  2. 这两个 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]

dependencies

在 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 依赖

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.7

json 模式展示 --json,可以将结果输出为 json 格式,更加清晰

默认情况下,pnpm why 命令只会查找当前目录所属的包的依赖树,这意味着在 monorepo 中,无法查看所有子包的依赖树

使用可以通过 --recursive 来扫描所有子包的依赖树

Released under the MIT License.