Skip to content

JS包管理工具对比

npm、Yarn 和 pnpm 都是 JavaScript 包管理工具,但它们在工作原理、性能和特性上有显著区别。

我会从它们的历史演变和核心技术差异两个方面来详细解释。

一、 历史演变与定位

  1. npm (Node Package Manager)

    • 诞生: 2010年,随 Node.js 一同发布,是 JavaScript 的官方包管理工具。
    • 定位: 开山鼻祖,拥有最大、最全的生态系统(registry)。几乎所有包都会优先支持 npm。
    • 版本: 在 npm v5 之前,没有 package-lock.json,依赖版本管理混乱。v5 之后引入了锁文件,稳定性大大提升。
  2. Yarn (Classic)

    • 诞生: 2016年,由 Facebook、Google、Exponent 和 Tilde 联合推出。
    • 背景: 当时 npm 存在性能问题(安装慢)、安全性问题(npm 包可以运行脚本)和确定性不足(没有可靠的锁文件机制)。
    • 创新: 引入了 yarn.lock 锁文件(后来 npm 也学习了这一特性)、离线缓存并行安装,大大提升了安装速度和可靠性。它一度成为更优的 npm 替代品。
  3. pnpm (Performance npm)

    • 诞生: 2017年,旨在解决 npm 和 Yarn 都存在的磁盘空间浪费幽灵依赖问题。
    • 核心创新: 使用内容可寻址存储硬链接/符号链接的独特方式管理依赖项,实现了极高的安装效率和几乎完美的磁盘空间利用。
    • 定位: 更高效、更节省空间、更严格的包管理工具。
  4. Yarn Berry (v2+)

    • 诞生: 2020年,Yarn 的颠覆性重写版本,现在通常被称为 “Modern Yarn"。
    • 创新: 引入了 Plug'n'Play (PnP) 模式(完全抛弃 node_modules)、离线优先、编写器(可扩展性强)、.yarnrc.yml 配置等。
    • 定位: 一个雄心勃勃的、试图重新定义包管理规则的现代化工具,但它的 PnP 模式与某些传统生态工具存在兼容性问题。

二、 核心技术差异对比

为了更直观,我们用一个表格来对比它们最核心的区别:

特性npmYarn (Classic)pnpmYarn Berry (Modern)
锁文件package-lock.jsonyarn.lockpnpm-lock.yamlyarn.lock
安装策略平铺的 node_modules平铺的 node_modules内容可寻址存储 + 符号链接Plug'n'Play (PnP) (默认,无 node_modules)
速度慢 → 较快 (v7+优化后)快 (并行安装)非常快 (硬链接)极快 (无需解包)
磁盘空间浪费 (大量重复依赖)浪费 (大量重复依赖)极其节省 (所有项目共享存储)节省 ( zip 包缓存)
严格性一般一般 (避免幽灵依赖)非常高 (PnP模式强制解析)
安全性一般较好
monorepo 支持内置 workspaces内置 workspaces原生优秀支持 (-r 命令)原生优秀支持 (Workspaces)
主要优势官方标准,生态最全成熟稳定,生态兼容性好性能和空间效率极致现代化,可插拔,离线优先

三、 关键概念详解

1. 平铺的 node_modules (npm & Yarn Classic)

这是 npm 和 Yarn 的传统方式。它们会尽可能地将依赖包“提升”(hoist)到项目 node_modules 的根目录下。

  • 优点: 一定程度上减少了重复。
  • 缺点
    • 幽灵依赖 (Phantom Dependencies): 你可以直接引用一个你并没有package.json 中声明的包(因为它是你某个依赖的依赖,被提升到了顶层)。这非常危险,一旦你的直接依赖不再依赖这个包,你的代码就会立刻报错。
    • 依赖重复 (Nested Dependencies): 如果不同的依赖要求同一个包的不同版本,那么无法被提升的版本还是会重复安装,造成冗余。
    • 不确定性: 依赖提升的规则比较复杂,有时难以预测最终结构。

2. 内容可寻址存储 + 符号链接 (pnpm)

这是 pnpm 的核心魔法。

  • 全局存储 (Content-addressable store): 所有依赖包都会以一个压缩后的格式存储在全局的一个硬盘目录里(例如 ~/.pnpm-store)。同一个版本的包在全电脑只保存一份
  • 硬链接 (Hard links): 当你安装时,pnpm 会从全局存储中硬链接这些文件到项目的 node_modules/.pnpm 目录下。这几乎不占用额外空间,速度极快(类似于创建快捷方式)。
  • 符号链接与嵌套结构 (Symbolic links & nesting): 项目的 node_modules 文件夹里只有你直接声明的依赖(package.json 里写的),它们被符号链接到 .pnpm 里的对应位置。而每个包的依赖项则嵌套在它们自己的目录下(例如 .pnpm/axios@1.0.0/node_modules/)。

带来的好处

  • 极致节省空间: 所有项目共享同一个存储。
  • 速度快: 链接远比下载和解压快。
  • 没有幽灵依赖: 你的代码只能访问到 package.json 中明确定义的依赖,因为其他包都被安全地隔离在 .pnpm 里了。这更严格、更安全。

3. Plug‘n’Play (Yarn Berry)

Yarn Berry 采取了更激进的方式:完全抛弃 node_modules 文件夹

  • 它生成一个 .pnp.cjs 文件,该文件是一个解析映射表,精确地告诉 Node.js 每个包在磁盘上的具体位置(在压缩的 .zip 缓存中)。
  • 优点
    • 极快的安装和启动: 因为不需要创建庞大的 node_modules 目录树(涉及大量 I/O 操作)。
    • 绝对严格: 彻底杜绝幽灵依赖。
  • 缺点
    • 兼容性: 需要整个工具链(如 ESLint、Jest、Webpack 等)都兼容 PnP API。虽然主流工具现在大多支持,但仍可能遇到边缘问题。Yarn 也提供了 nodeLinker: node-modules 选项来回退到传统模式。

四、 如何选择?

  • 新手/小型项目npm。它是内置的,无需额外安装,最简单无脑,生态兼容性100%。
  • 追求稳定和兼容性Yarn Classic。它非常成熟,速度比 npm 快,并且几乎拥有和 npm 一样的生态兼容性。
  • 追求极致的安装速度和节省磁盘空间pnpm这是目前最受推荐的选择。它在 monorepo 场景下表现尤其出色,并且严格性有助于写出更健康的代码。许多大型项目(如 Vue、Vite、Next.js)都已切换至 pnpm。
  • 追求前沿和技术探索Yarn Berry。如果你能解决它可能带来的兼容性问题,它的 PnP 模式和强大的插件系统能带来不一样的开发体验。

总结趋势: pnpm 凭借其出色的性能、严格性和对 monorepo 的优秀支持,正在成为越来越多开发者和团队的首选。Yarn Berry 是一个面向未来的有趣探索。而 npm 和 Yarn Classic 则是稳定可靠的选择。