pnpm
是一款当代备受关注的 新兴(问题较多) 包管理工具,使用过的同学们都会被它极快的安装速度、极少的磁盘存储空间所吸引!
首先,为什么会出现pnpm
?作者一开始对yarn
的发布有很高的期待,但是发布后并没有满足作者的一些期待,反而让作者有些失望。
After a few days, I realized that Yarn is just a small improvement over npm. Although it makes installations faster and it has some nice new features, it uses the same flat node_modules structure that npm does (since version 3). And flattened dependency trees come with a bunch of issues
几天后,我意识到 Yarn 只是对 npm 的一个小小的改进。尽管它使安装速度更快,并且具有一些不错的新功能,但它使用与npm相同的平面node_modules结构(自版本 3 起)。扁平化的依赖树带来了一系列问题
(具体后面会讲)
至于为什么叫pnpm
?是因为pnpm
作者对现有的包管理工具,尤其是npm
和yarn
的性能特别失望,所以起名叫做perfomance npm
,即pnpm
(高性能npm)
如何突显pnpm
的性能优势?在pnpm
官网上,提供了一个benchmarks图表,它比对了项目在npm、pnpm、yarn(正常版本和PnP版)中,install
、update
场景下的耗时:

image.png
下面表格是上图中的具体数据:
action | cache | lockfile | node_modules | npm | pnpm | Yarn | Yarn PnP |
---|---|---|---|---|---|---|---|
install | 1m 12.2s | 15.7s | 22.1s | 27.5s | |||
install | ✔ | ✔ | ✔ | 1.6s | 1.3s | 2.6s | n/a |
install | ✔ | ✔ | 9.5s | 4s | 8.6s | 1.9s | |
install | ✔ | 14.2s | 7.9s | 14.2s | 7.4s | ||
install | ✔ | 25.4s | 13s | 15.3s | 21.1s | ||
install | ✔ | ✔ | 2.1s | 1.8s | 8.3s | n/a | |
install | ✔ | ✔ | 1.6s | 1.4s | 9.4s | n/a | |
install | ✔ | 2.1s | 5.9s | 15s | n/a | ||
update | n/a | n/a | n/a | 1.6s | 12.1s | 18.7s | 32.4s |
可以看到pnpm(橘色)
有很明显性能提升,在我们项目实践中(基于gitlib
)提升更明显(cache-paths
跟store dir
搭配使用后)
在讨论性能提升原因之前,我们需要先了解下现有包管理工具中node_modules
存在的问题
node_modules 安装方式
目前有两种安装方式:`Nested installation`、`Flat installation`
Nested installation 嵌套安装
在 npm@3 之前,node_modules结构是干净
、可预测
的,因为node_modules 中的每个依赖项都有自己的node_modules文件夹,在package.json中指定了所有依赖项。例如下面所示,项目依赖了foo
,foo
又依赖了bar
,依赖关系如下图所示:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
上面结构有两个严重的问题:
-
package中经常创建太深的依赖树,这会导致 Windows 上的目录路径过长问题
-
当一个package在不同的依赖项中需要时,它会被多次复制粘贴并生成多份文件
Flat installation 扁平安装
为了解决上述问题,npm 重新考虑了node_modules结构并提出了扁平化结构。在npm@3+ 和 yarn中,node_modules 结构变成如下所示:
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
可以看到,hoist
机制下,bar
被提升到了顶层。如果同一个包的多个版本在项目中被依赖时,node_modules结构又是怎么样的?
例如:一个项目App
直接依赖了A(version: 1.0)
和C(version: 1.0)
,A
和C
都依赖了不同版本的B
,其中A
依赖B 1.0
,C
依赖B 2.0
,可以通过下图清晰的看到npm2
和npm3+
结构差异:

image.png
包B 1.0
被提升到了顶层,这里需要注意的是,多个版本的包只能有一个
被提升上来,其余版本的包会嵌套安装到各自的依赖当中(类似npm2
的结构)。

image.png
至于哪个版本的包被提升,依赖于包的安装顺序!
依赖变更会影响提升的版本号,比如变更后,有可能是B 1.0
,也有可能是 B 2.0
被提升上来(但只能有一个版本提升)
细心的小伙伴可能发现,这其实并没有解决之前的问题,反而又引入了新的问题
image.png
npm3+和yarn存在的问题
Phantom dependencies 幽灵依赖
Phantom dependencies 被称之为幽灵依赖或幻影依赖,解释起来很简单,即某个包没有在package.json
被依赖,但是用户却能够引用到这个包。
引发这个现象的原因一般是因为 node_modules 结构所导致的。例如使用 npm或yarn 对项目安装依赖,依赖里面有个依赖叫做 foo
,foo
这个依赖同时依赖了 bar
,yarn 会对安装的 node_modules 做一个扁平化结构的处理,会把依赖在 node_modules 下打平,这样相当于 foo
和 bar
出现在同一层级下面。那么根据 nodejs 的寻径原理,用户能 require 到 foo
,同样也能 require 到 bar
。
nodejs的寻址方式:(查看更多)
对于核心模块(core module) => 绝对路径 寻址
node标准库 => 相对路径寻址
第三方库(通过npm安装)到node_modules下的库: 3.1. 先在当前路径下,寻找 node_modules/xxx
3.2 递归从下往上到上级路径,寻找 ../node_modules/xxx
3.3 循环第二步
3.4 在全局环境路径下寻找 .node_modules/xxx
NPM doppelgangers NPM分身
这个问题其实也可以说是 hoist 导致的,这个问题可能会导致有大量的依赖的被重复安装.
举个例子:项目中有packageA
、packageB
、packageC
、packageD
。