pnpm是如何解决依赖不复用、windows 的最长路径限制、幽灵依赖、分身问题

226 阅读2分钟

node_modules 的目录结构

npm@2 是嵌套结构,npm@3+ 和 yarn 是扁平结构

image.png

image.png

image.png

  • 嵌套结构的优点:

    1. 目录结构比较清晰
  • 嵌套结构的问题:

    1. windows 的最长路径限制
    2. 相同的依赖没有复用
  • 扁平化结构的优点:

    1. 可以避免依赖包的重复安装,减少磁盘空间的占用。
  • 扁平化的问题:

    1. 幽灵依赖:没有显式声明在 package.json 中的依赖,提升到顶层后,可以被项目直接引用。当有一天这个子依赖不再是引用包的依赖时,项目中的依赖则会出现问题。
    2. 分身问题:相同依赖的不同版本,只会将其中一个进行提升,剩余的版本还是嵌套在对应的包中。

如下图:npm@3+ 和 yarn 项目可以直接引用 vue 依赖的 postcss,而 pnpm 是不可以的。 image.png

image.png

image.png

pnpm是如何解决上面这些问题

  1. pnpm 首先将依赖安装到全局 store
  2. 项目所需的所有的依赖(包括直接依赖和间接依赖)扁平于 node_modules/.pnpm 目录下,通过 hard link 硬链接到全局 store
  3. 将项目的直接依赖通过 symbolic link 软连接(符号链接)到 node_modules 的顶层,

实现了所有项目的依赖共享 store 的全局依赖,解决了幽灵依赖和 NPM 分身的问题

pnpm 的 hardlink 方式

当前测试版本为8.11.0,该版本默认不是硬链接的方式,需要在项目根目录创建 .npmrc 文件,文件内容如下:

package-import-method=hardlink

package-import-method详解: pnpm.io/npmrc#packa…

测试 hardlink 方式是否生效

  1. 修改后,删除 node_modules;
  2. 重新安装 vue(pnpm install vue);
  3. 安装完成后; cd到安装的 vue 目录下(cd pnpm-test-demo/node_modules/vue);
  4. 获取任意一个文件的 inode(ls -li index.js);得到 index.js 文件的 inode 为 1515252;
  5. 获取 pnpm 的存储目录 (pnpm store path);
  6. cd到存储目录下,通过 inode 找到了对应的原文件(find . -inum 1515252)。

测试直接依赖的软链

image.png

pnpm 的克隆

  • reflink:使用 reflink(也称为写时复制,copy-on-write)导入包。Reflink 是一种文件系统特性,它允许创建一个文件的副本,但只有在修改副本时才会实际复制数据。

调用pnpm实现的relink:

// darwin-arm64-GG6FQZPL.node 根据系统而异
const {reflinkFile, reflinkFileSync} = require('/Users/yanessa/.nvm/versions/node/v20.10.0/lib/node_modules/pnpm/dist/reflink.darwin-arm64-GG6FQZPL.node')
reflinkFileSync( '/Users/yanessa/Documents/code/pnpm-test-demo/test/reflink.js', '/Users/yanessa/Documents/code/pnpm-test-demo/reflink.js')

pnpm 的复制

使用复制(copy)导入包。这意味着 pnpm 会将包的文件复制到项目的 node_modules 目录中。