pnpm 对比 npm & yarn
pnpm npm yarn 包管理
前段时间用vite+vue3重写了博客的前端,也在公司内使用vite做了一个后台管理页面的模板;发现目前新的项目都推荐使用pnpm作为包管理工具,所以这里写一下pnpm相比较于其他包管理工具的优势(npm、yarn)。
npm2
首先 npm 有很多版本,从 npm2 说起: npm2 的 node_modules 是嵌套的,也就是说每个包下面都会有自己的 node_modules。
这样其实是有问题的,多个包之间难免会有公共的依赖,这样嵌套的话,同样的依赖会复制很多次,会占据比较大的磁盘空间。
这个还不是最大的问题,致命问题是 windows 的文件路径最长是 260 多个字符,这样嵌套是会超过 windows 路径的长度限制的。
当时 npm 还没解决,社区就出来新的解决方案了,就是 yarn。
yarn
针对 npm2 的问题,yarn 的解决方式是平铺,所有的依赖不再一层层嵌套了,而是全部在同一层,这样也就没有依赖重复多次的问题了,也就没有路径过长的问题了。
这样的话,大部分包是没有二层 node_modules 的,但是有一种情况,一个包是可能有多个版本的,提升只能提升一个,所以后面再遇到相同包的不同版本,依然还是用嵌套的方式。
npm 后来升级到 3 之后,也是采用这种铺平的方案了,和 yarn 很类似。
而这种扁平化的方案也有相应的问题:
最主要的一个问题是幽灵依赖,也就是你明明没有声明在 dependencies 里的依赖,但在代码里却可以 require 进来。
这个也很容易理解,因为都铺平了,那依赖的依赖也是可以找到的。
但是这样是有隐患的,因为没有显式依赖,万一有一天别的包不依赖这个包了,那你的代码也就不能跑了,因为你依赖这个包,但是现在不会被安装了。
这就是幽灵依赖的问题。
而且还有一个问题,就是上面提到的依赖包有多个版本的时候,只会提升一个,那其余版本的包不还是复制了很多次么,依然有浪费磁盘空间的问题。
pnpm 就解决了这两个问题。
pnpm
npm3 和 yarn 之所以做 node_modules 扁平化,就是因为同样的依赖会复制多次,并且路径过长在 windows 下有问题,那如果不复制呢,pnpm 就是通过 link 处理的。
首先介绍下 link,也就是软硬连接,这是操作系统提供的机制,硬连接就是同一个文件的不同引用,而软链接是新建一个文件,文件内容指向另一个路径。当然,这俩链接使用起来是差不多的。
如果不复制文件,只在全局仓库保存一份 npm 包的内容,其余的地方都 link 过去呢?
这样不会有复制多次的磁盘空间浪费,而且也不会有路径过长的问题。因为路径过长的限制本质上是不能有太深的目录层级,现在都是各个位置的目录的 link,并不是同一个目录,所以也不会有长度限制。
执行pnpm install可以看到控制台有这么一句打印:
Packages are hard linked from the content-addressable store to the virtual store.
包是从全局 store 硬连接到虚拟 store 的,这里的虚拟 store 就是 node_modules/.pnpm。
此时打开 node-modules 会发现确实不是扁平化的了,只有 dependcies 中的显式依赖,没有幽灵依赖。
展开.pnpm 可以看到所有的依赖都在这里铺平了,都是从全局 store 硬连接过来的,然后包和包之间的依赖关系是通过软链接组织的。
也就是说,所有的依赖都是从全局 store 硬连接到了 node_modules/.pnpm 下,然后之间通过软链接来相互依赖。

总结
npm2 是通过嵌套的方式管理 node_modules 的,会有同样的依赖复制多次的问题。
npm3+ 和 yarn 是通过铺平的扁平化的方式来管理 node_modules,解决了嵌套方式的部分问题,但是引入了幽灵依赖的问题,并且同名的包只会提升一个版本的,其余的版本依然会复制多次。
pnpm 则是用了另一种方式,不再是复制了,而是都从全局 store 硬连接到 node_modules/.pnpm,然后之间通过软链接来组织依赖关系。
这样不但节省磁盘空间,也没有幽灵依赖问题,安装速度还快(不用复制了)。