问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

[pnpm] pnpm 与 npm/yarn 的对比

创作时间:
作者:
@小白创作中心

[pnpm] pnpm 与 npm/yarn 的对比

引用
1
来源
1.
https://www.cnblogs.com/feixianxing/p/18359597/node-package-manager-compare-pnpm-vs-npm-and-yarn

JavaScript应用程序通常依赖于许多外部库,这些依赖项通常通过包管理器来管理。默认情况下,Node.js使用NPM作为包管理器。由于早期的NPM存在各种不足,社区后来开发了Yarn和pnpm作为替代品。本文将详细对比分析这三种主流的JavaScript包管理器的特点和优劣。

早期NPM的不足

在NPM 3.0之前,NPM使用了嵌套依赖树的结构。这意味着如果一个项目的多个依赖项需要同一个包的不同版本,NPM会在每个依赖项的目录中重复安装该包。这种结构会导致node_modules目录非常深,特别是在Windows系统中,这可能导致路径长度限制的问题。

每次安装包时都会重新从头开始解决依赖关系,并逐个下载和安装包。即使是已经安装过的包,也可能会再次下载,而没有利用缓存机制。这种重复安装的策略会导致安装十分缓慢。

在早期版本的NPM中,没有类似yarn.lockpackage-lock.json这样的锁文件。这意味着即使package.json中指定了版本范围(例如^1.0.0这种表示可以接受一个范围的版本),依赖关系的解析和安装仍然是动态的,可能会因为时间或网络状态的不同而导致不同的版本被安装。

NPM在3.0版本引入了扁平化依赖树,以解决早期版本中嵌套依赖树带来的问题,但是扁平化依赖带来了新的问题:

  • 依赖冲突:扁平化依赖树的设计将所有依赖项都安装在项目的根node_modules目录中,这意味着多个包可能会共享同一个依赖项的版本。如果不同的包需要不同版本的相同依赖项,就可能会发生冲突。
  • 幽灵依赖:依赖的依赖被平铺在根node_modules目录中,这意味着即使应用的package.json中没有声明的依赖,也可以被引入并使用。这种现象会导致依赖关系和依赖版本的不明确。


图中的虚线就代表*幽灵依赖,也叫隐式依赖。依赖E原本是B的依赖,但是被扁平化后提升到node_modules顶层。这个E没有被显式地在package.json中声明,但是结合node.js的模块解析机制可知这个依赖是可以被Project引入的。这种意料之外的依赖关系会使得项目难以维护。*

Yarn

Yarn的提出是为了解决NPM的不足,它具有以下特点:

  • 确定性安装:Yarn引入了yarn.lock锁文件,明确了依赖的版本。
  • 更快更小:Yarn通过并行下载以及引入缓存机制来加快安装速度,并且由于缓存的存在,在离线状态下也可以安装已缓存过的依赖。
  • 扁平化依赖结构:减少了路径深度,提高了依赖解析的速度。解决了依赖冲突问题:Yarn会通过将不同版本的依赖项放在各自子目录的node_modules中来解决冲突,而不是强制将所有依赖都安装在顶层。

  • 可以通过配置workspaces支持monorepo

不足

  • 没有解决幽灵依赖的问题;
  • workspaces配置较繁琐。

pnpm

pnpm的特点:

  • 节省磁盘空间:npm和Yarn会在每个项目的node_modules目录中为所有依赖项存储完整的文件副本。如果有多个项目依赖相同的包,那么这些包会被重复存储pnpm使用中心化的store统一存储安装的包,项目内的依赖通过链接指向store中的依赖。如果有多个项目依赖相同的包,都指向store中单一的包。
  • 安装速度更快:pnpm的中心化store可以更大程度地复用依赖包,使得安装依赖这一步骤更快完成。
  • 支持monorepo,配置比起yarn来说相对简单,并且得益于pnpm的特性,安装依赖很快。
  • 非扁平化的node_modules:上文说到yarn和npm为了解决路径过长、依赖管理复杂等问题,将依赖进行扁平化管理。但是也带来了幽灵依赖等新问题。pnpm的创新点在于提出了基于符号链接的非扁平化node_modules结构,解决了幽灵依赖问题。

硬链接和软链接:在Linux操作系统中,每一个文件对应一个inode(索引节点)。链接是一种在共享文件和访问它的用户的若干目录项之间建立联系的一种方法。

  • 硬链接是文件的别名,和源文件指向同一个inode。即硬链接和源文件是同一个文件。
  • 软连接也叫符号链接,是一种特殊的文件类型,其中包含对另一个文件的引用。软链接可以看作是对一个文件的间接指针,类似于Windows操作系统下的快捷方式。即软链接和源文件是不同文件。

在Windows中也有软硬链接的概念,在cmd中通过mklink指令创建链接:

  • 硬链接
mklink /H link_name target_file
  • 软链接
mklink link_name target_file

pnpm的node_modules结构:pnpm将实际的依赖文件都安装到全局store中,在项目中的node_modules文件夹内通过创建链接来使用store中的依赖。与yarn和npm直接将所有依赖平铺在node_modules中的做法不同,pnpm在node_modules中创建了一个.pnpm文件夹,再将所有依赖都平铺在这个文件夹中。这样node.js的模块解析算法就无法引入非顶层依赖了,故解决了幽灵依赖问题。

.pnpm中的依赖通过软链接建立依赖之间的父子关系,并通过硬链接指向实际存在于全局store中的依赖包。在package.json中显式声明的依赖会通过软链接提升到node_modules文件夹下,因此node.js可以正常解析package.json中声明的依赖。

.pnpm中,依赖通过.pnpm/<name>@<version>/node_modules/<name>的形式进行记录,可以看到同一个包的不同版本会被分开记录

如上图,项目中只有express这一个依赖,而express有许多子依赖,这里只列举了qs这一个依赖。可以观察到,这种基于链接的node_modules结构实现了:

  • 项目的node_modules只能解析到package.json中显式声明的依赖,解决了幽灵依赖问题
  • 所有依赖都被平铺在.pnpm文件夹中,不会导致过长的文件路径
  • 实际的依赖被安装在全局的store中,项目中仅通过硬链接进行关联,节省了磁盘空间
  • 观察到express和它的依赖同属于一个文件夹层级(图中蓝色区域),express所有的依赖都软链至了node_modules/.pnpm/中的对应目录。把express的依赖放置在同一级别避免了循环的软链。

现在的NPM

yarn和pnpm属于社区产物,NPM作为官方的包管理器,一直在吸收社区好物的优点。现在的NPM也有了锁文件来明确依赖的版本,并且也通过使用缓存、改进依赖解析算法等手段加速了安装。NPM在7.0版本之后也支持配置monorepo了,可以在package.json中直接配置,但是只支持一些简单的功能。yarn则提供了插件系统。

总结

特点
NPM
Yarn
pnpm
安装速度
较慢
较快
大部分情况下比Yarn快
依赖管理
直接安装到node_modules
通过缓存加速安装
中心化store,依赖通过符号链接安装
磁盘空间使用
中等
最低,通过去重和链接机制
依赖冲突处理
容易出现冲突
通过锁文件和解析依赖减少冲突
严格隔离各依赖版本,减少冲突
锁文件
package-lock.json
yarn.lock
pnpm-lock.yaml
幽灵依赖问题
可能发生
可能发生
严格依赖树,避免幽灵依赖
monorepo支持
基础支持
功能丰富,包含插件系统
高效的工作空间管理,模块共享更优化
安装一致性
可能由于缓存和平台差异而不一致
高,一致性较好
更高,通过全局硬链接机制确保一致性

性能对比图像来自pnpm官方文档:Benchmarks of JavaScript Package Managers | pnpm中文文档 | pnpm中文网

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号