「这是我参与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 起)。
扁平化的依赖树带来了一系列问题:
- 模块可以访问它们不依赖的包
- 压平依赖树的算法非常复杂
- 一些包必须复制到一个项目的node_modules文件夹中
此外,还有一些 Yarn 不打算解决的问题,比如磁盘空间使用问题。所以我决定继续把我的时间投入到 pnpm 上,并取得了巨大的成功。截至目前(2017 年 3 月),pnpm 拥有 Yarn 超过 npm 的所有附加功能:
- **安全。**与 Yarn 一样,pnpm 有一个包含所有已安装包校验和的特殊文件,用于在执行代码之前验证每个已安装包的完整性。
- **离线模式。**pnpm 将所有下载的包 tarball 保存在本地注册表镜像中。当包在本地可用时,它从不发出请求。使用该
--offline参数可以完全禁止 HTTP 请求。 - **速度。**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@3,node_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
复制代码
如你看到的
- 安装了foo(只是bar)的依赖项,但在目录结构中上一级。
- 两个包都在一个名为node_modules的文件夹中
foo可以要求bar,因为 Node.js 在目录结构中查找模块,直到磁盘的根目录。并且foo也可以要求foo,因为它位于名为node_modules的文件夹中 (是的,这就是某些包所做的)。