#毫无营养,只是第一次整理的初稿,请阅读参考文章,而且废话挺多哈哈。
一,我为什么整理出这一篇文章?
一直以来我对包管理器都不是很熟知,只是本能地认为pnpm>yarn>npm。但是我的主管就经常使用yarn而非pnpm。很多开源项目也是使用yarn,虽然这可能存在时间上的原因。但是这不禁让我想更加深入地了解一下前端包管理器。
二,包管理器是干啥的?
写代码时,很多功能我们不会从零开发手写,有很多其他人写的现成的包可用。对于这些包我们需要进行管理。
常见的我知道的包管理工具:
- JS:bower、npm、yarn、pnpm
- Java:Maven、Gradle
- Go:dep、glide
- Python:pip
1,怎么实现依赖管理?
简单地考虑地话,我们需要考虑我们的依赖的版本问题(依赖是会更新的吧,我们总不能跟着人家变吧)、依赖的存储问题(我们下载下来的第三方依赖怎么存储,他们之间可能还存在某种关系,数据结构怎么设计)
版本标识:A.B.C
A为主版本号,当接口发生改变,与之前的版本不兼容时。
B为次版本号,当增加了功能,但是向后兼容时。
C为补丁版本号,当修复了缺陷,但是向后兼容时。
修饰符
6.0.3 安装指定6.0.3版本 ~6.0.3 安装6.0.X中最新的版本 ^6.0.3表示安装6.X.X中最新的版本 *表示会直接安装最新版本
2,包管理器面临的难点
- 网络:部署在国外的第三方包怎么下载到国内
- 依赖不确定性:依赖包使用的修饰符原因导致我们可能依赖的包会改变,这种情况怎么办
- 嵌套层数太深:A依赖B、B依赖C、C依赖D,这会导致安装很耗时
- 依赖冲突:A依赖B的1.0.0,C依赖B的2.0.0,我们是都下载下来呢还是只下载一个。
- 磁盘空间占用大:依赖包每个项目都要下载一次,占用磁盘空间很大。
- 目录路径很长:对windows来说,很多程序无法处理超过260个字符的文件路径名。
这些面临的问题,目前都有了一定的解决方案。
三,他们之间有什么区别?
所有的包管理器都有很多缺点,你只需要选择你可以忍受的。
1,Bower vs Npm:
历史上:
npm是2009年跟随Node.js出现而诞生的,它最初的目的是为了管理node.js模块,后来逐渐扩展到前端开发中,逐渐被广泛使用。
而Bower是在npm出现之后出现的。它在2012年出现,定位是前端开发中的包管理器(专门做了优化)。Bower在一段时间内得到广泛使用,但是后来随着npm的发展,Bower逐渐没落,并且于2017年宣布停止维护。
功能上:
npm专注于javascript脚本,但是Bower包括样式等其他前端资源(文字、HTML、字体甚至图像等等)。
npm做嵌套依赖(但不需要的情况下依旧是扁平的),而Bower扁平依赖。(扁平简单地说就是所有依赖包都放在一起,都只能下载一个版本而且只能下载一次,这就是Bower做出的优化,只下载一次依赖包,不就是优化嘛)
怎么理解呢?如果我们项目的一个依赖包A本身需要用到1.0.0版本的pkg包,另一个依赖B本身要用到2.0.0版本的pkg包。
当扁平处理的话,只会存在一个pkg包。这时就会出现问题。A和B到底谁能正常工作,这就是一种依赖冲突。
用途上:
不能说Bower就一无是处了。比如Bower可以用于运行时(这种情况下为了避免重复),Npm用于其他情况。当然最好还是别用Bower了哈哈。
2,Npm vs Yarn vs Pnpm:
历史上:
前面我已经说了npm的出身了;
而yarn是Facebook在2016年发布的包管理器,旨在解决npm的性能问题和安装过程中的不稳定性,提供了比如离线模式、并行安装和锁定文件(yarn.lock)等功能(等会详细说),除此之外它的workspaces功能非常好用。
pnpm在2016年由Zoltan Kochan创建,旨在减少磁盘空间占用和安装时间,此外还提供了类似yarn的一些功能,比如并行安装和锁定文件(pnpm.lock.yaml)。
我个人是倾向于使用pnpm。
三者的下载量:
可以看出来npm依旧是非常稳健,而pnpm后来者居上,下载量成为最高(但是有水分的,因为npm是默认捆绑在node上,这个没算上,所以还是有点不准滴)
功能上:
(介绍一下三者的功能,其实很多功能三者就相互学习,你有了之后我立马安排上了,所以有时候我们会觉得这三者越来越像了,他们之间的区别也越来越少了)
npm
- 锁定文件:没错,yarn出我觉得挺好,我也出一个
- 扁平目录:npmv3之前是非扁平,npm v3及之后则改为扁平。
yarn
注意:大家一般使用的都是yarn classic(即yarn v1版本),但是现在yarn现在已经更新到yarn modern(yarn v3版本),现在我们通过npm安装的yarn依旧是yarn classic。
我个人试了一下,存在迁移压力(逻辑和之前的不一样,v1就能用,迁移很烦的拜托),故还是用yarn v1哈哈。
离线模式:会缓存下载过的包,意味着不需要再次下载相同的包(可以在没联网情况下载,直接从本地用户目录读取)
#查询yarn缓存目录 yarn cache dir #清除yarn的cache yarn cache clean #改变yarn缓存位置(这东西很吃存储,所以可以换个位置,又一个存储优化小妙招) yarn config set cache-folder "路径" #顺便说一下npm和pnpm的cache吧 #查询npm缓存位置 npm config get cache #清除npm的cache npm cache clean --force #设置npm缓存位置 npm config set cache "路径" #查询pnpm的缓存目录 pnpm c list #清除pnpm的缓存 pnpm cache prune #清除由于版本更新不被引用的依赖包 pnpm store prune #设置pnpm缓存路径 pnpm config set store-dir "路径"
锁定文件(yarn.lock):锁死项目的依赖包版本,确保依赖包版本更新不影响项目开发。
并行下载:避免请求瀑布,提高下载速度
扁平模式:传统情况下(指npm v3版本之前),npm将依赖包安装在每个依赖包的目录下(所以我们不会知道我们的依赖包的依赖包的确切版本),这可能会导致冲突。扁平化将依赖版本信息都放在yarn.lock里,之后找到一个兼容版本,将该版本安装在node_modules根目录下(只安装一次)。
补丁制作:有些第三方依赖包我们想对它进行修改,可以使用yarn进行修复
Yarn PnP(中文名即插即用,yarn v2之后出现的东西,已经被pnpm也实现了):一种新的安装策略,不使用node_module形式,安装速度极大提升,占用内存极大减少。但是目前前端社区很多东西都依赖node_module的查找机制(比如TS类型声明定义,ESLint等等,所以不建议新手尝试这个东西)
Workspaces机制:用于monorepo的依赖管理机制,很受欢迎(与lerna配合使用更香哦)。
yarn的workspaces机制是新手友好的,而lerna是配置更加复杂和多样的(嗯,新手不友好)。社区现在主流的方案是使用yarn workspaces来管理依赖,使用lerna来管理npm包的版本发布。
基本所有功能都被同行做到了,只有workspaces和PnP是有优势的,但是PnP没被大众认可,workspaces机制社区非常喜欢。
pnpm
非常快:比npm和yarn classic(经典模式的yarn,PnP模式下的yarn还是很能打的)都快。传统的三阶段安装是解析、获取、链接但是是全部的包完成才可以下一步。而pnpm不会等全部包下载才进行下一步。
节省磁盘空间:所有文件都存在磁盘的某一个位置上(pnpm-store),如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。
高效:文件为复制或链接内容寻址存储库
支持monorepo和PnP、离线模式、补丁制作:Pnpm内置支持单仓多包和PnP以及离线模式(友军(指yarn)有的我也有嘿嘿,但是确实更猛了,卷吧,都卷吧)
非扁平的node_module目录:
npm和yarn classic安装依赖包都会被提升到模块目录的根目录,这导致我们项目代码可以访问依赖包的依赖(有时候我们没有在dependences写入的包但是能用,是因为我们的dependences有依赖包的依赖包有这个包,这样子导致之后可能会出现问题,这就是幽灵依赖)。所以pnpm使用这种非平铺的方式避免这种问题。
除此之外,我们还要考虑一个问题。这种非扁平的方式npm早期使用,为啥npm放弃了,然后pnpm怎么解决npm当时放弃的原因的问题呢?当时npm放弃是避免npm v2 创建的嵌套
node_modules
引起的长路径问题。而pnpm使用一个.pnpm文件夹,这个文件夹下放置扁平化的依赖包。详情阅读参考文章pnpm相关的部分。非扁平的依赖关系树算法也比较简单
这里还有一个符号链接(软链接,windows的软链接使用junctions替代,本质是一种文件夹形式的硬链接)的机制,它很适配Node的模块解析算法。它既避免了依赖目录过深,又满足node的模板解析算法。参考文章
上一个问题简单的说:pnpm通过hard link(硬链接)来链接到真正的文件资源,项目中则通过使用symbolic link(软链接,又叫符号链接)链接到项目node-modules/.pnpm/node_modules。
-
这里要说明一个问题,我们的pnpm-store要和我们的项目在一个磁盘上,不然它是复制而不是硬链接(因为硬链接只能创建在同一个分区或者说磁盘)。
我们在一个磁盘上创建项目并使用pnpm安装的话,会在当前磁盘创建.pnpm/pnpm-store存放依赖包,然后hard-link到项目里。
这里要单独讲一讲他们的数据结构和对依赖的处理:
以我的一个微型项目为例:
这是pnpm:将所有次级依赖包(依赖包的依赖包)和根依赖包都放在了.pnpm文件夹里。node_modules目录下只放根依赖包。
这是peerdependences(举个例子,ant-design想要安装要求安装指定的react版本,我们有时候install完会弹出警告我们没安装相应的peer),比较复杂,前面的包+后面的peer包以及版本号
这是yarn:所有的依赖包以及(依赖包的依赖包)都放到一起
这是npm:和yarn一样
三者比较表格:功能比较 | pnpm
四,总结
实话说,你用的哪个爽就用哪个呗,真实开发你会发现哪个都行,没啥太大的区别(安装慢点多摸会鱼不香嘛,为啥就要快快快,如果再从空间上来说,我觉得直接硬件提升到2TB,你随便下载,那空间都不带掉的)。我反正是喜欢新东西,我用pnpm(现在大家新项目一般pnpm能做到前两者所有的事情了)。但是有时候用yarn和npm感觉也挺好的,没必要纠结这个,就酱。
#,参考资料
Bower — a package manager for the web
javascript - Bower 和 npm 有什么区别? - SegmentFault 思否
Home page | Yarn (yarnpkg.com)
前端各包管理器清除缓存攻略教程 | npm、yarn、pnpm | 清除缓存方法-诚哥博客 (chengzz.com)
Lerna · 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目 | Lerna 中文文档 (lernajs.cn)
Yarn Workspace使用指南 - 掘金 (juejin.cn)
Yarn Plug’n’Play可否助你脱离node_modules苦海? - 掘金 (juejin.cn)
Fast, disk space efficient package manager | pnpm
都2023年了,别再用 Yarn v1 啦 - JiPai Store
平铺的结构不是 node_modules 的唯一实现方式 | pnpm
Why should we use pnpm? by @ZoltanKochan
基于符号链接的 node_modules 结构 | pnpm
都2022年了,pnpm快到碗里来! - 掘金 (juejin.cn)
PNPM设置全局包的安装路径 - 掘金 (juejin.cn)