一、核心原理与核心差异
1. 依赖存储方式(最根本的区别)
-
npm:
- 采用嵌套/扁平化混合的依赖树结构(npm3+ 开始扁平化)。
- 每个项目的
node_modules里会完整复制所有依赖包(包括子依赖),即使不同项目依赖同一个版本的包,也会重复存储。 - 例如:项目 A 和项目 B 都依赖 lodash@4.17.21,npm 会在两个项目的
node_modules里各存一份,磁盘占用高。
-
pnpm:
- 核心是内容寻址的存储(Content-addressable storage) + 硬链接+符号链接。
- 所有下载的包会统一存储在电脑的全局仓库(默认路径:
~/.pnpm-store),不同项目通过硬链接 复用全局仓库的包文件,通过符号链接构建扁平化的依赖树。 - 例如:多个项目依赖同一个版本的 lodash,全局仓库只存一份,所有项目通过硬链接引用,磁盘占用极低。
2. 性能对比
| 维度 | npm | pnpm |
|---|---|---|
| 安装速度 | 较慢(重复下载/复制文件) | 极快(复用全局缓存,仅创建链接) |
| 更新速度 | 中等 | 更快(仅更新变更的依赖) |
| 磁盘占用 | 高(重复存储) | 极低(全局复用) |
3. 其他关键差异
| 特性 | npm | pnpm |
|---|---|---|
| 依赖隔离 | 弱(扁平化可能导致幽灵依赖) | 强(严格的依赖树,无幽灵依赖) |
| 工作区(monorepo) | 支持但体验一般(npm workspaces) | 原生深度支持,体验更优 |
| 兼容性 | 完全兼容 npm 规范 | 基本兼容,极少数边缘场景需适配 |
| 命令兼容性 | 作为标准,pnpm 兼容大部分 npm 命令 | 兼容 npm install/npm run 等核心命令,可直接替换 |
| 锁文件 | package-lock.json | pnpm-lock.yaml(结构更清晰) |
二、实操层面的差异(新手易感知)
1. 安装命令
bash
# npm 安装依赖
npm install
# pnpm 安装依赖(命令更简洁)
pnpm install # 或简写 pnpm i
2. 全局安装
bash
# npm 全局安装
npm install -g pkg-name
# pnpm 全局安装(更安全,不会污染全局环境)
pnpm add -g pkg-name
3. 避免幽灵依赖
- npm 问题:即使项目没直接依赖某个包,也可能通过依赖的依赖访问到(幽灵依赖),导致项目依赖不清晰。
- pnpm 优势 :
node_modules结构严格,只有项目package.json里声明的依赖才能被访问到,从根源避免幽灵依赖,提升项目稳定性。
三、适用场景
- 选 npm :
- 项目需要完全兼容老旧 Node.js 版本;
- 团队对包管理工具认知单一,不想学习新工具。
- 选 pnpm :
- 追求更快的安装速度、更低的磁盘占用;
- 开发 monorepo 项目(多包管理);
- 希望依赖结构更清晰、避免幽灵依赖;
- 团队需要统一高效的包管理规范。
总结
- 核心差异:pnpm 用全局缓存+硬链接/符号链接存储依赖,npm 则在每个项目重复存储,这是 pnpm 速度更快、更省磁盘的根本原因;
- 依赖规范:pnpm 严格隔离依赖,避免幽灵依赖,npm 依赖结构松散,兼容性更好但规范性弱;
- 实操建议:新项目优先用 pnpm,老项目若需兼容可保留 npm,两者核心命令基本兼容,迁移成本低。