为什么我们应该使用pnpm?

1. 前言

原文: Why should we use pnpm?

Author: Zoltan Kochan

译者:philoenglish.com 团队

关键字: pnpm, npm, yarn

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

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

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

2. 为什么不使用yarn?

说实话,当 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 只是从全局存储中链接它们。

3. 这是如何成为可能的

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

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

1
2
3
4
5
6
7
8
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json

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

包经常创建太深的依赖树,这导致 Windows 上的长目录路径问题
当不同的依赖项需要包时,它们会被复制粘贴多次
为了解决这些问题,npm 重新思考了node_modules结构并提出了扁平化。使用npm@3 ,node_modules结构现在如下所示:

1
2
3
4
5
6
7
8
9

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 保持所有依赖关系平坦,但使用符号链接将它们分组在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

-> - 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 而不是node_modules/foo/index.js

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

1
2
3
4
5
6
7

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

4. 您是否会使用pnpm?

如果我的观点说服了, 并打算使用pnpm.

只需通过 npm: 安装 pnpm 即可npm install -g pnpm。每当你想安装某些东西时,请使用它而不是 npm:pnpm i foo

您还可以在pnpm GitHub 存储库pnpm.js.org阅读更多信息。您可以在 Twitter 上关注pnpm on Twitter或在pnpm Gitter 聊天室寻求帮助。

5. Nodejs 系列文章

最新更新以及更多Nodejs相关文章请访问 鹏叔的技术博客 - Nodejs

6. 参考文章

Why should we use pnpm?