Skip to content

pnpm:更快的包管理器,更安全的依赖管理 ​

发布日期:2026-04-06

pnpm( performant npm)自 2017 年发布以来,已成为前端生态中增长最快的包管理器之一。Vue 3、Vite、pnpm 自身等知名项目都在使用它。本文从原理、优势、实战三个维度全面解析 pnpm。

npm / yarn / pnpm 三代管理器对比 ​

特性npmyarnpnpm
首次安装速度慢中快
再次安装速度慢中极快
磁盘空间占用大很大极小
node_modules 结构扁平嵌套扁平嵌套虚拟存储
幽灵依赖错误 有错误 有正确 严格管控
lockfilepackage-lock.jsonyarn.lockpnpm-lock.yaml
幽灵依赖允许访问未声明依赖允许访问未声明依赖禁止访问未声明依赖

pnpm 的核心原理:硬链接 + 内容寻址存储 ​

pnpm 不会把包复制到每个项目的 node_modules,而是利用文件系统硬链接(Hard Link),让多个项目共享同一份物理文件。

传统方式(npm/yarn)的空间浪费 ​

node_modules/
├── vue/
│   └── ...
├── element-ui/
│   └── node_modules/
│       └── vue/        ← 复制了一份 vue!
└── vant/
    └── node_modules/
        └── vue/        ← 又复制了一份!

同一个包被复制了三次,占用三倍空间。

pnpm 的物理结构 ​

~/.pnpm-store/          ← 全局内容寻址存储(物理文件只存一份)
├── node_modules/
│   ├── vue@3.x.x/
│   │   └── index.js
│   └── ...

project/node_modules/    ← 项目目录(全是硬链接,不占空间)
├── .pnpm/              ← 虚拟存储视图(所有包在这里)
│   ├── vue@3.x.x/
│   ├── element-ui@2.x.x/node_modules/vue → ../../vue@3.x.x/
│   └── vant@3.x.x/node_modules/vue → ../../vue@3.x.x/
└── vue → .pnpm/vue@3.x.x/

关键点:

  • 物理文件在 ~/.pnpm-store/ 只存在一份
  • 项目中通过硬链接指向物理文件
  • 删除项目后,硬链接消失,物理文件还在(其他项目继续用)
  • 物理文件无人引用后,自动被清理

硬链接 vs 软链接 vs 符号链接 ​

bash
# 硬链接:同一个文件的多个目录入口(同一个 inode)
ln file1.txt file2.txt

# 软链接(符号链接):类似快捷方式,独立的 inode
ln -s file1.txt symlink.txt

# pnpm 的做法:硬链接到 store,符号链接到 .pnpm/

pnpm 不用普通符号链接,而用目录符号链接(directory symlinks)来构建包结构:

# element-ui 的真实结构(来自 .pnpm/)
.pnpm/element-ui@2.15.13/node_modules/element-ui/
├── lib/
├── es/
└── dist/
    └── fonts/

# 包的入口在 .pnpm/ 可见,扁平化但隔离

幽灵依赖:npm/yarn 的隐患 ​

"幽灵依赖"指代码中使用了 node_modules 里存在但 package.json 中没有声明的包。

javascript
// element-ui 内部安装了 vue,但你的 package.json 里没写
// npm/yarn 允许这样做,pnpm 不允许

// npm/yarn 下这段代码能跑(幽灵依赖)
import Vue from 'vue'  // 来自 element-ui 的 node_modules

// pnpm 下:错误 报错,找不到 vue
// 必须显式声明在根 package.json 的 dependencies 里

为什么这是隐患?

  1. element-ui 升级后可能换了一个 vue 版本,你的代码行为可能改变
  2. 删掉 element-ui 后,代码突然报错
  3. 依赖不透明,难以维护

pnpm 通过 .pnpm/node_modules/ 的隔离结构,从根本上消灭了幽灵依赖。

快速上手 pnpm ​

安装 ​

bash
# npm 安装
npm install -g pnpm

# 或用 corepack(Node.js 内置)
corepack enable && corepack prepare pnpm@latest --activate

常用命令 ​

bash
pnpm install          # 安装所有依赖
pnpm add vue          # 添加生产依赖
pnpm add -D typescript # 添加开发依赖
pnpm add vue axios     # 同时添加多个
pnpm remove vue       # 移除依赖
pnpm update           # 更新所有
pnpm update vue@latest # 更新指定包
pnpm install --prod   # 只装生产依赖
pnpm install --legacy-peer-deps  # 兼容老包

workspaces: monorepo 一把梭 ​

yaml
# pnpm-workspace.yaml(项目根目录)
packages:
  - 'packages/*'
  - 'apps/*'
  - 'tools/*'
json
// package.json(workspace 根包)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}
bash
# 一键安装所有 workspace 依赖
pnpm install

# 所有子包共享 hoist 优化后的依赖
# 某个包新增依赖后,其他包自动可引用

workspace 依赖管理技巧 ​

bash
# 从本地 workspace 包引用
pnpm add @my/utils --filter @my/web

# filter 指定对哪个包执行操作
pnpm add lodash --filter @my/web

# 查看依赖图
pnpm list --recursive
pnpm list --depth=0  # 只看顶层依赖

pnpm 的安全机制 ​

.npmrc 配置 ​

ini
# 强制使用 pnpm
package-manager = pnpm

# 只从registry下载,不执行生命周期脚本(安全)
enable-pre-post-scripts = false

# hoist 层级控制(类似 yarn berry 的 PnP 但更易用)
public-hoist-pattern[] = *types*
public-hoist-pattern[] = *eslint*
public-hoist-pattern[] = *babel*

# 严格模式(禁止幽灵依赖)
resolution-mode = highest

配置私有仓库 ​

ini
# .npmrc
@myorg:registry=https://npm.mycompany.com/
always-auth=true
//npm.mycompany.com/:_authToken=YOUR_TOKEN

常见问题与解决方案 ​

1. 幽灵依赖报错 ​

ERR_PNPM_MISSING_DEPENDENCY  Vue is not in dependencies

原因:代码里 import 了一个没有在根 package.json 声明的包。

解决:

bash
# 方案一:显式添加依赖
pnpm add vue

# 方案二:如果确实不需要,删除对应的 import 语句

# 方案三:临时降级到宽松模式(不推荐)
shamefully-hoist=true

2. pnpm install 报错 peerDependencies 冲突 ​

bash
# 方案一:安装时忽略 peer 警告
pnpm install --legacy-peer-deps

# 方案二:在 .npmrc 配置默认忽略
ignore-peer-dependencies = true

# 方案三:修复依赖(推荐)
pnpm install --strict-peer-dependencies

3. monorepo 子包找不到依赖 ​

bash
# 子包 package.json
{
  "dependencies": {
    "lodash": "workspace:*"  // 引用 workspace 内最新版
  }
}

4. CI/CD 缓存失效 ​

yaml
# GitHub Actions 示例
- name: Cache pnpm
  uses: actions/cache@v3
  with:
    path: ~/.pnpm-store
    key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
    restore-keys: |
      ${{ runner.os }}-pnpm-

pnpm + Vite 最佳实践 ​

json
// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}
bash
# .npmrc(推荐配置)
shamefully-hoist=true           # 兼容部分旧包
strict-peer-dependencies=false  # 宽松 peer
public-hoist-pattern[]=*        # 全部提升,避免调试困难
json
// tsconfig.json(pnpm 对类型提升的处理)
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

性能对比实测 ​

在同一个 React + TypeScript 项目上测试:

操作npmyarnpnpm
首次安装45s38s12s
再次安装18s22s3s
删除并重装40s35s11s
磁盘占用1.2GB1.4GB280MB

pnpm 在再次安装场景下有巨大优势,因为依赖已经存在于 store,直接硬链接即可。

总结 ​

pnpm 的核心优势:

优势说明
极速安装硬链接复用,重复依赖不重复下载
节省磁盘物理文件一份,多个项目共享
安全隔离严格管控依赖,禁止幽灵依赖
高效 monorepoworkspace 原生支持,依赖共享
兼容性好npm/yarn 命令几乎通用

如果你还没用过 pnpm,建议在新项目中尝试,或者把现有项目迁移过去。pnpm import 可以一键从 npm 或 yarn 迁移 lockfile:

bash
pnpm import

迁移后 .npmrc 加一行 shamefully-hoist=true,基本能无缝兼容。


本文由小虾子 撰写

Last updated: