Why pnpm/Yarn PnP?——npm留下的幽灵依赖难题

28 阅读3分钟

首先,传统npm最为人诟病的就是其庞大的node_modules,一方面是体积巨大,另一方面是幽灵依赖(项目代码中引用了某个包(比如 lodash),但你并没有在自己的 package.json 文件中显式地声明它),导致项目文件冗余。为了解决这个大问题,现在常用的两种上位替代品有pnpmYarn PnP(区分于Yarn v1)



首先是最早的尝试——Yarn,这哥们属于万户级别的,通过一个yarn.lock锁文件,开创了统一版本的开创性做法,但是没有彻底解决幽灵依赖的问题,不过后续也启发npm,促成了package.json.lock的诞生,至少提高了稳定性



然后是更好的创新——pnpm,采取的是内容寻址,其场景如是:从项目到依赖库之间有且仅有一座通行的桥梁,但是有个门卫看守着,只有通过package.json清晰声明了引用的依赖,才能拿到对应的“通行证”(地址),也就是能够放行让项目的某个part调用该依赖,从而避免“幽灵依赖”的问题。但是因为“桥梁”是客观存在的,所以哪怕没有“通行证”,“不法分子”也可以通过某些手段绕开门卫,访问到依赖(例如强行require读取,但实际情况很难出现)。



而颠覆性的Yarn PnP,则是完全废弃了node_modules文件包,采用压缩归档虚拟映射,其场景如是:诸多依赖库的压缩包文件存在于仓库中,但是项目和依赖之间一开始没有建立链接,只有yarn.lock中声明了的依赖,对应的文件才能通过查询.pnp.cjs文件(映射图),再通过专属的“传送门”,读取对应的安装包,然后调用对应依赖。



那么pnpm似乎并没有Yarn PnP那么完善,为什么仍然被诸多团队使用?还是因为迁移成本,诸多插件都依托于node_modules结构,虽然pnpm的node_modules是假的(软链接),但也确实有这样一个结构,所以原先使用npm的项目转为pnpm时不会有太大的适配成本。



特性npm (v3+) / Yarn v1pnpm (推荐)Yarn PnP (Plug'n'Play)
物理存储复制粘贴:每个项目的依赖都完整拷贝一份到 node_modules内容寻址:全局只存一份,项目内通过硬链接指向全局仓库。压缩归档:依赖以 .zip 格式存在缓存中,项目不产生 node_modules
目录结构**扁平化 (Flat) **:为了减少层级,把所有间接依赖都提到根目录。**链接树 (Symlink) **:只有声明的依赖在根目录,其余在 .pnpm 隐藏目录下。虚拟映射:完全没有物理目录结构,由一个 .pnp.cjs 文件记录位置。
幽灵依赖无法防范:只要是扁平化提升上来的包,代码都能偷偷调用。彻底杜绝:代码只能“看见”你在 package.json 里写的包。彻底杜绝:由 Yarn 运行时严格控制查找权限。
安装速度:需要频繁的 IO 读写和解压。极快:只需建立链接,无需重复下载和解压。极快:无需生成物理文件,几乎瞬时完成。
磁盘占用极大:项目越多,硬盘占用翻倍增长。极小:无论多少个项目,同一版本的包只占一份空间。极小:直接读取压缩包。
生态兼容性完美:所有工具都原生支持这种物理结构。优秀:通过软链接模拟了结构,绝大多数工具无感适配。较差:需要插件适配,且不支持部分不支持 PnP 的旧库。