npm 从 2009 年起就是 Node.js 的默认包管理器。但项目规模一膨胀, 它的固有问题就藏不住了:磁盘空间浪费、安装缓慢、幽灵依赖。 2017 年出现的 pnpm 用一套全新的依赖管理机制,对这些痛点发起了一次「降维打击」。
下面是结论与关键数据的整理,完整的对比表、实测来源与选择建议请到掘金阅读。
一句话要点
- npm 的扁平化依赖树带来幽灵依赖:代码能访问没在
package.json里声明的包,据统计约 30% 的 npm 漏洞源于此。 - pnpm 用内容寻址存储 + 硬链接 + 符号链接,同一个包在任意项目里只存一份。
- pnpm 的项目只能访问自己声明过的依赖,从架构层面消灭幽灵依赖。
一、依赖管理:扁平化 vs 虚拟存储
npm v3 之后把所有依赖提升到 node_modules 根目录,本意是解决早期嵌套过深导致的
「路径地狱」,但带来两个新麻烦:
- 重复安装:10 个项目共用同一个 lodash,就在磁盘上存 10 份;同名不同小版本还会同时存在。
- 幽灵依赖:项目代码能访问到那些并未声明的依赖包,导致构建行为不可预测。
pnpm 采用内容可寻址存储:所有包统一放在全局 .pnpm-store,
安装时用硬链接链到项目的 node_modules;
再用符号链接构建出精确的依赖关系图。
最终 node_modules 根目录下只有项目直接声明的依赖,其余都待在
.pnpm 的虚拟位置。
还有一个更精妙的细节:同一个包的不同版本之间,只有差异文件才会被新增进存储。 一个 100 文件的包,新版本只改了 1 个文件,那 pnpm 就只多存这 1 个文件。
二、磁盘占用与安装速度
- 实测
create-react-app初始化项目:npm 占 240MB,pnpm 只需 85MB。 - 更极端的场景:npm v8 占 320MB 的项目,pnpm v7 只需 28MB。
- 大型 Monorepo 中,pnpm 的磁盘占用可减少 60% 以上。
- 包含 1200 个依赖的 Monorepo,pnpm 冷安装速度约为 npm 的 2.3 倍。
- 「有缓存 + 有 lockfile + 有 node_modules」的最优场景下:pnpm 493ms vs npm 1.2s。
原因不复杂:pnpm 的核心操作是创建硬链接与符号链接,几乎不涉及文件物理复制,I/O 等待极少。
三、安全性:隔离带来天然优势
npm 虽然提供 audit 检测已知漏洞,但扁平化结构本身扩大了攻击面。
pnpm 的隔离式存储让每个包只能访问自己声明的依赖——即使某个依赖被恶意篡改,
攻击也无法扩散到未声明它的包。此外还内置了哈希校验、签名验证与锁文件完整性三重防护。
四、功能对比
功能 pnpm npm
Workspace(Monorepo) 支持 支持
隔离式 node_modules 默认 不支持
内容可寻址存储 支持 不支持
Plug'n'Play 支持 不支持
依赖补丁(Patching) 支持 不支持
Node.js 版本管理 支持 不支持
依赖清单(Catalogs) 支持 不支持
副作用缓存 支持 不支持
尤其对 Monorepo 的支持,pnpm 的 workspace: 协议能实现精准的依赖控制。
相比 lerna + yarn 的组合,CI 构建时间可缩短 45%,本地开发启动速度提升约 3 倍。
五、什么时候还用 npm
- 小型项目或原型验证:依赖少的时候 pnpm 优势不明显,npm 的简单直接更合适。
- 深度依赖 npm 生态插件的项目:部分工具链对符号链接结构存在兼容性问题。
- 团队还没准备好迁移:迁移有时间成本,要先算清楚投入产出比。
小结
从「扁平化依赖树」到「虚拟存储 + 硬链接」,这不只是工具的迭代, 而是依赖管理范式的升级。 正如 pnpm 的全称——Performant npm,它正在重新定义「高性能包管理」的标准。