选择正确的 JavaScript 包管理器可以显著影响你的开发工作流、CI/CD 流水线速度和磁盘使用量。2025 年的四大主要竞争者是 npm、Yarn、pnpm 和 Bun。每个都有不同的理念和权衡取舍。本综合指南从所有重要维度对比这四个工具:速度、磁盘效率、monorepo 支持、安全性和开发者体验。
什么是 JavaScript 包管理器?
包管理器自动化了在项目中安装、更新、配置和删除第三方库(包)的过程。它解析依赖树,通过锁文件管理版本,并提供构建和测试代码的脚本。
每个现代 JavaScript 项目都依赖于一个包管理器。它们之间的差异初看可能很小,但在大型代码库、monorepo 和 CI 流水线中会逐渐累积。让我们深入了解每一个。
npm:默认标准
历史和背景
npm(Node Package Manager)由 Isaac Z. Schlueter 于 2010 年创建,自 Node.js 0.6.3 版本起与 Node.js 捆绑发布。它是每个 Node.js 开发者开箱即用的默认包管理器。npm 注册表托管了超过 200 万个包,是世界上最大的软件注册表。
核心特性
- 与 Node.js 捆绑——零配置,安装了 Node.js 就可用
- npm workspaces——从 npm 7 开始内置 monorepo 支持
- npm ci——为 CI 环境提供确定性的快速安装(删除 node_modules 并从锁文件安装)
- npm audit——内置安全漏洞扫描
- npx——无需全局安装即可执行包
- Overrides——强制指定依赖版本以解决冲突
- Provenance——npm 现在支持包来源证明,增强供应链安全
优点
- 零安装——随 Node.js 一起提供
- 最大的生态系统和社区
- 优秀的文档
- 稳定且经过实战检验
- workspaces 支持 monorepo
- npm audit 安全扫描
缺点
- 四个管理器中安装速度最慢
- 较高的磁盘使用量——每个项目都会复制一份所有依赖
- 历史上存在安全问题(虽然已大幅改善)
- node_modules 可能变得极其庞大("宇宙中最重的物体"梗)
# 安装依赖
npm install
# 添加包
npm install express
# 安装开发依赖
npm install --save-dev typescript
# CI 优化安装
npm ci
# 运行脚本
npm run build
# 不安装直接执行包
npx create-react-app my-appYarn:创新的先驱
历史和背景
Yarn 由 Facebook(现 Meta)于 2016 年创建,旨在解决当时 npm 的不足:非确定性安装、默认无锁文件、性能缓慢。Yarn 引入了 yarn.lock 文件和并行下载,大幅提升了安装速度。如今,Yarn 存在两个版本:Yarn Classic (1.x) 和 Yarn Berry (2.x+),它们本质上是不同的工具。
Yarn Classic (1.x)
Yarn Classic 的工作方式类似于 npm,但性能更好,锁文件更可靠。它使用扁平的 node_modules 目录结构并支持 workspaces 用于 monorepo。虽然仍被广泛使用,但 Yarn Classic 已进入维护模式——团队建议迁移到 Yarn Berry。
Yarn Berry (2.x+) 和 Plug'n'Play
Yarn Berry 是一次完全重写,引入了 Plug'n'Play (PnP),一种彻底消除 node_modules 目录的激进方法。它生成一个 .pnp.cjs 文件,将包名映射到压缩的 .yarn/cache 文件夹中的位置。这带来了:
- 近乎即时的安装——无需将数千个文件复制到 node_modules
- 严格的依赖解析——防止幻影依赖(访问你未声明的包)
- 零安装——将缓存提交到 git,在 CI 中完全跳过 npm install
- 大幅减少磁盘使用
核心特性
- Plug'n'Play (PnP)——消除 node_modules,通过 .pnp.cjs 映射依赖
- 零安装——将缓存提交到仓库,在 CI 中跳过安装
- Workspaces——一流的 monorepo 支持(首创此概念)
- Constraints——在工作区间强制执行规则(例如所有包必须使用相同的 React 版本)
- 插件——通过插件系统扩展架构
- 离线模式——使用缓存的存档在无网络环境下安装包
优点
- PnP 提供最快的安装速度和最小的磁盘占用
- 零安装完全消除 CI 安装步骤
- 出色的 monorepo 支持,包含 workspaces 和 constraints
- 严格的依赖解析能捕获幻影依赖问题
- 插件系统提供扩展性
缺点
- PnP 与某些包和工具存在兼容性问题
- 学习曲线较陡,特别是从 npm 迁移时
- 两个差异很大的版本(Classic vs Berry)容易混淆
- 社区比 npm 小
- 某些 IDE 集成需要额外配置才能支持 PnP
# 安装依赖
yarn install
# 添加包
yarn add express
# 添加开发依赖
yarn add --dev typescript
# 运行脚本
yarn build
# 交互式升级
yarn upgrade-interactive
# 启用 PnP(Yarn Berry)
yarn set version berry
yarn installpnpm:磁盘空间冠军
历史和背景
pnpm(performant npm)由 Zoltan Kochan 于 2017 年创建,旨在解决 npm 和 Yarn Classic 固有的磁盘空间问题。它的核心创新是内容寻址存储系统:每个包的每个版本在磁盘上只存储一次,项目通过链接指向这些共享副本。如果 10 个项目使用 lodash@4.17.21,你的机器上只有一份副本。
内容寻址存储详解
当你运行 pnpm install 时,pnpm 不会将包复制到每个项目的 node_modules 中。相反,它创建一个全局存储(通常在 ~/.local/share/pnpm/store),每个唯一文件按其内容哈希存储。你项目的 node_modules 包含指向此存储的硬链接(或 Windows 上的符号链接)。这意味着:
- 大量磁盘节省——包只全局存储一次,而非每个项目一份
- 更快的安装——文件是链接的,不是复制的
- 严格的 node_modules 结构——默认防止幻影依赖
- 完整性验证——内容寻址意味着文件通过哈希验证
核心特性
- 内容寻址存储——全局存储加硬链接,大幅节省磁盘
- 默认严格模式——只有声明的依赖才可访问(无幻影依赖)
- pnpm workspaces——出色的 monorepo 支持,包含过滤和并行执行
- 副作用缓存——缓存 postinstall 脚本输出以加速后续安装
- 补丁——内置
pnpm patch命令,无需 fork 即可修补依赖 - Catalogs——在工作区包之间定义共享依赖版本
优点
- 最佳磁盘空间效率——在多项目机器上节省数 GB 空间
- 快速安装(特别是有热存储的第二次安装)
- 默认严格依赖解析
- 出色的 monorepo 工具,支持过滤命令
- npm 的直接替代品(类似的 CLI 命令)
- 积极开发和不断增长的社区
缺点
- 需要单独安装(未与 Node.js 捆绑)
- 硬链接可能使某些工具和文件监视器混淆
- 严格模式可能破坏依赖幻影依赖的包
- 社区和生态系统比 npm 或 Yarn 小
- 某些托管平台没有内置 pnpm 支持
# 安装 pnpm
npm install -g pnpm
# 或使用 corepack(推荐)
corepack enable
corepack prepare pnpm@latest --activate
# 安装依赖
pnpm install
# 添加包
pnpm add express
# 添加开发依赖
pnpm add -D typescript
# 运行脚本
pnpm run build
# 检查磁盘使用节省
pnpm store statusBun:全能运行时
历史和背景
Bun 由 Jarred Sumner 创建,于 2023 年 9 月发布 1.0 版本。与其他三个不同,Bun 不仅仅是包管理器——它是一个全能 JavaScript/TypeScript 运行时,包含包管理器、打包器、测试运行器和转译器。使用 Zig 从头构建(使用 JavaScriptCore 而非 V8),Bun 专为最大性能而设计。
核心特性
- 极速安装——用原生代码编写,通常比 npm 快 10-25 倍
- 全能运行时——一个工具替代 Node.js、npm、webpack/esbuild 和 Jest
- 原生 TypeScript 支持——直接运行 .ts 文件,无需编译步骤
- npm 兼容——读取 package.json,使用 node_modules,兼容 npm 注册表
- 内置打包器——为生产环境打包 JavaScript/TypeScript
- 内置测试运行器——使用 bun test 进行 Jest 兼容的测试
- 热重载——使用 --hot 标志的内置监视模式
- Node.js 兼容——实现大多数 Node.js API 以进行直接替换
优点
- 包安装速度最快,优势显著
- 全能工具减少工具链复杂性
- 原生 TypeScript 执行,无需配置
- 大多数项目可直接替换 Node.js
- 内置打包器和测试运行器
- 积极开发,快速改进
缺点
- 相对较新——在生产环境中的实战检验较少
- 不是 100% Node.js 兼容(某些边缘情况和原生模块可能不工作)
- 社区较小,Stack Overflow 答案较少
- Windows 支持是后来添加的,可能存在一些粗糙之处
- Monorepo 支持不如 pnpm 或 Yarn 成熟
- 某些带有原生插件的 npm 包可能无法用 Bun 编译
# 安装 Bun
curl -fsSL https://bun.sh/install | bash
# 或在 Windows 上
powershell -c "irm bun.sh/install.ps1 | iex"
# 安装依赖
bun install
# 添加包
bun add express
# 添加开发依赖
bun add --dev typescript
# 运行脚本
bun run build
# 直接运行 TypeScript 文件
bun run server.ts
# 运行测试
bun test并排对比
| 特性 | npm | Yarn (Berry) | pnpm | Bun |
|---|---|---|---|---|
| 首次发布 | 2010 | 2016(Berry:2020) | 2017 | 2023(1.0) |
| 编写语言 | JavaScript | JavaScript | JavaScript | Zig + C++ |
| 安装速度(冷启动) | 慢 | 快(PnP:非常快) | 快 | 非常快 |
| 磁盘使用 | 高(每项目复制) | 低(PnP)/ 高(Classic) | 非常低(共享存储) | 中等(node_modules) |
| 锁文件 | package-lock.json | yarn.lock | pnpm-lock.yaml | bun.lockb(二进制) |
| Monorepo 支持 | Workspaces(基础) | Workspaces + Constraints | Workspaces + 过滤 | Workspaces(基础) |
| 幻影依赖防护 | 否 | 是(PnP) | 是(默认严格) | 否 |
| Node.js 捆绑 | 是 | 否(使用 corepack) | 否(使用 corepack) | 否(自身是运行时) |
| 安全审计 | npm audit(内置) | yarn audit | pnpm audit | 未内置 |
| 离线模式 | 基于缓存 | 零安装(优秀) | 基于存储 | 基于缓存 |
| 依赖补丁 | 通过 overrides | yarn patch | pnpm patch | patch-package |
安装速度基准测试
以下基准测试基于典型的中型项目(约 500 个依赖),在现代机器上运行。时间为近似值,会因网络条件、缓存状态和硬件而异。
| 场景 | npm | Yarn (Berry) | pnpm | Bun |
|---|---|---|---|---|
| 冷安装(无缓存) | ~45s | ~25s | ~20s | ~5s |
| 热安装(有缓存) | ~20s | ~1s(零安装) | ~5s | ~2s |
| CI 安装(干净环境) | ~35s(npm ci) | ~15s | ~12s | ~4s |
| 添加单个包 | ~8s | ~4s | ~3s | ~1s |
注意:基准测试仅供参考。请务必用你自己的项目进行测试,因为结果会因依赖树形状、网络速度和操作系统而有显著差异。
迁移指南
在包管理器之间切换通常很简单。以下是每条迁移路径的关键步骤。
从 npm 迁移到 pnpm
pnpm 被设计为 npm 的直接替代品,迁移很简单:
# Step 1: Install pnpm
corepack enable
corepack prepare pnpm@latest --activate
# Step 2: Remove npm artifacts
rm -rf node_modules package-lock.json
# Step 3: Install with pnpm
pnpm install
# Step 4: Update scripts in package.json
# Replace "npm run" with "pnpm run" in CI configs
# Step 5: Add packageManager field
# "packageManager": "pnpm@9.x.x"从 npm 迁移到 Yarn Berry
迁移到 Yarn Berry 需要更多步骤,特别是如果你想使用 PnP:
# Step 1: Enable Yarn via corepack
corepack enable
yarn set version berry
# Step 2: Remove npm artifacts
rm -rf node_modules package-lock.json
# Step 3: Install with Yarn
yarn install
# Step 4: Configure PnP (optional but recommended)
# Add to .yarnrc.yml:
# nodeLinker: pnp
# Step 5: Add SDK for your editor
yarn dlx @yarnpkg/sdks vscode
# Step 6: Add to .gitignore
# .yarn/*
# !.yarn/cache
# !.yarn/patches
# !.yarn/plugins
# !.yarn/releases
# !.yarn/sdks
# !.yarn/versions从 npm 迁移到 Bun
Bun 读取你现有的 package.json,可以直接使用 npm 的注册表:
# Step 1: Install Bun
curl -fsSL https://bun.sh/install | bash
# Windows: powershell -c "irm bun.sh/install.ps1 | iex"
# Step 2: Remove npm artifacts
rm -rf node_modules package-lock.json
# Step 3: Install with Bun
bun install
# Step 4: Update scripts
# Replace "node" with "bun" in scripts
# Replace "npm run" with "bun run"
# Replace "npx" with "bunx"
# Step 5: Test everything
bun test
bun run build通用迁移建议
- 充分测试——迁移后运行完整的测试套件
- 更新 CI/CD——更新流水线配置以使用新的包管理器
- 锁文件——在生成新锁文件之前删除旧锁文件;永远不要有两个锁文件
- 团队对齐——确保团队中的每个人同时切换
- corepack——使用
corepack enable在团队中一致管理包管理器版本 - Engines 字段——在 package.json 中添加
packageManager字段以强制使用正确的包管理器
何时使用哪个:建议
使用 npm 当...
- 你想要零配置——npm 随 Node.js 提供,无需额外安装
- 你在处理中小型项目,安装速度不是关键
- 你是初学者,想要最大的社区来解决问题
- 你的团队不想学习新工具
- 你需要与教程、指南和 Stack Overflow 答案的最广泛兼容性
使用 Yarn Berry 当...
- 你想要最快的 CI 流水线,使用零安装
- 你运行大型 monorepo,需要 constraints 来强制一致性
- 你想要严格的依赖解析来捕获幻影依赖问题
- 你的团队愿意投入时间学习 PnP 并解决兼容性问题
- 离线开发能力对你的工作流很重要
使用 pnpm 当...
- 磁盘空间是一个考虑因素——你在一台机器上有很多项目
- 你想要一个比 npm 更快更高效的直接替代品
- 你运行 monorepo,想要强大的过滤和并行执行
- 你想要严格的依赖解析,但不想要 PnP 的学习曲线
- 你正在为生产构建,想要一个经过验证的稳定工具
使用 Bun 当...
- 原始速度是你的首要优先级
- 你想要一个全能工具(运行时 + 包管理器 + 打包器 + 测试运行器)
- 你正在启动一个新项目,可以容忍一些兼容性边缘情况
- 你正在构建 TypeScript 项目,想要原生 TS 执行
- 你愿意成为早期采用者,帮助塑造生态系统
对于 2025 年的大多数团队,pnpm 在速度、磁盘效率和兼容性方面提供了最佳平衡。如果你从头开始并想要最前沿的性能,Bun 值得评估。如果你已经使用 npm 且一切正常,没有紧迫的切换需要——npm 在最近的版本中已经显著改善。
使用 Corepack 管理包管理器
Corepack 是从 Node.js v16.9.0 起捆绑的工具,让你无需全局安装即可管理 Yarn 和 pnpm 版本。它确保团队中的每个人都使用完全相同版本的包管理器。
以下是使用 Corepack 的方法:
# Enable corepack (bundled with Node.js 16.9+)
corepack enable
# Use a specific Yarn version
corepack prepare yarn@4.1.0 --activate
# Use a specific pnpm version
corepack prepare pnpm@9.0.0 --activate
# Verify the active version
yarn --version
pnpm --version在 package.json 中添加 packageManager 字段以强制使用特定版本:
{
"name": "my-project",
"version": "1.0.0",
"packageManager": "pnpm@9.1.0",
"engines": {
"node": ">=18.0.0"
}
}常见问题
pnpm 比 npm 快吗?
是的,pnpm 在大多数场景下比 npm 快得多。冷安装时,pnpm 通常比 npm 快 2-3 倍。热安装时(当全局存储已有包时),pnpm 可以快 4-5 倍,因为它只需要创建硬链接而不是复制文件。在共享相同依赖的多项目机器上,速度优势更加明显。
Bun 可以作为 Node.js 的直接替代品吗?
对于大多数应用,是的。Bun 实现了大部分 Node.js API,包括 fs、path、http、crypto 等。但是,一些原生 Node.js 模块(使用 node-gyp/N-API)可能不工作,Node.js API 中还有一些 Bun 尚未实现的边缘情况。在将生产应用从 Node.js 切换到 Bun 之前,务必充分测试。
什么是幻影依赖,为什么它很重要?
幻影依赖是你的代码导入但未在 package.json 中列出的包,它恰好存在于 node_modules 中,因为它是你某个已声明依赖的依赖。这在 npm 的扁平 node_modules 结构中碰巧可以工作。当间接依赖被更新或删除时就会出问题,意外地破坏你的代码。pnpm 和 Yarn PnP 默认防止幻影依赖,使你的依赖树显式且可靠。
应该将锁文件提交到 git 吗?
绝对应该。锁文件(package-lock.json、yarn.lock、pnpm-lock.yaml 或 bun.lockb)确保每个开发者和 CI 服务器安装完全相同的依赖版本。没有它,你可能会遇到由不同依赖解析导致的"在我机器上可以工作"的 bug。始终提交你的锁文件,永远不要将其添加到 .gitignore。
可以在用 npm 设置的项目中使用 pnpm 或 Yarn 吗?
可以。pnpm 和 Yarn 都可以读取你现有的 package.json。要迁移,删除 node_modules 和旧锁文件(package-lock.json),然后运行新包管理器的安装命令(pnpm install 或 yarn install)。新工具会生成自己的锁文件。迁移后充分测试你的应用以确保一切正常。
为什么 Bun 的锁文件是二进制而不是文本?
Bun 使用二进制锁文件(bun.lockb)是为了性能——二进制文件比基于文本的格式(如 JSON 或 YAML)读写更快。代价是你不能在 PR 中轻松查看或对比它。你可以使用 bun install --yarn 生成 yarn.lock 风格的可读文本文件,或使用 bun.lockb --hash 验证完整性。从 Bun 1.1+ 开始,你还可以使用 bun install --save-text-lockfile 生成基于文本的 bun.lock 文件。