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)

他们的区别有以下几点:

  1. 软链接可理解为指向源文件的指针,它是单独的一个文件,仅仅只有几个字节,它拥有独立的 inode,相当于快捷方式
  1. 硬链接与源文件同时指向一个物理地址,它与源文件共享存储数据,它俩拥有相同的 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中找

阅读剩余
THE END