为什么要使用 pnpm?

116 阅读4分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

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

多快?*快3倍!*请参阅此处的基准。

为什么效率更高?当您安装软件包时,我们会将其保存在您机器上的全局存储中,然后我们会从中创建一个硬链接,而不是进行复制。对于模块的每个版本,磁盘上只保留一个副本。例如,当使用 npm 或 yarn 时,如果您有 100 个使用 lodash 的包,则磁盘上将有 100 个 lodash 副本。Pnpm 可让您节省数 GB 的磁盘空间!

为什么不是Yarn?

TBH,当 Yarn 公开时,我真的很失望。几个月来,我一直在为 pnpm 做出大量贡献,但没有任何关于 Yarn 的消息。有关其发展的信息未公开。

几天后,我意识到 Yarn 只是对 npm 的一个小小的改进。尽管它使安装速度更快,并且具有一些不错的新功能,但它使用与npm相同的平面node_modules结构(自版本 3 起)。

扁平化的依赖树带来了一系列问题:

  1. 模块可以访问它们不依赖的包
  2. 压平依赖树的算法非常复杂
  3. 一些包必须复制到一个项目的node_modules文件夹中

此外,还有一些 Yarn 不打算解决的问题,比如磁盘空间使用问题。所以我决定继续把我的时间投入到 pnpm 上,并取得了巨大的成功。截至目前(2017 年 3 月),pnpm 拥有 Yarn 超过 npm 的所有附加功能:

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

这怎么可能?

正如我之前提到的,pnpm 不会扁平化依赖树。这样一来,pnpm 使用的算法就可以轻松很多!这就是为什么可能只有 1 个开发人员可以跟上 Yarn 的数十个贡献者的步伐。

那么pnpm如何构造node_modules目录,如果不是通过展平呢?要理解它,我们应该回想一下 npm 版本 3 之前node_modules文件夹的样子。 在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 v3 依赖项解析的更多信息,请参阅npm v3 依赖项解析

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

-> - a symlink (or junction on Windows)

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
复制代码

要查看实时示例,请访问示例 pnpm 项目存储库。

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

首先,您可能已经注意到,node_modules根目录中的包只是一个符号链接。这很好,因为 Node.js 会忽略符号链接并执行真实路径。所以require('foo')将在node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/foo/index.js not in 中执行文件node_modules/foo/index.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(只是bar)的依赖项,但在目录结构中上一级。
  2. 两个包都在一个名为node_modules的文件夹中

foo可以要求bar,因为 Node.js 在目录结构中查找模块,直到磁盘的根目录。并且foo也可以要求foo,因为它位于名为node_modules的文件夹中 (是的,这就是某些包所做的)。