pnpm的node_modules结构设计
分析pnpm之前,先说一下npm现在的一些问题
npm的问题
平时我们使用
npm install
命令后,node_modules就会呈现以下类似内容之所以出现这种扁平的结构,最初时初衷是好的,为了更好的利用资源,把每个依赖全部放到顶层,这样就不会造成每个依赖嵌套过深,导致很多重复依赖文件
但是随着这种文件结构逐步使用后,就会暴露出来两个问题
- 幽灵依赖
- 版本冲突
幽灵依赖就是你在引用npm包时,你会发现一些没有在
package.json
中出现的包也能引用,这个就是幽灵依赖,这是因为你在install下载npm包时,npm包也有它的依赖会下载,但是下载的依赖也会存在node_modules同一层级下,这样就会导致可以直接引用版本冲突是因为不同的依赖可能依赖的包版本也不同,但是node_modules的同一层级只能存在一个包的一个版本号,如果有不同的版本号就只能存在依赖包的node_modules中,这样就会导致出现重复资源
├── package-A @1.0 |── package-B @1.0 ├── package-C @1.0 │ └── package-A @2.0 │ └── package-B @2.0 ├── package-D @1.0 │ └── package-A @2.0 │ └── package-B @2.0
pnpm的出现
在所有前端苦npm久已时,pnpm出现了,并且在pnpm官网的简介上就简单说明了它的构造:
store + link
- store就是依赖的实际存储位置,Mac/linux在
{home dir}>/.pnpm-store/v3
,windows在当前盘/.pnpm-store/v3
。这样就会有个好处,你在多个项目使用的是同一个依赖时,就不用重复下载,这样就极大的减少存储空间 - link是指符号链接(
SymbolicLink
)和硬链接(HardLink
)- SymbolicLink是一种特殊的文件,包含一条以绝对路径或者相对路径的形式指向其他文件或者目录的引用,它的存在不依赖于目标文件,如果目标文件被删除或者移动,指向目标文件的符号链接依然存在,但是它们会指向一个不复存在的文件
- 相比于SymbolicLink,HardLink不是引用文件,而是引用inode,inode是文件系统的一种数据结构,用于描述文件系统对象。所以你即使更改目标文件的内容或位置,HardLink仍然指向目标文件,因为inode指向该文件
- store就是依赖的实际存储位置,Mac/linux在
然后拿react举例,当
pnpm add react
后,在node_modules终端输入tree -a -L 3
会得到以下node_modules结构. ├── .modules.yaml ├── .pnpm │ ├── js-tokens@4.0.0 │ │ └── node_modules │ ├── lock.yaml │ ├── loose-envify@1.4.0 │ │ └── node_modules │ ├── node_modules │ │ ├── .bin │ │ ├── js-tokens -> ../js-tokens@4.0.0/node_modules/js-tokens │ │ └── loose-envify -> ../loose-envify@1.4.0/node_modules/loose-envify │ └── react@18.2.0 │ └── node_modules └── react -> .pnpm/react@18.2.0/node_modules/react
node_modules下除了.pnpm外只有一个react,这个react只是一个SymbolicLink,当node.js解析时,会找到react的真实位置
node_modules/.pnpm/react@18.2.0/node_modules/react
.pnpm就是将所有依赖放在同一层文件夹中,每个包都可以通过
.pnpm/<name>@<version>/node_modules/<name>
这种路径找到,然后通过hand link
的方式在store中引用依赖文件通过这种文件结构,pnpm就解决了npm的两个问题,首先node_modules下不会有你未在package.json中声明的依赖,这样就不会有幽灵依赖的问题。然后.pnpm下会有name+version的方式链接到store中,这样就不会出现因为版本冲突造成资源重复的问题