pnpm 的好处 (Benefits of pnpm)
一句话理解
pnpm(Performant npm)是 Node.js 生态中的包管理器。它与 npm 使用相似的命令,但重新设计了依赖包的存储、链接和项目内布局,因此在磁盘占用、安装速度、依赖隔离和 monorepo 管理方面有明显特点。
pnpm 的核心价值不是“命令更短”,而是让多个项目共享依赖文件,同时让项目只能使用自己明确声明的依赖。
1. 内容寻址存储:更省磁盘
npm 常见的做法是把依赖包复制到每个项目的 node_modules 中。如果电脑上有很多项目,它们可能会重复保存大量相同文件。
pnpm 使用内容寻址存储(Content-Addressable Storage, CAS):
全局 store
├── react 的文件
├── lodash 的文件
└── 其他依赖的文件
项目 A/node_modules ──链接──> 全局 store
项目 B/node_modules ──链接──> 全局 store安装包时,pnpm 将文件保存到全局 store,项目中的 node_modules 通过 hard link 或 reflink 使用这些文件,而不是再次复制完整内容。
这带来几个结果:
- 多个项目使用同一版本依赖时可以复用文件;
- 项目越多,重复占用的磁盘空间越少;
- 同一包的不同版本之间也可以复用没有变化的文件;
- 已经下载过的依赖再次安装时,通常可以跳过下载和完整复制。
可以用下面的命令查看 store 位置:
pnpm store path2. 默认依赖隔离:减少幽灵依赖
传统扁平化 node_modules 容易出现幽灵依赖(Phantom Dependency):项目没有在 package.json 中声明某个包,但因为另一个传递依赖把它提升到了可访问的位置,项目却意外地可以 import 它。
这种代码可能暂时能运行:
// package.json 没有声明 lodash
import _ from 'lodash'但当上游依赖升级、依赖树重新排列时,代码就可能突然失效。
pnpm 默认采用更严格的依赖布局。一个包通常只能访问自己 package.json 中声明的依赖,因此依赖声明不完整的问题会更早暴露。
这种严格性提高了项目的可维护性:
package.json更接近真实依赖关系;- 升级传递依赖时更不容易出现隐性破坏;
- 新成员可以通过依赖声明理解项目需要什么;
- 构建环境和本地环境之间更容易保持一致。
如果某个旧工具确实依赖扁平结构,可以使用:
nodeLinker: hoisted但这会牺牲一部分隔离性,应当把它当作兼容性选项,而不是默认方案。
3. 安装速度:缓存命中后尤其明显
pnpm 的安装速度优势主要来自两个方面:
- 依赖包在全局 store 中已经存在时,不需要重复下载;
- 项目安装主要进行链接,而不是把所有文件再次复制到项目目录。
实际速度受网络、缓存命中率、依赖数量、磁盘和杀毒软件影响。官方 benchmark 在部分场景中显示 pnpm 可以比 npm 和 Yarn Classic 更快,但不能把“最高两倍”理解成任何项目都固定快两倍。
常用操作:
pnpm install
pnpm add react
pnpm remove react
pnpm update在 CI 中,通常使用:
pnpm install --frozen-lockfile这会按照提交到仓库中的 pnpm-lock.yaml 安装,并在清单和锁文件不一致时失败,而不是悄悄改写锁文件。
4. Workspace:适合 monorepo
pnpm 原生支持 workspace,适合在一个仓库中管理多个应用和共享包:
repo/
├── apps/
│ ├── web/
│ └── api/
├── packages/
│ ├── ui/
│ └── config/
├── package.json
├── pnpm-workspace.yaml
└── pnpm-lock.yaml常用命令:
pnpm --filter web dev
pnpm --filter api test
pnpm --filter './packages/*' build
pnpm -r lintWorkspace 能力包括:
- 自动识别仓库中的多个包;
- 在本地链接 workspace 内部依赖;
- 使用
workspace:协议表达内部依赖; - 通过
--filter只操作目标项目及其依赖; - 使用
-r对多个项目递归执行脚本; - 使用 catalog 集中维护一组依赖版本;
- 使用
pnpm deploy生成部署用目录。
相比单独维护多个 npm 项目,workspace 能减少重复配置,也能让包之间的依赖关系更清晰。
5. Lockfile 和版本固定
pnpm 使用 pnpm-lock.yaml 记录实际解析出的依赖版本、完整依赖图和完整性信息。
团队可以在 package.json 中固定包管理器版本:
{
"packageManager": "pnpm@11.22.0"
}固定版本可以减少以下问题:
- 不同开发者生成不同的 lockfile;
- CI 和本地解析结果不同;
- 包管理器自身升级带来意外行为变化;
- workspace 的安装结果不一致。
迁移到 pnpm 后,应该提交 pnpm-lock.yaml,并尽量不要在同一个项目中交替使用 npm、Yarn 和 pnpm 修改依赖。
6. 命令接近 npm,迁移成本低
pnpm 保留了许多熟悉的命令:
| npm | pnpm |
|---|---|
npm install | pnpm install |
npm install axios | pnpm add axios |
npm install -D vite | pnpm add -D vite |
npm uninstall axios | pnpm remove axios |
npm run dev | pnpm run dev 或 pnpm dev |
npx vite | pnpm dlx vite |
npm exec | pnpm exec |
因此,迁移的主要学习成本不是命令,而是理解全局 store、非扁平 node_modules、workspace 和 filter。
7. 需要接受的代价
pnpm 的优势来自它不同于 npm 的存储和链接模型,所以也有相应代价:
- 一些假设扁平
node_modules的旧工具可能需要额外配置; - Windows 上文件链接可能受到权限、跨磁盘和杀毒软件占用的影响;
- 团队需要统一包管理器和 lockfile;
- 第一次迁移可能暴露项目长期隐藏的漏声明依赖;
- 需要让开发者理解
pnpm store和.pnpm目录,而不是把node_modules当作普通复制目录。
这些通常不是 pnpm 的缺陷,而是更严格、更高效的模型与旧生态假设之间的兼容成本。
适合使用 pnpm 的场景
pnpm 特别适合:
- 同一台电脑上维护多个 Node.js 项目;
- 大型前端项目;
- monorepo 和多包仓库;
- 需要控制磁盘占用的开发环境;
- 重视 lockfile 和可复现安装的团队;
- 希望尽早发现未声明依赖的项目。
单个很小的项目使用 npm 也完全合理;pnpm 的收益会随着项目数量、依赖规模和 workspace 复杂度增加而变得更明显。
迁移的最小流程
从已有 npm 项目迁移时,可以在分支或项目副本中操作:
pnpm import
pnpm install
pnpm test
pnpm buildpnpm import 可用于从已有的 package-lock.json 或 yarn.lock 导入依赖信息。确认测试和构建通过后,提交 pnpm-lock.yaml,并移除团队不再使用的旧 lockfile,避免后续工具选择错误。
总结
pnpm 的主要收益可以概括为:
- 用内容寻址存储和链接机制减少重复文件;
- 用严格依赖布局减少幽灵依赖;
- 用全局缓存和链接提升安装效率;
- 用 workspace、filter 和 catalog 管理 monorepo;
- 用 lockfile 和版本固定提高可复现性。
如果只看命令,pnpm 和 npm 很像;如果看底层模型,pnpm 更像是一套针对现代 Node.js 多项目开发重新设计的依赖管理系统。
参考
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/nodejs