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

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)结构也带来了一系列问题:
-
可以在模块中访问到没有依赖的包(即没有写在 package.json
文件中的依赖) -
扁平化依赖树的算法非常复杂 -
某些包必须被复制到一个项目的 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]寻求帮助。
参考资料
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