npm与pnpm的拓扑结构
# 0.前言
本篇博客主要讲的是:不同包管理工具形成node_modules
拓扑结构的变化。
在这一节就可以明显的发现最近大火的pnpm
相比于前代包管理(npm/yarn
)工具上在资源空间上的优势,创造性的使用hardlink
和softlink
来链接module
与module
之间的引用。
此外,讲解pnpm
的时候,需要有软链接
和硬链接
的前提知识,这个可以看我的另一篇文章《软链接与硬链接》。
# 1.module
的查找方式:向上查找
当 require('package-hello')
时,假设 package-hello
是一个 npm
库,我们是如何找到该 package
的?
- 寻找当前目录的
node_modules/package-hello
目录 - 如果未找到,寻找上一级的
../node_modules/package-hello
目录,以此递归查找。
# 2.npm2
的依赖结构
在 npmv2
时,node_modules
对于各个 package 的拓扑为嵌套结构。
假设:
- 项目依赖
package-a
与package-b
两个 package package-a
与package-b
均依赖lodash@4.17.4
依赖关系以 Markdown 列表表示:
+ package-a
+ `lodash@4.17.4`
+ package-b
+ `lodash@4.17.4`
2
3
4
此时 node_modules
目录结构如下:
此时最大的问题:
- 嵌套过深。
- 占用空间过大。
# 3.npm3
的依赖结构
目前在 npm/yarn 中仍然为平铺结构,但 pnpm 使用了更省空间的方法,以后将会提到。
扁平/平铺的含义:第三方依赖和第三方依赖的依赖会被安装在同级。
在 npmv3
之后 node_modules
为平铺结构,拓扑结构如下:
问题:以下依赖最终node_modules
的结果是什么?
依赖关系以 Markdown 列表表示:
+ package-a
+ `lodash@^4.17.4`
+ package-b
+ `lodash@^4.16.1`
2
3
4
答: 与上图所示的拓扑结构一致,因为二者为 ^
版本号,他们均会下载匹配该版本号范围的最新版本,即 @4.17.4
,因此二者依赖一致。
如果考虑 package.lock.json
文件的话,会严格按照package.json
中指定的版本规则,此时拓扑结构会变成:
此时,npm3
拓扑结构的问题就差不多出现了,即当存在两个相同module
,而不同version
时。
即,当第一个同名的module
会被扁平的安装在与dependency
同级的目录,其余version
的module
则会和npm2
一样嵌套的安装在依赖下方。
如,假设存在依赖:
+ package-a
+ `lodash@4.0.0`
+ package-b
+ `lodash@4.0.0`
+ package-c
+ `lodash@3.0.0`
+ package-d
+ `lodash@3.0.0`
2
3
4
5
6
7
8
答:package-d 只能从自身的 node_modules
下寻找 lodash@3.0.0
,而无法从 package-c
下寻找,此时 lodash@3.0.0 不可避免地会被安装两次
node_modules
目录结构如下图:
# 4.pnpm
的依赖结构
那么不可避免地在 npm 或者 yarn 中,lodash@3.0.0
会被多次安装,无疑造成了空间的浪费与诸多问题。
这是一个较为常见的场景,在平时项目中有些库相同版本甚至会安装七八次,如 postcss
、ansi-styles
、ansi-regex
、braces
等。
而在 pnpm
中,它改变了 npm/yarn
的目录结构,采用软链接
+硬链接
的方式,避免了 doppelgangers
问题,更加节省空间。
假设在package.json
中存在以下依赖:
+ bar@1.0.0
+ bar
+ `foo@1.0.0` #(且foo之前已经被别的依赖在同级安装过了)
2
3
pnpm
的目录拓扑结构如下所示:
pnpm
在安装bar
依赖时,会在node_modules
目录下会生成两个文件:
# 1. node.js正常寻找的目录
|-- node_modules/bar # 全部存放着软链接
# 2. modulde真正存放的位置
|-- node_modules/.pnpm/bar@1.0.0/node_modules/A # 硬链接
2
3
4
5
从图中可以发现,当 foo
遇到其余同名其他 version
的依赖时,会把多个version
版本的module
安装在.pnpm
这一层级上,并以hardlink
的方式与真实硬件存放的.pnpm store
链接在一起,如图中的foo@1.0.0
、foo@2.0.0
。
当发现重复的依赖时,如foo@1.0.0
,则会以软链接的方式与第一层的foo@1.0.0
链接起来。
此时,会有同学存在一个问题,为啥嵌套的依赖不以
hardlink
方式与.pnpm store
链接起来呢?这部分我也不是很清楚,详见:https://pnpm.io/blog/2020/05/27/flat-node-modules (opens new window)-is-not-the-only-way (opens new window)