pnpm如何解决npm/yarn的痛点
软链接和硬链接
假设我们有一个文件,称为 hello
通过 ln -s
创建一个软链接,通过 ln
可以创建一个硬链接。
$ ln -s hello hello-soft
$ ln hello hello-hard
$ ls -lh
total 768
45459612 -rw-r--r-- 2 xiange staff 153K 11 19 17:56 hello
45459612 -rw-r--r-- 2 xiange staff 153K 11 19 17:56 hello-hard
45463415 lrwxr-xr-x 1 xiange staff 5B 11 19 19:40 hello-soft -> hello
此文件一共占用的空间大小为153k + 5b +硬链接(大小小于153k)
他们的区别有以下几点:
- 软链接可理解为指向源文件的指针,它是单独的一个文件,仅仅只有几个字节,它拥有独立的
inode
,相当于快捷方式
- 硬链接与源文件同时指向一个物理地址,它与源文件共享存储数据,它俩拥有相同的
inode

pnpm 为何节省空间
它解决了 npm/yarn 平铺 node_modules 带来的依赖项重复的问题
用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink
硬链接。
例如在项目中使用 pnpm 安装了一个叫做 express
的依赖,那么最后会在 node_modules 中形成这样两个目录结构:
node_modules/express/...
node_modules/.pnpm/express@4.17.1/node_modules/xxx
复制代码
其中第一个路径是 nodejs 正常寻找路径会去找的一个目录,如果去查看这个目录下的内容,会发现里面连个 node_modules
文件都没有:
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
复制代码
实际上这个文件只是个软连接,它会形成一个到第二个目录的一个软连接(类似于软件的快捷方式),这样 node 在找路径的时候,最终会找到 .pnpm 这个目录下的内容。
其中这个 .pnpm
是个虚拟磁盘目录,然后 express 这个依赖的一些依赖会被平铺到 .pnpm/express@4.17.1/node_modules/
这个目录下面,这样保证了依赖能够 require 到,同时也不会形成很深的依赖层级。
假如有一个项目依赖了 bar@1.0.0
和 foo@1.0.0
,那么最后的 node_modules 结构呈现出来的依赖结构可能会是这样的:
node_modules
└── bar // symlink to .pnpm/bar@1.0.0/node_modules/bar
└── foo // symlink to .pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── foo@1.0.0
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
复制代码
node_modules
中的 bar 和 foo 两个目录会软连接到 .pnpm 这个目录下的真实依赖中,而这些真实依赖则是通过 hard link 存储到全局的 store 目录中。
假设存在依赖依赖:
.
├── package-a
│ └── lodash@4.0.0
├── package-b
│ └── lodash@4.0.0
├── package-c
│ └── lodash@3.0.0
└── package-d
└── lodash@3.0.0
它最终生成的 node_modules 如下所示,从中也可以看出它解决了幽灵依赖的问题。
以下为软链接 =>到.pnpm下
./node_modules/package-a -> .pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/package-b -> .pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/package-c -> .pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/package-d -> .pnpm/package-d@1.0.0/node_modules/package-d
.pnpm下将所有依赖都平铺
./node_modules/.pnpm/lodash@3.0.0 //被平铺的依赖
./node_modules/.pnpm/lodash@4.0.0 //被平铺的依赖
./node_modules/.pnpm/package-a@1.0.0
./node_modules/.pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/.pnpm/package-a@1.0.0/node_modules/lodash -> ------.pnpm/package-a@1.0.0/node_modules/lodash@4.0.0。 硬链接到全局.pnpm中找
./node_modules/.pnpm/package-b@1.0.0
./node_modules/.pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/.pnpm/package-b@1.0.0/node_modules/lodash -> -----------.pnpm/package-b@1.0.0/node_modules/lodash@4.0.0。 硬链接
./node_modules/.pnpm/package-c@1.0.0
./node_modules/.pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/.pnpm/package-c@1.0.0/node_modules/lodash -> ----------------.pnpm/package-c@1.0.0/node_modules/lodash@3.0.0 硬链接
./node_modules/.pnpm/package-d@1.0.0
./node_modules/.pnpm/package-d@1.0.0/node_modules/package-d
./node_modules/.pnpm/package-d@1.0.0/node_modules/lodash -----------------> .pnpm/package-d@1.0.0/node_modules/lodash@3.0.0 硬链接
个人总结: 模块a中引入lodash require lodash -> ./node_modules/package-a -> .pnpm/package-a@1.0.0/node_modules/package-a (找loadash,往上一层找) ------> .pnpm/package-a@1.0.0/node_modules/lodash@4.0.0。 硬链接到全局.pnpm中找