这篇文章主要介绍包管理器-pnpm。它是由npm/yarn衍生而来,但却解决了npm/yarn内部潜在的bug,并且极大地优化了性能,扩展了使用场景。
npm/yarn所存在的问题描述
npm@3之前所存在的问题
在 npm@3 之前,嵌套安装方式,node_modules结构是干净、可预测的,因为node_modules 中的每个依赖项都有自己的node_modules文件夹,在package.json中指定了所有依赖项。例如下面所示,项目依赖了foo,foo又依赖了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),A和C都依赖了不同版本的B,其中A依赖B 1.0,C依赖B 2.0,可以通过下图清晰的看到npm2和npm3+结构差异:
包
B 1.0被提升到了顶层,这里需要注意的是,多个版本的包只能有一个被提升上来,其余版本的包会嵌套安装到各自的依赖当中(类似npm2的结构)。至于哪个版本的包被提升,依赖于包的安装顺序。
依赖变更会影响提升的版本号,比如变更后,有可能是B 1.0 ,也有可能是 B 2.0被提升上来(但只能有一个版本提升)
细心的小伙伴可能发现,这其实并没有解决之前的问题,反而又引入了新的问题。
存在问题总结
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。
NPM doppelgangers NPM分身
这个问题其实也可以说是版本提升导致的,这个问题可能会导致有大量的依赖的被重复安装.
举个例子:项目中有packageA、packageB、packageC、packageD。packageA依赖packageX 1.0和packageY 1.0,packageB依赖packageX 2.0和packageY 2.0,packageC依赖packageX 1.0和packageY 2.0,packageD依赖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.节省磁盘空间
使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:
- 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有 100 个文件,而它的新版本只改变了其中 1 个文件。那么
pnpm update时只会向存储中额外添加 1 个新文件,而不会因为单个改变克隆整个依赖。 - 所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
因此,你在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!
2.依赖管理
使用store和Links来进行文件资源的关联