前端主流包管理器 npm/yarn/pnpm
npm
npm 是包管理器的祖先。它的前身其实是名为 pm(pkgmakeinst)的 bash 工具,它可以在各种平台上安装各种东西。
很多人认为 npm 是 node package manager 的缩写,其实不是,而且 npm 根本也不是任何短语的缩写。
Is "npm" an acronym for "Node Package Manager"? Contrary to popular belief,
npmis not in fact an acronym for "Node Package Manager"; It is a recursive bacronymic abbreviation for "npm is not an acronym" (if the project was named "ninaa", then it would be an acronym). The precursor tonpmwas actually a bash utility named "pm", which was the shortform name of "pkgmakeinst" - a bash function that installed various things on various platforms. Ifnpmwere to ever have been considered an acronym, it would be as "node pm" or, potentially "new pm".
在 npm 之前,项目依赖都是手动下载和管理的。npm 引入了一些概念:
- package.json 文件
- 元数据字段(例如,devDependencies)
- node_modules 中存储依赖项
- 自定义脚本
- 公共和私有包注册
在 node_modules 中存储依赖项、自定义脚本、公共和私有包注册等概念都是 npm 引入的。
npm 在早期采用的是嵌套的 node_modules 结构,直接依赖会平铺在 node_modules 下,子依赖嵌套在直接依赖的 node_modules 中。
在真实场景下,依赖增多,冗余的包也变多,node_modules 最终会堪比黑洞,很快就能把磁盘占满。而且依赖嵌套的深度也会十分可怕,这个就是依赖地狱 (Dependency Hell)。
为了将嵌套的依赖尽量打平,避免过深的依赖树和包冗余,npm v3 将子依赖「提升」(hoist),采用扁平的 node_modules 结构,子依赖会尽量平铺安装在主依赖项所在的目录中。这样不会造成大量包的重复安装,依赖的层级也不会太深,解决了依赖地狱问题,但也形成了新的问题幽灵依赖 (Phantom dependencies)。幽灵依赖是指在 package.json 中未定义的依赖,但项目中依然可以正确地被引用到。幽灵依赖是由依赖的声明丢失造成的,如果某天某个版本的 A 依赖不再依赖 B 或者 B 的版本发生了变化,那么就会造成依赖缺失或兼容性问题。
yarn classic
Yarn 是 Facebook 宣布与谷歌和其他一些公司开发新的软件包管理器,主要解决 npm 当时存在的一致性、安全性和性能问题,他们命名为 Yarn.
速度快
Yarn缓存了每个下载过的包,所以再次使用时无需重复下载- 同时利用并行下载以最大化资源利用率,因此安装速度更快
安全:在执行代码之前,
Yarn会通过算法校验每个安装包的完整性可靠:使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作
Yarn 的架构设计建立在 npm 许多概念和流程之上,Yarn 在最初的发布中对包管理器产生了重大影响。Yarn 在安装依赖的过程中采用了并行安装,yarn 会将每个包缓存在磁盘上,在下一次安装这个包时,可以脱离网络实现从磁盘离线安装。
创新:
- 离线模式:如果你以前安装过某个包,再次安装时可以在没有任何互联网连接的情况下进行。
- 确定性:不管安装顺序如何,相同的依赖关系将在每台机器上以相同的方式安装。
- 网络性能:Yarn 有效地对请求进行排队处理,避免发起的请求如瀑布般倾泻,以便最大限度地利用网络资源。
- 相同的软件包:从 npm 安装软件包并保持相同的包管理流程。
- 网络弹性:重试机制确保单个请求失败并不会导致整个安装失败。
- 扁平模式:将依赖包的不同版本归结为单个版本,以避免创建多个副本。
Yarn 还发明了自己的许多概念,例如:
- 原生
monorpo支持 - 缓存感知安装
- 离线缓存
- 锁文件 lockfile. lockfile 里记录了依赖,以及依赖的子依赖,依赖的版本,获取地址与验证模块完整性的 hash。即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都能得到稳定的 node_modules 目录结构,保证了依赖安装的确定性。所以 yarn 在出现时被定义为快速、安全、可靠的依赖管理。而 npm 在一年后的 v5 才发布了 package-lock.json。
但是,yarn 依然和 npm 一样是扁平化的 node_modules 结构,没有解决幽灵依赖和依赖分身问题。
pnpm
pnpm - performant npm,在 2017 年正式发布,定义为快速的,节省磁盘空间的包管理工具,开创了一套新的依赖管理机制,成为了包管理的后起之秀。
pnpm 的出现是为了解决 Yarn 的问题,因为 Yarn 不解决例如磁盘占用的问题以及内部的发展不公开等原因,所以就自己去开发了一个,目前在使用体验上要比 Yarn 好一些而且解决了一些 Yarn 目前存在的问题以及痛点,感兴趣的同学可以看看原文。Why should we use pnpm?
- pnpm 运行起来非常的快,超过了 npm 和 yarn 是同类工具速度的将近 2 倍;
- node_modules 中的所有文件均链接自单一存储位置;
- pnpm 内置了对单个源码仓库中包含多个软件包的支持;
- pnpm 创建的 node_modules 并非扁平结构,因此代码不能对任意软件包进行访问;
- pnpm 采用了一种巧妙的方法,利用硬链接和符号链接来避免复制所有本地缓存源文件,这是 yarn 的最大的性能弱点之一,内部使用基于内容寻址的文件系统来存储磁盘上所有的文件。
与依赖提升和扁平化的 node_modules 不同,pnpm 引入了另一套依赖管理策略:内容寻址存储。
该策略会将包安装在系统的全局 store 中,依赖的每个版本只会在系统中安装一次。
在引用项目 node_modules 的依赖时,会通过硬链接与符号链接在全局 store 中找到这个文件。为了实现此过程,node_modules 下会多出 .pnpm 目录,而且是非扁平化结构。
- 硬链接 Hard link:硬链接可以理解为源文件的副本,项目里安装的其实是副本,它使得用户可以通过路径引用查找到全局 store 中的源文件,而且这个副本根本不占任何空间。同时,pnpm 会在全局 store 里存储硬链接,不同的项目可以从全局 store 寻找到同一个依赖,大大地节省了磁盘空间。
- 符号链接 Symbolic link:也叫软连接,可以理解为快捷方式,pnpm 可以通过它找到对应磁盘目录下的依赖地址。
这套全新的机制设计地十分巧妙,不仅兼容 node 的依赖解析,同时也解决了:
- 幽灵依赖问题:只有直接依赖会平铺在 node_modules 下,子依赖不会被提升,不会产生幽灵依赖。
- 依赖分身问题:相同的依赖只会在全局 store 中安装一次。项目中的都是源文件的副本,几乎不占用任何空间,没有了依赖分身。
同时,由于链接的优势,pnpm 的安装速度在大多数场景都比 npm 和 yarn 快 2 倍,节省的磁盘空间也更多。
但也存在一些弊端:
- 由于 pnpm 创建的 node_modules 依赖软链接,因此在不支持软链接的环境中,无法使用 pnpm,比如 Electron 应用。
- 因为依赖源文件是安装在 store 中,调试依赖或 patch-package 给依赖打补丁也不太方便,可能会影响其他项目。
yarn berry
2020 年 1 月,yarn v2 发布,也叫 yarn berry(v1 叫 yarn classic)。它是对 yarn 的一次重大改造,主要有以下几点:
- Zero Install:yarn v1 会在项目中安装依赖,而 yarn v2 会在全局 store 中安装依赖,然后通过符号链接的方式引用全局 store 中的依赖。这样的好处是可以减少项目中的依赖,加快安装速度,节省磁盘空间。
- Plug'n'Play:yarn v2 通过 PnP 技术,使得依赖的引用变得更加方便,不再需要 node_modules,也不需要像 webpack 那样配置 resolve.alias。这个特性是 yarn v2 的核心,也是 yarn v2 和 pnpm 最大的区别。
- Zero Config:yarn v2 会自动检测项目中的依赖,不需要再像 yarn v1 一样配置 package.json 中的依赖,也不需要像 pnpm 一样手动安装依赖。
- Workspace:yarn v2 支持 Workspace,可以将项目中的不同模块放在不同的目录中,方便管理。