什么是 pnpm
performance npm 意为高性能的npm
为什么是pnpm
pnpm 解决了 npm 和 yarn 的什么问题
- 扁平化带来的问题
- 幽灵依赖
如果你的项目依赖 a, a 依赖 b,你是直接可以使用 b的,因为它也在你的 node_modules 下,如果哪天 a 被删除了,或者 a 的新版本没有用到 b ,就会导致错误
- 分身依赖
如果你的项目依赖 a,a 依赖 lodash@1.0.0,你的项目依赖 b,b 依赖 lodash@1.0.1, 那么你的node_modules结构是:
└── node_modules
├── a
│ ├── index.js
│ └── package.json
├── b
│ ├── index.js
│ ├── package.json
│ └── node_modules
│ └── lodash
│ ├── index.js
│ └── package.json(@1.0.1)
└── lodash
├── index.js
└── package.json(@1.0.0)
还是
└── node_modules
├── a
│ ├── index.js
│ └── package.json
│ └── lodash
│ ├── index.js
│ └── package.json(@1.0.0)
├── b
│ ├── index.js
│ └── package.json
|
└── lodash
├── index.js
└── package.json(@1.0.1)
答案是都有可能,取决于 a 和 b 在package.json 的位置决定,a 在上边的话,那么 a 下边的 lodash 就会被优先提升,b 在上边的话,b 下边的 lodash 就会被优先提升
npm@5/yarn 引入了一个 lock 文件已解决这种不确定因素,这使得无论安装多少次都会产生相同的结构,这也是为什么lock文件应该始终包含在版本控制中并且不应该手动编辑的原因。但是扁平化算法的复杂度、幽灵依赖和性能问题仍未解决
- 扁平化算法复杂
- 不同项目之间并不共享 node_modules,磁盘占用过大,安装缓慢
npm/yarn 有一个缺点,就是占用了太多的磁盘空间, 如果你安装同一个包100次,100分的就会被储存在不同的node_modules文件夹下,这往往会占用大量的磁盘空间,可以用 npkill 来查看你的电脑有多少 node_modules 我自己查了一下有40多个g
pnpm是怎么解决这些问题的
我们可以先来看一下npm的发展史,npm经历了三次重大的更新,才逐渐形成了现在的形式,所以让我们一个一个地看
npm1 /2 嵌套式的node_modules
如果 a 依赖了 b,那么最简单的思考方式 b 应该放在 a 的 node_modules 里
└── node_modules
└──
├── index.d.ts
├── package.json
└── node_modules
└── b
├── index.js
└── package.json
如果 b 依赖了 c 按照理论我们应该把 c 放在 b 的 node_modules 下
└── node_modules
└──
├── index.d.ts
├── package.json
└── node_modules
└── b
├── index.js
├── package.json
└── node_modules
└── c
同理如果 c 依赖 d 那么 d 应该放在 c 的 node_modules 下
这样产生的问题:
- 路径太长,很容易超过windows路径长度的限制。
- 大量相同版本的包被重复安装
- 不同共享相同的实例,例如,从不同的地方引入react,它将是不同的实例
npm3/yarn - 扁平化的node_modules
从npm3开始(也包括yarn),扁平化的node_modules一直被采用并使用到现在。
nodejs的依赖解析算法有一个规则,如果它在当前目录下没有找到node_modules,它将递归解析父目录下的node_modules,那么利用这一点把所有引用的包放在项目下node_modules中,就可以解决相同版本的包的不共享和过长的依赖路径问题,所以上边的例子结构会是这样
└── node_modules
├── a
│ ├── index.js
│ └── package.json
└── b
├── index.js
└── package.json
但是又有了新的问题:
- 幽灵依赖
- 分身依赖
- 扁平化算法耗费时间
npm5.x/yarn - 带有lock文件的平铺式的node_modules
引入了 lock 文件用来解决安装时不稳定的结构,但是扁平化算法的复杂性、幽灵依赖问题并没解决
pnpm 基于符号连接的 node_modules
pnpm 尝试解决的是 npm@1的问题,而不是使依赖扁平化,
我们拿 express 来做一个例子,当我们执行 pnpm add express,我们得到了一个这样的 node_modules 目录
node_modules
├── express -> .pnpm/express@4.17.2/node_modules/express
└── mime-types -> .pnpm/mime-types@2.1.34/node_modules/mime-types
可以发现,我们的node_modules 只有一个 express, 我们只安装了 pnpm 那么它使我们的程序唯一能访问的包,接下来我们点开 express
node_modules/express
├── History.md
├── LICENSE
├── Readme.md
├── index.js
├── lib
└── package.json
我们发现其实并没有 node_modules,其实诀窍在于它其实是一个软链而已,并且当 nodejs/webpack(默认) 解析的时候其实是解析它们的真实位置
此时我们可以点开 .pnpm 文件,发现了如下的结构
├── accepts@1.3.8\
├── array-flatten@1.1.1\
├── body-parser@1.19.1\
├── bytes@3.1.1\
├── content-disposition@0.5.4\
......
我们发现了一堆 name@version/的文件,我们可以点开真实位置的express
我们发现 其实真实位置下的 express 也并没有 node_modues
其实 expreess 的 node_modules 和它处于同级目录,
node_modules/.pnpm/express@4.17.2/node_modules/* 就是 node_modules/.pnpm/express@4.17.2/node_modules/express 的 node_modules,
此外这些文件也都是软链,
node_modules/.pnpm/express@4.17.2/node_modules/\
├── accepts -> ../../accepts@1.3.8/node_modules/accepts\
├── array-flatten -> ../../array-flatten@1.1.1/node_modules/array-flatten\
├── body-parser -> ../../body-parser@1.19.1/node_modules/body-parser\
├── content-disposition -> ../../content-disposition@0.5.4/node_modules/content-disposition\
├── content-type -> ../../content-type@1.0.4/node_modules/content-type\
├── cookie -> ../../cookie@0.4.1/node_modules/cookie\
├── cookie-signature -> ../../cookie-signature@1.0.6/node_modules/cookie-signature\
├── debug -> ../../debug@2.6.9/node_modules/debug\
├── depd -> ../../depd@1.1.2/node_modules/depd\
├── encodeurl -> ../../encodeurl@1.0.2/node_modules/encodeurl\
├── escape-html -> ../../escape-html@1.0.3/node_modules/escape-html\
├── etag -> ../../etag@1.8.1/node_modules/etag \
├── express\
├── finalhandler -> ../../finalhandler@1.1.2/node_modules/finalhandler\
........
指向了外层加版本版本号下对于的真实文件
我们可以发现这种实现方法巧妙的避开了之前讲的那些问题
- 幽灵依赖
- 分身依赖
- 嵌套结构大量包重复安装
- 路径太长 此外,不止这些,上边还讲到:不同项目之间并不共享 node_modules,磁盘占用过大,pnpm 同样解决掉了
这里是文档的截图:
如何切换
分为三个步骤: 1.
rm -rf node_modules 删除原本的node_modules
pnpm import package-lock.json/yarn.lock (把 lock 文件转换为 pnpm )
-
把所有 script 相关的 都换为 pnpm
-
跑一下 build/dev 这时候 项目中的幽灵依赖自会暴露出来,解决一下 此外
-
如果你的项目是 monorepo 那么你可能需要定义 pnpm-workspace.yaml
-
如果你的某些依赖项 由于历史原因 依赖于扁平化的 node_modules 可以使用 shamefully-hois 配置它
-
如果你的项目 不适用于 软链,那么 可以使用 node-linker: hoisted 来获得扁平化并且没有软链的 node_modules
-
如果你切换了 registery 在是呀 pnpm 安装全局包会收到
ERR_PNPM_REGISTRIES_MISMATCH报错需要执行一下:
pnpm install -g
pnpm install -g pnpm
-
匹配路径问题,比如在webpack里,原本的路径可能不管用了,因为现在真实的路径是在 node_modules/.pnpm/* 下边的
-
当你需要在 node_modules 内改东西,可能会影响到所有你的电脑内所有你用到的地方 :)
参考:www.pnpm.cn/