对于大多数开发者来说,包管理器通常是项目初期设置一次便可“束之高阁”的工具——直到某个环节出错,或者团队成员使用了不同的管理器,导致你的node_modules目录一团糟。
展望2026年,主流的三个包管理器依旧是npm、Yarn和pnpm。我的建议是:几乎所有新项目都应该优先选择pnpm。以下是具体原因。
快速对比
| 特性 | npm | Yarn (v1/Berry) | pnpm |
|---|---|---|---|
| 速度 | 基线 | 约快2倍 | 约快3倍 |
| 磁盘占用 | 高(复制) | 高/PnP | 极低(硬链接) |
| Monorepo支持 | Workspaces (基础) | Workspaces (成熟) | Workspaces (最佳) |
| 锁文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
| 幽灵依赖 | 是(问题) | 是/否(PnP) | 否(严格) |
| Node内置 | 是 | 否 | 否 |
速度:pnpm 独占鳌头
pnpm采用内容寻址存储(content-addressable store)机制——包在磁盘上只存储一次,并通过硬链接(hard links)引用到各个项目中。
以下是安装速度对比:
- 安装全新 React + TypeScript 项目
npm install: ~18秒yarn install: ~9秒pnpm install: ~5秒
- 第二次安装(缓存预热)
npm install: ~9秒yarn install: ~4秒pnpm install: ~1.5秒
对于每天运行数十次构建的CI/CD流水线而言,这些时间累积起来的节省是非常可观的。
幽灵依赖:pnpm 的杀手级特性
使用npm和Yarn时,你的代码可能会意外地导入package.json中未声明的包——这些包作为传递性依赖(transitive dependencies)被提升(hoisted)到node_modules目录中。
例如:
// 这在npm中可能运行,但在pnpm中会正确地报错:
import { something } from 'a-transitive-dep-you-never-declared'
pnpm使用符号链接(symlinked)的node_modules结构,这意味着你只能导入那些你明确声明在package.json中的包。这能捕获npm/Yarn默默允许的真实bug。
Monorepos:pnpm 再次胜出
pnpm在Monorepo支持方面表现卓越。
一个典型的pnpm-workspace.yaml配置示例:
packages:
- 'apps/*'
- 'packages/*'
pnpm提供了一系列强大的Monorepo命令:
pnpm --filter @myapp/ui build # 构建特定工作区
pnpm --filter @myapp/api add express # 为特定工作区添加依赖
pnpm -r test # 并行运行所有工作区的测试
Turborepo、Nx以及大多数Monorepo工具都与pnpm完美兼容。
何时继续使用 npm
在以下情况下,npm仍然是合理的选择:
- 编写公共 npm 包: 开发者对此最为熟悉,降低贡献门槛。
- 需要零配置的快速脚本: 对于简单脚本,npm的即用性更强。
- 团队成员非主攻 JavaScript: 对于只需“能用就行”的非专业JavaScript开发者团队。
尽管npm v9/10相比旧版本已有显著改进,但它在速度上仍是三者中最慢的。
何时使用 Yarn
- Yarn 1 (Classic): 已进入维护模式。不建议用于新项目。
- Yarn Berry (v2+): 其Plug'n'Play (PnP) 功能彻底消除了
node_modules目录——但工具链的兼容性仍有待提高。仅当你完全致力于实现零安装(zero-install)工作流时,才值得考虑。
从 npm/Yarn 迁移到 pnpm
要开始使用 pnpm,首先全局安装它:
npm install -g pnpm
安装完成后,你就可以在项目中运行pnpm install来管理依赖了。