为什么我们应该使用 pnpm?

原文链接:Why should we use pnpm?[1],2017.03.19,by Zoltan Kochan

为什么我们应该使用 pnpm?

pnpm 是 Node.js 的一种替代包管理器,它可以完全取代 npm,而且更快、更高效。

有多快呢?比 npm 快 3 倍!可以在这里查看基准测试结果[2](benchmarks)。

为什么更高效呢?在你安装一个包时,我们会将其保存在你机器上的全局存储(global store)中,并通过创建硬链接(hard link)来引用它,而不是通过文件复制方式。对于每个模块的版本,磁盘上只保留唯一一个副本。然而,使用 npm 或 yarn 时,如果有 100 个包都依赖 lodash 库,那么磁盘上就会 100 份 lodash 的拷贝。pnpm 能够帮助你节省数 GB 的磁盘空间!

为什么不用 Yarn 呢?

说实话,当 Yarn 公开发布时,我真的感到非常失望。我在这几个月里一直积极地为 pnpm 做贡献,但关于 Yarn 的任何消息都没有出现过,它的开发信息并不对外公开。

几天后,我意识到 Yarn 只是相对于 npm 的一个小改进。虽然它可以加快安装速度,并且有一些很好的新功能,但它使用了与 npm 相同的扁平化 node_modules 结构(自版本 3 开始)。

而扁平化依赖树(flattened dependency trees)结构也带来了一系列问题:

  1. 可以在模块中访问到没有依赖的包(即没有写在 package.json 文件中的依赖)
  2. 扁平化依赖树的算法非常复杂
  3. 某些包必须被复制到一个项目node_modules 文件夹中

此外,Yarn 并不打算解决一些问题,比如磁盘空间的使用问题。因此我决定继续投入时间来支持 pnpm,并且取得了巨大的成功。截至目前(2017 年 3 月),pnpm 已经具备了 Yarn 相对于 npm 的所有附加功能:

  • 「安全性」。与 Yarn 类似,pnpm 有一个特殊文件,其中包含所有已安装软件包的校验码(checksums),方便在执行代码前验证每个已安装软件包的完整性
  • 「离线模式」。pnpm 将所有下载的软件包 tarball 保存在本地注册表镜像中(local registry mirror)。当本地存在某个软件包时,它永远不会发出请求。通过使用 --offline 参数,可以完全禁止 HTTP 请求
  • 「速度快」。pnpm 不仅比 npm 更快,而且比 Yarn 更快,无论是冷启动(cold)还是热启动(hot cache)。Yarn 会从缓存中复制文件,而 pnpm 只需从全局存储库中链接它们。

怎么可能呢?

正如我之前提到的,pnpm 不会将依赖树展平。因此,pnpm 使用的算法要简单得多!这就是为什么只有 1 个开发者能够跟上 Yarn 数十个贡献者的步伐。

那么,如果不是通过展平来组织 node_modules 目录,pnpm 又是如何做到的呢?要理解这一点,我们回忆一下 npm 版本 3 之前 node_modules 的文件夹结构:在 npm@3 之前,node_modules 结构是可预测且清晰的,因为每个依赖都有自己的 node_modules 文件夹,并在 package.json 中指定了所有它所依赖的其他模块。

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

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

  • 频繁地创建过深的依赖树,导致在 Windows 上出现了长目录路径问题(一般工具软件会有 256 字符长度限制)
  • 当不同的依赖项依赖了同一个包时,这个包会被多次复制粘贴

为了解决这些问题,npm 重新思考了 node_modules 结构,并提出了扁平化处理方案。从 npm@3 开始,node_modules 结构如下所示:

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

有关 npm v3 依赖解析的更多信息,请参阅这里的文章[3]

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

-> - 表示一个符号链接 (在 Windows 上叫 junction)

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 样本项目[4]仓库。

虽然这个案例对于一个小项目来说似乎过于复杂,但对于更大的项目来说,它的结构比 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 如何引用 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 的文件夹中(是的,有些包就是这么做的)。

你相信吗?

只需通过 npm 安装 pnpm:npm install -g pnpm 。每当想要安装某个东西时,就使用 pnpm 而不是npm:pnpm i foo

此外,你可以在 pnpm 的 GitHub 仓库[5]pnpm.js.org[6] 上阅读更多信息。还可以在 Twitter 上关注 pnpm[7],或者在 pnpm Gitter 聊天室[8]寻求帮助。

参考资料

[1]

Why should we use pnpm?: https://www.kochan.io/nodejs/why-should-we-use-pnpm.html

[2]

查看基准测试结果: https://pnpm.io/benchmarks

[3]

这里的文章: http://npm.github.io/how-npm-works-docs/npm3/how-npm3-works.html

[4]

pnpm 样本项目: https://github.com/pnpm/sample-project

[5]

pnpm 的 GitHub 仓库: https://github.com/pnpm/pnpm

[6]

pnpm.js.org: https://pnpm.js.org/

[7]

在 Twitter 上关注 pnpm: https://twitter.com/pnpmjs

[8]

pnpm Gitter 聊天室: https://gitter.im/pnpm/pnpm


原文始发于微信公众号(写代码的宝哥):为什么我们应该使用 pnpm?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243939.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!