一、模块访问
分析
我们可以直观地看到项目中 node_modules 里的包数量是远大于 package.json 中的依赖数量的。这是因为我们直接显示依赖的包,它自己还会有其他依赖。
对于使用 npm 来管理包的项目来说,我们是可以在代码中去使用 node_modules 里的依赖的。但这存在不稳定性或者说增加了项目的安全风险。
举个例子,如果我们的项目依赖于一个 A 包,A 包又依赖于一个 B 包,在项目中,我们只在 package.json 中声明了对 A 的依赖,而没有声明对 B 的依赖。由于 npm 是允许我们访问 B 包的,当我们在项目中直接使用了 B 包,而未来 A 包如果发布了新版本,B 包有了破坏性更改或者说不再依赖于 B 包了,我们的项目升级了 A 包后就会突然出现故障,而你可能想不起来你用了 B 包这个事。
除了这个幽灵依赖的问题,还有依赖地域、模块解析不一致等其他问题。
总结
npm 可以访问它们没有在 'package.json' 中显示依赖的包。
pnpm 使用(半)严格的 node_modules 结构来防止访问未声明的包。
二、磁盘空间
符号链接(symlinks)
符号链接本身是一个特殊的文件类型。
pnpm 通过使用符号链接的方式仅将项目的直接依赖项添加到 node_modules 的根目录下。
符号链接可以跨文件系统创建,可以指向不同系统中的文件或目录。
如果原始文件被删除或移动,符号链接会变成悬空。这类似于 Windows 中的快捷方式,如果目标文件被删除了,这个链接就会失效。
硬链接(hard links)
在 pnpm 管理的项目中,相同的包版本不会在磁盘中重复保存,所有的包都存储在一个全局存储空间。当项目需要某个包时,pnpm 会在项目的 node_modules 目录中创建指向全局存储中相应文件的硬链接。
创建一个硬链接时,实际上时创建了一个引用。对于硬链接,数据只有在所有指向它的引用都被删除时才会被真正删除。
如何理解
可以想象我们的全局存储相当于一个图书馆,里面的书就是一个个包。每本书都有唯一一个编号(包的版本)。现在有很多人(项目)想要借阅书来阅读。
符号链接就像是一个包含书籍位置信息的便签,它需要额外的存储空间来保存这些信息。如果你想要借书,图书馆就会给你一张便签,你通过这个便签去找书。图书馆如果更改了图书的位置,只要更新便签信息就可以了,不需要真正移动书,它允许项目以灵活的方式来访问依赖。
硬链接就像给借阅者提供一本书的复制品(想象它是一个引用),复制品都指向同一本原书,图书馆也只要保留一本原书就可以,实际上占空间的也只有这一本书的大小,来一个人借阅,就给他一个复制品。
npm 主要是通过复制文件的方式来管理依赖,pnpm 就是使用了符号链接和硬链接来处理依赖,所以 pnpm 更节省磁盘空间。
三、安装速度
pnpm 在包安装速度方面通常优于 npm 和 yarn。
四、对monorepo的支持
pnpm 对大型 monorepo 项目提供了良好的支持,这对于管理多个包的项目来说是一个重要的优势。