Skip to content
On this page

包管理工具

关于 pnpm

早期的时候,依赖会被嵌套安装,这样每个依赖都有自己的 node modules,但是这样会导致依赖地狱。有文件路径过长和文件被多次复制的问题。此后,依赖的结构被扁平化,依赖被提升到顶层,但是当有 不同版本号的相同依赖时,只有一份依赖会随机被提升上来,然后依然会存在多份相同版本的依赖都没有被提升,占用空间。当然相比更严重的问题是,我们能够访问 package.json 文件中未声明的幽灵依赖。

pnpm 拥有自己的 .pnpm 目录,它会以平铺的方式来存储所有的包,以依赖名加上版本号的方式命名,实现了版本的复用。而且它不是通过拷贝机器缓存中的依赖到项目目录下,而是通过硬链接的方式,这能减少空间占用。

至于根目录下用于项目使用的依赖则是通过符号链接(即软链接)的方式,链接到它在 .pnpm 目录下的对应位置。在 .pnpm 目录下扁平化,并且不止提升一个版本,是把所有的版本都给扁平的列出来,然后具体的依赖用软链来组织,这个是嵌套的,但因为是软链也就不会重复。

综上,pnpm 的机制确实能彻底解决依赖重复的问题,也没有扁平化带来的幽灵依赖问题。

pnpm 作为杀手锏的两点优势在于:

  • 包安装速度极快;
  • 磁盘空间利用非常高效(主要是采用符号链接的方式管理模块)。

shamefully-hoist

默认情况下,通过 pnpm 的 node_modules 只能访问到在 package.json 文件中声明了的依赖,只有依赖项本身才能访问未声明的依赖项。可能需要在 .npmrc 中配置 node_modules 形式为和 npm 类似的平铺方式,因为某些工具它只认根目录中的依赖。

不过事实上 pnpm 严格模式能够帮我们避免一些低级的错误,可以阅读 pnpm's strictness helps to avoid silly bugs

peer-dependencies

需要手动安装 peer-dependencies,需要额外安装时会在命令行提示。比如这个 vuepress 项目,如果使用 pnpm 的话,在配置了shamefully-hoist 的本地运行和构建都没问题,而在 vercel 的 node14 版本上构建时会报错。这是因为本地的依赖被提升,而 vercel 上面没有。

Error: [vite]: Rollup failed to resolveimport"@vuepress/client"from "docs/vuepress/temp/internal/pagesRoutesjs".

需要手动安装 vue 和 @vuepress/client,至于是否应该自动安装peerDependencies 的讨论,可以参考 automatically installpeerDependencies

npm 的问题

  • 安装 bootstrap 会内置animate.css 不用重新安装 animate.css (幽灵依赖)就能直接用。如果哪天卸载掉 bootstrap , animate.css 也就不能用了。
  • 但是放到 pnpm 就不行,需要单独配置 .npmrc 文件,配置完成后,再重新安装依赖。
bash
# 实现和 npm 一样的功能放到 node_modules 下面拍平

shamefully-hoist = true

软硬链接

注意:文件夹不会被链接。 两者的区别:

  • 软链接可理解为指向源文件的指针,它是单独的一个文件,仅仅只有几个字节,它拥有独立的 inode ;
  • 硬链接与源文件同时“指向一个屋里的地址”,它与源文件共享存储数据,它俩拥有相同的 inode 。 在 pnpm 中软链接解决幽灵问题,硬链接解决被重复下载的问题。

浅谈 npm 2.x / npm 3.x / yarn

  • npm 2.x 的问题 npm2(node 版本为4.x) 的 node_modules 是“无限”嵌套的,有什么问题吗?多个包可能会有公共的依赖包,同样的依赖会拷贝很多次,因此会占据较大的磁盘空间,而且在 Windows 系统有个致命问题就是文件路径最长是 260+ 个字符(长度限制问题)。此问题后来被 yarn 解决。
  • yarn 的问题 采用“平铺”的方式解决 npm 2.x 的问题,将所有的依赖放在同一层,即解决了依赖重复问题,又解决路径过长的问题。但是,仍然有一些包还有 node_modules ,为什么?因为一个包有多个版本,且只能提升其中一个,所以其他版本里有 node_modules 嵌套的情况。yarn.lock 是用来锁定依赖版本的,后来 npm 也采用 lock 方案还实现了“平铺”功能。
  • "平铺"的问题 主要的问题是幽灵依赖,项目里 package.json 的 dependencies 没有引入的依赖包,却能被 require 进来。原因是平铺后,依赖的依赖是能被找到的,由于并不是显式依赖,且假如有个包不依赖这个包了,现有的项目就无法跑了。 其次的问题就是仍会出现浪费磁盘空间的情况,铺平时,依赖包有多个版本是,只会有一个被提升,剩下的版本仍然背拷贝下来。
  • pnpm 的出现 pnpm install 之后,控制台会打印这样一句话:

Packages are hard linked from the content-addressable store to the vitual store.

意思是 包从全局 store (用户根目录有 .pnpm-store) 硬链接到虚拟 store (当前项目的 node_modules/.pnpm)。 node_modules 下的包依赖关系是通过软链接组织的(软链接到 node_modules/.pnpm 下),可以结合上面的图看一下,使用软硬链接的方式解决磁盘占用的问题,同时没有幽灵依赖问题。 这个特点标榜了:快速的,节省磁盘空间的包管理工具。

  • 结语 从上面看得出来,pnpm 碾压 npm 和 yarn ,猜测未来 npm 和 yarn 会抄袭 pnpm 的优点。

推荐文章

pnpm官方文章

JavaScript 包管理器比较: npm,Yarn,还是 pnpm?

关于现代包管理器的深度思考

为什么 vue 源码以及生态仓库要迁移 pnpm? - 知乎 (zhihu.com)

Released under the MIT License.