pnpm、npm和yarn的比对

125 阅读5分钟

这篇文章主要介绍包管理器-pnpm。它是由npm/yarn衍生而来,但却解决了npm/yarn内部潜在的bug,并且极大地优化了性能,扩展了使用场景。

image.png

npm/yarn所存在的问题描述

npm@3之前所存在的问题

npm@3 之前,嵌套安装方式,node_modules结构是干净可预测的,因为node_modules 中的每个依赖项都有自己的node_modules文件夹,在package.json中指定了所有依赖项。例如下面所示,项目依赖了foofoo又依赖了bar,依赖关系如下图所示:

go
 代码解读
复制代码
node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

上面结构有两个严重的问题:

  • package中经常创建太深的依赖树,这会导致 Windows 上的目录路径过长问题
  • 不同依赖包的间接依赖有相同的,会重复安装,导致体积过大

npm@3之后所存在的问题

为了解决上述问题,npm 重新考虑了node_modules结构并提出了扁平化结构。在npm@3+yarn中,node_modules 结构变成如下所示:

go
 代码解读
复制代码
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)AC都依赖了不同版本的B,其中A依赖B 1.0C依赖B 2.0,可以通过下图清晰的看到npm2npm3+结构差异:

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

依赖变更会影响提升的版本号,比如变更后,有可能是B 1.0 ,也有可能是 B 2.0被提升上来(但只能有一个版本提升)

细心的小伙伴可能发现,这其实并没有解决之前的问题,反而又引入了新的问题。

存在问题总结

Phantom dependencies 幽灵依赖

Phantom dependencies 被称之为幽灵依赖幻影依赖,解释起来很简单,即某个包没有在package.json 被依赖,但是用户却能够引用到这个包。

引发这个现象的原因一般是因为 node_modules 结构所导致的。例如使用 npm或yarn 对项目安装依赖,依赖里面有个依赖叫做 foofoo 这个依赖同时依赖了 bar,yarn 会对安装的 node_modules 做一个扁平化结构的处理,会把依赖在 node_modules 下打平,这样相当于 foobar 出现在同一层级下面。那么根据 nodejs 的寻径原理,用户能 require 到 foo,同样也能 require 到 bar

NPM doppelgangers NPM分身

这个问题其实也可以说是版本提升导致的,这个问题可能会导致有大量的依赖的被重复安装.

举个例子:项目中有packageApackageBpackageCpackageDpackageA依赖packageX 1.0和packageY 1.0packageB依赖packageX 2.0和packageY 2.0packageC依赖packageX 1.0和packageY 2.0packageD依赖packageX 2.0和packageY 1.0

npm2时,结构如下

markdown
 代码解读
复制代码
- package A
    - packageX 1.0
    - packageY 1.0
- package B
    - packageX 2.0
    - packageY 2.0
- package C
    - packageX 1.0
    - packageY 2.0
- package D
    - packageX 2.0
    - packageY 1.0

npm3+yarn中,由于存在hoist机制,所以X和Y各有一个版本被提升了上来,目录结构如下

- package X => 1.0版本
- package Y => 1.0版本

- package A
- package B
    - packageX 2.0
    - packageY 2.0
- package C
    - packageY 2.0
- package D
    - packageX 2.0

npm3+yarn中,由于存在hoist机制,所以X和Y各有一个版本被提升了上来,目录结构如下

markdown
 代码解读
复制代码
- package X => 1.0版本
- package Y => 1.0版本

- package A
- package B
    - packageX 2.0
    - packageY 2.0
- package C
    - packageY 2.0
- package D
    - packageX 2.0

如上图所示的packageX 2.0和packageY 2.0被重复安装多次,从而造成 npm 和 yarn 的性能一些性能损失。

这种场景在monorepo 多包场景下尤其明显,这也是yarn workspace经常被吐槽的点,另外扁平化的算法实现也相当复杂,改动成本很高。

Pnpm优势

1.节省磁盘空间

image.png

使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:

  1. 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有 100 个文件,而它的新版本只改变了其中 1 个文件。那么 pnpm update 时只会向存储中额外添加 1 个新文件,而不会因为单个改变克隆整个依赖。
  2. 所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。

因此,你在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!

2.依赖管理

使用store和Links来进行文件资源的关联

Store

image.png