关于包管理器Npm、Yarn和Pnpm的一些总结

开发 前端
对于存储大量依赖的情况,pnpm提供了「pnpm store prune」命令,可以定期清理不再使用的依赖项,释放磁盘空间。

写在前面

在Node.js生态系统中,包管理器是至关重要的组件之一,它们负责维护各种应用程序和库之间的依赖关系。npm是Node.js的默认包管理器,它的初始版本是npm1,但是它很快就被npm2所取代。

npm2

关于npm2最初作为包管理管理,采用的是node_modules嵌套模式,即每个包都会有自己独立的node_modules,且会将各自依赖进行安装,依赖的依赖也会产生自己的node_modules,这样就产生了“嵌套依赖”。

就像回调嵌套一样,容易陷入回调地狱,嵌套依赖也不例外。

这种嵌套依赖的模式,虽然可以使依赖项的版本更加明确和稳定,但是在实际应用中也存在一些问题。其中最大的问题是包的嵌套层级很深,这可能会导致安装和更新依赖项的时间变长,并增加包的大小。此外,由于每个包都有自己的node_modules文件夹,这可能会导致文件系统中出现大量重复的依赖项,从而占用更多的磁盘空间。

在实际操作中,当需要在特定的Node.js版本中使用npm2时,可以使用Node Version Manager (nvm)来管理多个Node.js版本。例如,在切换到Node.js v4.0版本时,对应的npm版本是npm2.x。

图片

为了更好地说明嵌套依赖的问题,我们可以通过安装koa来演示。koa是一个基于Node.js的Web应用程序框架,它有许多依赖项,我们可以使用以下命令来安装koa:

npm install koa

在安装koa时,npm会自动下载和安装所有必需的依赖项,并将它们安装到koa的node_modules文件夹中。如果我们检查koa的node_modules文件夹,我们会发现它包含了大量的依赖项,这些依赖项中又包含了更多的依赖项,导致整个文件夹的嵌套层级变得很深。

图片

对于多包之间会存在公共依赖,如果对于每个依赖都生成自己独立的node_modules,那么就会对相同包重复安装多次,这就会占据很大的磁盘空间。且无限嵌套,也会超过windows的最大文件路径长度限制(265个字符)。

嵌套依赖项的模式是npm2中的一个特性,虽然可以保证依赖项的版本稳定性和精确性,但是它可能会导致嵌套层级变得很深,并占用大量的磁盘空间。

yarn

我们想到,既然树形结构存在弊端,为什么不将依赖包在根node_modules进行扁平化处理,这不就解决了依赖嵌套、依赖重复和路径限制问题了?

此时新方式yarn就横空诞生。

当使用yarn进行依赖管理时,我们可以看到所有依赖都会被安装在根目录下的node_modules文件夹中。与npm2不同的是,yarn采用了扁平依赖项的模式,这意味着相同的依赖包只会被安装一次,并且不会存在多个嵌套的node_modules文件夹。

使用yarn add koa进行安装,可以看到通过yarn进行管理的依赖全部平铺在根node_modules下,且没有重复依赖安装的问题。

图片

但是,当某些依赖包存在多个版本时,yarn会将其中一个版本提升到根node_modules文件夹中,而其他依赖包则会继续维护自己的版本。这可能会导致某些依赖包无法正常工作,因为它们可能需要使用特定版本的依赖包。为了解决这个问题,yarn仍然需要使用嵌套的node_modules文件夹,以确保每个依赖包使用正确的版本。

图片

值得注意的是,yarn采用的扁平依赖项模式具有许多优点,例如更快的安装速度,更少的磁盘空间占用和更少的依赖冲突问题。此外,yarn还提供了一个lock文件,该文件记录了所有依赖项的确切版本和位置,以确保依赖项的版本稳定性和一致性。

yarn的变与不变:

yarn采用了更加高效和可靠的依赖项管理方式,可以有效地避免依赖冲突和嵌套的问题。但是,对于某些多版本依赖包,yarn仍然需要使用node_modules嵌套的方式来确保每个依赖包都使用正确的版本。

npm3

npm3在2015年发布时引入了一种新的依赖项安装算法,称为“扁平依赖项”。其主要原理是通过将所有依赖项都放置在同一个目录下,并使用符号链接来实现依赖项的共享。

在npm3中,所有依赖项都被直接安装到根目录下的node_modules中,而不是像npm2一样在每个依赖包中嵌套一个node_modules目录。这种扁平化的结构可以减少依赖项的嵌套层级,从而降低了磁盘空间的占用和文件路径的长度。在这种模式下,所有依赖项都被安装到顶级node_modules文件夹中,这样就避免了嵌套依赖项的问题。这种模式虽然简单,但是它可能会导致依赖项的版本不稳定,从而可能会导致依赖冲突的问题。

当我们使用npm3安装koa包时,它会首先检查该包所需的所有依赖项是否已经安装,如果没有安装,则会将这些依赖项直接安装到根目录下的node_modules目录中。同时,npm3会使用符号链接将这些依赖项链接到需要使用它们的包的node_modules目录下。

图片

通过使用符号链接,npm3可以实现依赖项的共享,从而避免了依赖项的重复安装和占用大量的磁盘空间。此外,npm3还支持npm shrinkwrap命令,可以生成一个lockfile文件,记录每个包所使用的依赖项的精确版本号,从而避免了版本冲突和不兼容的问题。

shrinkwrap 文件的作用是什么?

这个文件用于记录整个依赖树的结构和依赖包的版本信息,可以保证依赖包的版本稳定性和一致性。

那么使用扁平化方案就能完美解决以上问题吗?当然不是。

  • 幽灵依赖:在声明中没有使用dependencies中的依赖,代码中也可以进行reqiure引入。没有在项目中进行显式依赖,如果别的包不再依赖这个包,就会导致代码因为依赖这个包,而没有进行安装,最终不能正常运行。
  • 磁盘浪费:对于依赖包只会提升一个,存在多个版本时其余包同样得进行拷贝到各自node_modules下,依然会存在磁盘空间浪费。

什么是幽灵依赖?

在安装和使用某个第三方包时,该包依赖的其他依赖没有在它的js文件中显式引入的情况。这些依赖可能在代码中被引用,但是没有被包含在软件包的package.json文件中。这种情况被称为“幽灵依赖”。

举个例子,假设有个项目需要依赖包 A 和 B,而这两个包都依赖于包 C,但是包 A 依赖于包 C 的版本 1.0.0,而包 B 依赖于包 C 的版本 2.0.0。在 npm2 中,这两个版本的包 C 会被分别安装在 A 和 B 的 node_modules 目录下,不会产生冲突。但在 npm3 中,这两个版本的包 C 可能会被安装在同一个 node_modules 目录下,这时候就会产生冲突,导致代码无法运行。

图片

虽然在npm3提供了 npm dedupe 命令,可以是手动输入命令将重复的依赖项合并到顶层 node_modules 目录下,避免了幽灵依赖的问题。但是好像并没有很智能。

总的来说,npm3通过采用扁平化的依赖管理结构和符号链接机制,引入 shrinkwrap 文件实现了依赖项的共享和版本精确控制,并且减少了依赖项的嵌套层级和磁盘空间占用。可以手动使用 dedupe 命令等方式,解决了 npm2 中出现的幽灵依赖问题,提高了包管理的效率和可靠性。

pnpm

针对上面遗留下的两个问题,pnpm横空出世,采用硬链接和符号链接来管理依赖项,以减少重复下载和占用空间,从而有效地解决幽灵依赖和磁盘浪费的问题。

  • 基于内容寻址的文件系统来存储磁盘的文件
  • 不会重复安装同一个包,只在磁盘中写入一次,而后在使用的地方通过hardlink
  • 同包不同版本,也尽可能复用代码

link:也就是软硬连接,这是操作系统提供的机制。

  • 硬连接就是同一个文件的不同引用
  • 软链接是新建一个文件,文件内容指向另一个路径

具体来说,当使用 pnpm 安装koa包的依赖项时,它会首先检查系统上是否已经安装了所需的依赖项。如果已经安装,则 pnpm 将创建一个符号链接到该依赖项,而不是在当前项目中复制该依赖项。这样就避免了重复下载和占用磁盘空间的问题。

我们在命令行输入:

pnpm add koa

图片

图片

此外,pnpm 还支持不同的包引用方式,如路径引用和 git 仓库引用,这使得 pnpm 可以更快地安装依赖项并减少重复下载,从而提高开发效率和依赖项管理的可靠性。通过将包从全局 store 进行硬链接到项目的虚拟 store 中,pnpm 可以避免多次拷贝文件和深度嵌套路径过长的问题,从而进一步减少磁盘空间的占用和提高性能。

图片

PNPM 的核心思想是在整个项目内共享依赖项,而不是每个项目都拥有自己的依赖项副本。

这是官方文档提供的原理图:

图片

可以看到有个公共的依赖包安装池,然后通过软链接引入到各个项目所需要的依赖中,这样就减少了幽灵依赖、依赖嵌套和重复下载的问题。

PNPM的优点如下:

节省磁盘空间:pnpm采用链接的方式将依赖项共享到全局store中,避免了每个项目都需要拷贝一份依赖包的问题,从而显著减少了磁盘占用空间。

提升安装速度:pnpm不需要每次都下载相同的依赖项,而是从全局store中直接链接到各个项目中,因此可以极大地提高安装速度。

避免了幽灵依赖、重复依赖和依赖嵌套:pnpm采用链接的方式,避免了项目之间依赖相同包不一致的问题,同时避免了重复安装相同版本的依赖项和依赖嵌套的问题。

支持多种包引用方式:pnpm支持路径引用和git仓库引用,可以更加灵活地管理依赖项。

天生支持monorepo管理:得益于pnpm的软链接特性,可以在同一个workspace下共享依赖和模块等。

另外,对于存储大量依赖的情况,pnpm提供了「pnpm store prune」命令,可以定期清理不再使用的依赖项,释放磁盘空间。

参考文章

  • 关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?
  • pnpm 是凭什么对 npm 和 yarn 降维打击的
  • 现代前端工程为什么越来越离不开 Monorepo?
  • 现代前端工程为什么越来越离不开 Monorepo?
  • 为什么越来越多的项目选择 Monorepo?
  • pnpm官方文档

写在最后

最后对不同包管理器的优缺点、特点做了一些总结:

图片

学而知不足,水平有限,还望诸君多多指教。觉得文章不错的读者,不妨点个关注,收藏起来上班摸鱼的时候品尝。

责任编辑:武晓燕 来源: 前端一码平川
相关推荐

2022-02-28 10:22:08

前端管理工具

2022-02-21 09:58:31

包管理器npmyarn

2024-06-20 08:06:04

2021-02-25 07:24:35

pnpm包管理器前端

2022-05-26 08:01:44

Pnpm包管理器磁盘

2021-11-29 12:11:09

npm包管理器工具

2022-09-16 22:23:35

pnpmCLI软件

2022-02-25 14:19:56

依赖管理前端命令

2021-11-11 11:13:20

js Npm基础

2024-05-10 08:41:05

NPMYarn

2020-09-28 06:45:42

故障复盘修复

2020-05-19 14:35:42

Shell脚本循环

2020-04-10 08:50:37

Shell脚本循环

2022-08-03 00:04:29

pnpmyarnnpm

2021-07-27 12:58:46

Linux包管理器安命令

2023-02-10 09:46:04

bash脚本变量

2018-07-30 08:41:48

VueReact区别

2020-03-09 11:43:35

RustCargo编程语言

2020-12-03 12:06:54

HarmonyOS

2017-09-05 09:17:47

Java编程用法总结
点赞
收藏

51CTO技术栈公众号