pnpm 的出现解决了什么问题?

1,962 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

pnpm 是 Node.js 的替代包管理器。它是 npm 的直接替代品,但速度更快、效率更高。

为什么更有效率?当你安装一个包时,我们将它保存在你电脑上的全局存储中,然后我们从它创建一个硬链接而不是复制。也就是说:对于模块的每个版本,磁盘上只保留一份副本。例如,当使用 npm 或 yarn 时,如果您有 100 个使用 lodash 的包,您将在磁盘上拥有 100 个 lodash 副本。Pnpm 可以让你节省千兆字节的磁盘空间!

为什么不是 yarn ?

事实上 yarn 只是对 npm 的一个小改进。尽管它使安装速度更快并且具有一些不错的新功能,但它却使用了和 npm 相同的扁平化 node_modules 结构。

扁平化的 node_modules 结构带来了很多问题:

  1. 模块可以访问它们不依赖(没有进行声明)的包 (这极其不合理!)
  2. 扁平化依赖的算法非常复杂
  3. 一些包必须复制到一个项目的 node_modules 文件夹中(太浪费空间了!)

截至目前,pnpm 具有 yarn 优于 npm 的所有优点(是的,是所有):

  1. 安全: 与 Yarn 一样,pnpm 有一个特殊文件,其中包含所有已安装包的校验和,用于在执行其代码之前验证每个已安装包的完整性。
  2. 离线模式: pnpm 将所有下载的包 tarball 保存在本地注册表镜像中。当包在本地可用时,它从不发出请求。使用该--offline参数,可以完全禁止HTTP 请求。
  3. 速度: pnpm 不仅比 npm 快,而且比 Yarn 快。无论是冷缓存还是热缓存,它都比 Yarn 快。Yarn 从缓存中复制文件,而 pnpm 只是从全局存储中链接它们。

为什么?

正如我之前提到的,pnpm 不会扁平化依赖结构。因此,pnpm 使用的算法可以简单很多。

那么 pnpm 如何构建 node_modules 目录,而不是通过扁平化它呢?要理解它,我们应该回忆一下 node_modules 文件夹在 npm@3 之前的样子。在 npm@3 之前,node_modules 结构是可预测的,因为 node_modules 中的每个依赖项都有自己的 node_modules 文件夹,其中指定了所有依赖项包 .json 。

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

这种方法有两个严重的问题:

  • 包经常创建太深的依赖树,这导致 Windows 上的目录路径过长(直到出错)
  • 当在不同的依赖项中需要时,包被复制粘贴了几次

为了解决这些问题,npm 重新考虑了 node_modules 结构并提出了扁平化。使用 npm@3node_modules 结构现在看起来像这样:

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

与 npm@3 不同,pnpm 试图解决 npm@2 存在的问题,而不扁平化依赖树。在 pnpm 创建的 node_modules 文件夹中,所有包都有自己的依赖项组合在一起,但目录树永远不会像 npm@2 那样深。pnpm 保持所有依赖关系平坦,但使用软连接将它们组合在一起。


node_modules
├─ foo -> .registry.npmjs.org/foo/1.0.0/node_modules/foo
└─ .registry.npmjs.org
   ├─ foo/1.0.0/node_modules
   |  ├─ bar -> ../../bar/2.0.0/node_modules/bar
   |  └─ foo
   |     ├─ index.js
   |     └─ package.json
   └─ bar/2.0.0/node_modules
      └─ bar
         ├─ index.js
         └─ package.json

虽然这个例子对于一个小项目来说似乎太复杂了,但对于更大的项目来说,这个结构看起来比 npm/yarn 创建的结构更好。让我们看看它为什么有效。

首先,你可能已经注意到, node_modules 根目录中的包只是一个符号链接。这很好,因为 Node.js 会忽略符号链接并执行真实路径。

其次,所有已安装的包在其目录中都没有自己的 node_modules 文件夹。那么 foo 怎么找到 bar 呢?让我们看一下包含 foo 包的文件夹:

node_modules/.registry.npmjs.org/foo/1.0.0/node_modules
├─ bar -> ../../bar/2.0.0/node_modules/bar
└─ foo
   ├─ index.js
   └─ package.json

如你看到的:

  1. foo 的依赖项在目录结构中更上一层。
  2. 这两个包都在 *node_modules 文件夹中

foo 可能需要 bar,因为 Node.js 在目录结构中查找模块直到磁盘的根目录。foo 也可以 require foo,因为它就在 node_modules 中。

尝试一下吧

你只需要通过 npm 安装 pnpm:执行 npm install -g pnpm。这样以后就不需要用 npm 啦。