pnpm包管理机制理解
上图是pnpm官网的,在项目根目录安装依赖包bar,而bar依赖了foo。点开项目根目录的node_modules,可以看到我们在项目中安装的bar,以及.pnpm文件夹。
那么pnpm是如何维护依赖的嵌套关系的呢?
首先在.pnpm文件夹以平铺的方式存储了项目中所有的依赖包,建立硬链接。命名的方式如下:
.pnpm/
+ @ /node_modules/ // 组织名(若无会省略)+包名@版本号/node_modules/名称(项目名称) 其中.pnpm中的包中的node_modules下面的包中的每个文件都是内容可寻址存储的硬链接。将foo和bar都放到node_modules文件夹,可以保证包能自行导入自己。
node_modules └── .pnpm ├── [email protected] │ └── node_modules │ └── foo ->
/bar 硬链接 │ ├── index.js │ └── package.json └── [email protected] └── node_modules └── bar -> /bar 硬链接 ├── index.js └── package.json 建立符号链接,由于bar依赖了foo,因此foo被符号链接到[email protected]/node_modules文件夹下,
node_modules └── .pnpm ├── [email protected] │ └── node_modules │ └── foo ->
/bar 硬链接 │ ├── index.js │ └── package.json └── [email protected] └── node_modules └── bar -> /bar 硬链接 │ ├── index.js │ └── package.json └── foo -> ../../[email protected]/node_modules/bar 符号链接 处理直接依赖,bar被符号链接到根目录的node_modules文件夹
node_modules ├── bar -> ./.pnpm/[email protected]/node_modules/bar 符号链接 └── .pnpm ├── [email protected] │ └── node_modules │ └── foo ->
/bar 硬链接 │ ├── index.js │ └── package.json └── [email protected] └── node_modules └── bar -> /bar 硬链接 │ ├── index.js │ └── package.json └── foo -> ../../[email protected]/node_modules/bar 符号链接 - pnpm的包管理机制符合node寻址机制
nodejs的寻址方式:
对于核心模块(core module) => 绝对路径 寻址
node标准库 => 相对路径寻址
第三方库(通过npm安装)到node_modules下的库(可以在node环境中输入module.paths查看):
3.1. 先在当前路径下,寻找 currentProject/node_modules/xxx
3.2 递归从下往上,到上级路径寻找,例如 ../node_modules/xxx
3.3 循环步骤3.2
3.4 在全局环境路径下寻找,例如 .node_modules/xxx
3.5 在用户目录下寻找,例如 users/金虹桥程序员/.node_modules/xxx 或者 users/金虹桥程序员/node_libraries/xxx
3.6 node安装目录下查找,例如 nodejs/lib/node/.node_modules/xxx
在项目中我们引入bar的时候,会在当前项目根目录的node_modules
找bar,从projectRoot/node_modules/bar
这个符号链接找到bar的真实文件projectRoot/node_modules/.pnpm/[email protected]/node_modules/bar
(硬链接即可看做真实存在的文件)。
接着bar依赖了foo,根据node的寻址机制,会从里到外找foo,最后在这个路径projectRoot/node_modules/.pnpm/[email protected]/node_modules/foo
找到了,这个是一个符号链接,会定位到projectRoot/node_modules/.pnpm/[email protected]
pnpm优势
解决了幽灵依赖的问题
yarn和npm中的幽灵依赖:由于在yarn或npm中采用扁平化的安装包的方式,即所有的包(包括依赖的依赖)都被提升到了项目根目录中的node_modules中,那么项目中可以直接引用到不在package.json中声明的包,那么在包更新的时候,可能会导致问题。
而pnmp中的项目根目录的node_modules中只有项目直接依赖的包,所以不会有这个问题。
路径过长的问题
在 npm@3 之前,npm采用的嵌套结构管理包,node_modules结构是干净、可预测的,因为node_modules 中的每个依赖项都有自己的node_modules文件夹。但是这样的管理方式会导致路径过程超出window的限制。
在npm@3和yarn中,采用了扁平化结构管理包(解决了路径过长的问题,导致了幽灵依赖的问题)。
pnpm利用符号链接解决了这个问题。
继续上面的
添加[email protected]作为foo和bar的依赖项
node_modules
├── bar -> ./.pnpm/[email protected]/node_modules/bar 符号链接
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── foo -> /bar 硬链接
│ ├── index.js
│ └── package.json
│ └── qar -> ../../[email protected]/node_modules/qar 符号链接
└── [email protected]
│ └── node_modules
│ └── bar -> /bar 硬链接
│ │ ├── index.js
│ │ └── package.json
│ └── foo -> ../../[email protected]/node_modules/bar 符号链接
│ └── qar -> ../../[email protected]/node_modules/qar 符号链接
└── [email protected]
└── node_modules
└── qar -> /qar
可以看到我们目录深度没有改变,但却表达了嵌套的包依赖关系。
占用更小的内存,下载速度更快
使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:
如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么 pnpm update 时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。
所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!
参考
pnpm官网
基于符号链接的 node_modules 结构
都2022年了,pnpm快到碗里来!
关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?
Hard links and Symbolic links (硬链接 VS 符号链接)
彻底明白Linux硬链接和软链接