前言
这是一篇翻译自Zoltan Kochan编写的【Why should we use pnpm?】 的文章。以下提及的“我”均指pnpm的贡献者也就是作者Zoltan Kochan。此外,译文有些地方会额外多一些说明帮助更好的理解。
这将收录进我的专栏 《前端环境搭建与准备-指引》,用以让我的读者更好的了解pnpm配置。
相关网站/文章地址
译文正文
pnpm
是Node.js
的另一个包管理器,它是npm
的替代品,但它速度更快,效率更高。
那pnpm
有多快呢? 快3倍!请参阅 此处 的基准。
为什么pnpm
的效率更高呢?当你安装一个依赖包时,pnpm
会把这个依赖包放到你电脑上的全局缓存里,然后从中创建一个硬链接,而不是复制(npm或yarn是复制这个依赖包到 node_modules
里)。对于依赖包模块的每个版本,磁盘上只保留一个副本。例如,当我们使用npm
或yarn
时,如果你的项目中使用了100个依赖包,这些依赖包都使用到了lodash
,那么你的磁盘里就会有100份lodash
的复制包。pnpm会让你节省千兆(字节)的磁盘空间!
为什么不使用Yarn?
说实话,Yarn
发布的时候,“我”真的很失望。几个月来,“我”为pnpm
做了大量的贡献,期间,没有任何关于yarn
的新闻。关于其(yarn
)的未来发展的信息并未公开。
几天之后,“我”才意识到,yarn
仅仅是对npm
进行了一个小改进而已。虽然它(yarn
)让我们安装依赖包更快一点且有一些很不错的新功能,但是,yarn
使用与npm
(自版本3起)相同的扁平化 node_modules
结构。
扁平化依赖树带来了一系列问题,如下:
-
- 模块可以访问它们不依赖的包
-
- 依赖树的扁平化算法是相当复杂的
-
- 一些包必须被复制到一个项目的
node_modules
文件夹中
- 一些包必须被复制到一个项目的
此外,还有一些问题Yarn并不打算解决,比如磁盘空间的使用问题。所以“我”决定继续把“我”的时间投入到pnpm
,并取得了巨大的成功。截至目前(2017年3月),pnpm
具有 yarn
覆盖npm
的所有附加功能:
-
安全性
。和yarn 一样,pnpm 有一个特殊的文件,文件里包含所有已安装包的校验和,以此来确保在执行代码之前验证每个已安装包是否完整(包完整性)。
-
脱机模式
。pnpm将所有已下载好的包源码保存在本地注册表镜像中。当包在本地可用时,将不再发出请求去下载包。配置--offline
参数,就可以完全禁止HTTP请求。
-
更快速
。 pnpm不仅比npm更快,还比yarn更快。pnpm比使用冷缓存和热缓存的yarn都快。Yarn是从缓存里复制文件到项目,而pnpm仅从全局存储中创建链接文件。
怎么可能呢?
就像“我”前面说的,pnpm不扁平化依赖树,因此,pnpm使用的算法简单得多!这就是为什么仅仅只需一个开发者就相当于Yarn的几十个贡献者。那么,如果不是通过扁平化的话,pnpm又是如何构造 node_modules
目录的呢?
为了理解它,我们要先回想一下在npm的版本3之前, node_modules
文件夹是什么样子。npm@3之前, node_modules
文件的结构是可预测且干净的,因为,每个依赖项的 node_modules
文件夹中都有它自己的 node_modules
文件夹,并在package.json中指明了所有依赖项(以此类推,node_modules
文件夹是可预测且干净整洁的)。例如:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
但是这样就会造成了两个严重的问题,如:
-
软件包经常创建太深的依赖树,这导致Windows上出现长目录路径问题。
-
当在不同的依赖项中使用同一个包时(例如多个依赖项都使用到了lodash ),那么同一个包会被复制粘贴了多次( lodash就会被复制粘贴多次到这些依赖项的node_modules文件夹里 )。
为了解决这个问题,npm团队重新考虑了 node_modules
结构并提出了扁平化。这就有了npm@3,其 node_modules
结构现如下所示:
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
有关npm v3依赖项解决方案的更多信息,请参阅npm v3 Dependency Resolution.
不同于npm@3的是,pnpm 尝试去解决 npm@2 已有的问题,而不是扁平化依赖树。通过pnpm创建的 node_modules
文件夹,所有包都有他们自己的依赖性组合在一起,但是目录树从来不会像 npm@2 一样那么深。pnpm平铺所有的依赖项,但是使用软链(symlinks
)把这些依赖组合在一起。
a symlink (or junction on Windows)
软链(或者在windows上的链接),(我们也会称symlinks为
“软链接”或“符号链接”,但是称为软链接似乎更为妥帖)
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
想要查看实时示例,请访问示例 sample pnpm project
虽然这个示例对于一个小项目来说似乎太复杂了,但是对于大型项目而言,这种结构比npm/yarn创建出来的结构看起来要更好些。让我们来看看原因吧。
首先
,你应该可能已经注意到了, node_modules
根目录中的包只是一个软链接。这其实挺好的,因为Node.js忽略软连接并执行真实路径。所以require('foo')
将会执行 node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/foo/index.js
这个路径的index.js文件,而不是 node_modules/foo/index.js
这个路径的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)已安装,但在目录结构中(bar)处于上一级。 这两个包都位于名为
node_modules
的文件夹中,但是foo包所依赖的bar包在此却是以软链形式存在。 -
foo可以require引入bar,是因为Node.js在目录结构中查找模块,直到磁盘的根目录。 foo也以引用foo,因为它位于一个名为
node_modules
的文件夹中(是的,这是一些包会这样做)。
你信吗?
只需通过npm安装pnpm:npm install -g pnpm
, 当你想安装一些东西时,用pnpm代替npm,例如: pnpm i foo
你也可以在pnpm GitHub repo中阅读更多详情。你也可以在推特上关注pnpm 或在pnpm Gitter 聊天室寻求帮助