前端工程第一步,依赖安装扫盲篇

前端工程第一步,依赖安装扫盲篇

项目包拿过来,第一步先安装个依赖吧

npm i怎么报错了?
npm i不成功了,那我就yarn一下,还怕你不成功?
安装成功了,怎么运行就包错了?
咿,怎么他能运行成功?
诶,怎么这里用了一个没安装的包,居然都没有报错?
打包分析一看,怎么这么多重复包?
脚手架用的是npm,我可以用yarn吗?
为什么本地安装的包版本和部署上去的包版本不一致?

不知道大家在项目开发过程中是否总被这样的问题搞的焦头烂额,今天我们就来把依赖安装这件事情搞的明明白白,再也不怕控制台下那一排排密密麻麻的报错了

我们先从npm开始讲起

npm v2

被node_modules支配的恐惧

npm 作为前端领域最早的包管理工具,其早期版本(v1/v2)的工作模式和现在还是有很大的区别,其中最典型的就是 node_modules 的目录管理。npm v2在安装依赖的时候, 使用的是比较原始的方式:将所有的依赖包放进node_modules中进行依赖嵌套并向下查找,这是一种比较符合直觉的方式

拿以下依赖关系为例:

Application -> A -> B
            -> C -> B

则 node_modules 目录结构为:
cad74b15b62ca5abb1eb459e51fd18c4.jpg

如上图所示,依赖包的安装和目录结构都十分清晰且可预测,但是却带来了两个比较严重问题:

  • 依赖包重复安装
    这个问题在上图中就可以很明显的看出来,B 包被 A 依赖,同时也被 C 所依赖,因此 B 包就分别在 A 和 C 之下分别被安装了一次。这都是没有考虑包版本问题存在的情况下,依赖包都会被重复安装。此种设计结构直接导致了 node_modules 体积过度膨胀,这也是臭名昭著的 node_modules hell 问题。

  • 嵌套层级太深
    同样如上图所示,应用依赖 A 和 C,而 A 的 node_modules 中又安装了 B,如果 B 也依赖其他包,那么 B 又会存在一个 node_modules 中来存放其他依赖包,如此层层递进。

而 Windows 以及一些应用工具无法处理超过 260 个字符的文件和文件夹路径,嵌套层级过深则会导致相应包的路径名很容易就超出了能处理的范围 ,因此会导致一系列问题。比如在想删除相应的依赖包时,系统就无法处理了。

除此之外,npm 还存在着一些问题被人诟病:

  • SemVer 版本管理使得依赖的安装不确定

  • 缓存能力存在问题,且无离线模式

因此面对上述问题,特别是 node_modules 的嵌套结构问题,经过社区的反复讨论,npm v3 几乎重写了安装程序,来试图给开发者带来更好的体验。

npm v3

为了解决上面 node_moduels 嵌套结构所产生的问题, npm v3 提出的解法就是目录扁平化。采用hoist机制进行依赖提升

** hoist 机制 **:

npm v3 在处理 A 的依赖 B 时,会将其提升到顶级依赖,然后再处理 C 包,然后发现 C 依赖的 B 包已经被安装了,就不用再重复安装了。

同样拿之前的依赖结构为例,npm v2 和 npm v3 的安装目录就完全不一致了。
e49153a7abda88fff1df4f65508eda91.jpg

当然会这种情况只是一个理想化的简单 demo,如果存在版本不同的情况呢?
依赖关系变为:

Application -> A_v1 -> B_v1
            -> C_v1 -> B_v2

则扁平化的目录结构为:

40b6d141d10f20a4c81ba665d73bc35a.jpg

如上图所示,在依赖分析过程中,检查到 A v1 依赖了 B v1,因此将 B v1 提升到了顶层。再检查到 C v1 依赖了 B v2 时,发现顶层已经存在了 B v1,因此 B v2 无法提升到顶层,那么只能接着放在 C v1 之下。可以看出,如果出现了同一依赖的不同版本的话,也无法做到完全的扁平化。但是这样的设计在很大程度上确实解决了之前嵌套层级过深的问题。

新的问题

上面提到了 npm v3 通过扁平化设计 node_modules 来尽量规避同一版本依赖包重复安装的问题和减少层级嵌套过深的问题。但是这个设计也不是十全十美的的,在解决旧有问题的同时也产生了新问题。

phantom dependencies (幽灵依赖)

phantom dependencies 也称幽灵依赖,指的是业务代码中能够引用到 package.json 指定依赖以外的包。拿上面的依赖关系为例:
40b6d141d10f20a4c81ba665d73bc35a.jpg

package.json 中实际只写明了 Application 依赖 A v1 和 C v1,但是由于 hoist 机制,B v1 被提升到了 node_modules 的第一层目录中,那么依照 node 依赖查找的方式,在我们的业务代码中是可以直接引用 B v1 包的。虽然乍一看也没有比较大的问题,但是 B v1 的版本管理是不在我们的感知之内的。也许某个时期使用了 B v1 的某个方法看起来没有什么问题,等到下次 A 有更新,相应的 A 引用的 B 版本也有了 breaking change 的更新,那么我们在原本代码中使用 B 的方法可能就出现报错。

doppelgangers(双胞胎陌生人)

将上面提到的依赖关系中再加入一个 D v1 包,则依赖关系变为:

Application -> A_v1 -> B_v1
            -> C_v1 -> B_v2
            -> D_v1 -> B_v2

则目录结构为:
b7e3779fa3232c64bef97506e91944c2.jpg

结果会发现 B v2 又被安装了一份在 D v1 下面。C v1 和 D v1 的依赖都是 B v2 版本,不存在任何差别,但是却依然被重复安装了两遍,这个现象就叫做 doppelgangers,中文名被叫做 “双胞胎陌生人”问题。

被加重的依赖不幂等

先不考虑 doppelgangers 的现象,可以转过来思考一下 B v2 明明有两个却没有提升到顶层,仍然还是 B v1 在顶层,是什么决定的这个关系呢。

安装顺序很重要!
正常来说,如果是 package.json 里面写好了依赖包,那么 npm install 安装的先后顺序则由依赖包的字母顺序进行排序,那如果是使用 npm install 对每个包进行单独安装,那就看手动的安装顺序了。

如果是先安装的 C v1 ,然后再安装的 A v1,那么提升到顶层的就是 B v2 了。

如果情况再复杂一点,即 Application 又依赖了 E v1 的包:

Application -> A_v1 -> B_v1
            -> C_v1 -> B_v2
            -> D_v1 -> B_v2
            -> E_v1 -> B_v1

那么目录结构就会变成
c93b08d7f323eee8dc6377744b8d29b5.jpg

之后的迭代过程中, A v1 包被手动升级成 A v2,其依赖项变成了 B v2,那么本地的依赖树结构就变成了:

2385dd6395ae82bded255d26107a4ad8.jpg

因为是直接升级的 A 版本,而不是删掉 node_modules 进行重新安装,而由于 E v1 存在,那么 B v1 不会被从 node_modules 中删掉,因此 A v2 的依赖包 B v2 仍然得不到提升,而是依然放在 A v2 之下。

但是,当这版代码上传到服务器上进行部署时,依赖进行重新安装,由于 A v2 的依赖会被最先安装,所以服务器上的依赖树结构则为如下:
24255cc923ea4f664081256a18a112fa.jpg

因此可见,本来就因为 SemVer 机制导致的依赖不幂等问题被进一步放大了。

锁文件

npm-shrinkwrap.json

上面提到三个典型问题,其中依赖不幂等的问题在 npm v3 中是提出了相应的解决方法的,那就是 npm-shrinkwrap.json 文件

在 npm v3 版本中,需要手动运行 npm shrinkwrap 才会生成 npm-shrinkwrap.json 文件,之后每次改动版本依赖,都无法自动更新 npm-shrinkwrap.json 文件,仍然需要手动运行更新,因此这个特性对于开发者来说有一定的成本(开发者可能不知道该特性,或者没有每次及时更新)。

图片

package-lock.json

之后受到 yarn.lock 的启发,npm 在 v5 版本中设计了我们现在比较熟悉的 package-lock.json 文件,此时锁文件就是自动生成和更新了。

图片

异同点

package-lock.json 和 npm-shrinkwrap.json 的作用基本一致,只有一些细微差别:
package-lock.json 不会在发布包中出现

之前 npm-shrinkwrap.json 允许在发布包中进行版本控制,这样使得子依赖包的版本不容易被共享,从而增加依赖包的体积。

  • package-lock.json 多了 integrity 参数,用来进行包的SRI验证。

在本地存在 package-lock.json 文件的情况下,npm 就不需要再去请求查看依赖包的具体信息和满足要求的版本,而是直接通过 lock 文件中内容先去查找文件缓存。若发现没有缓存则直接下载并进行完整性校验,如若无误,则安装。

yarn

yarn 0.x 版本正式发布的时候,是在 2016 年,也就是 npm v4 还没有发布之前。Yarn 的诞生是由于当时 Facebook 的工程师不满足 npm 所存在的一系列问题,从而开发出来的一个新的包管理工具。由于后发优势,yarn 0.x 版本吸取了 npm v3 优点的同时,也作出了自己的创新:

  • 扁平化目录结构

yarn 的扁平化结构和 npm 基本类似,但是对重复安装包计算上更加智能一些。

在上面锁文件小节中提到在将 A v1 升级到 A v2 时, npm 安装的时候会出现以下情况:

图片

而 yarn 则会自动地将 B v1 放在 E v1 下面,而 B v2 则被提升到顶层。

  • 锁文件:yarn.lock

对比于当时 npm 的 npm-shrinkwrap,yarn.lock 不需要手动生成,而是自动生成和更新。这一点在 npm v5 中被借鉴。同时加入了以下特性:

  • 并行安装,提升安装速度
  • 缓存机制,支持离线安装

总结

  1. npm v3以后npm与yarn两者设计类似 都有幽灵依赖问题
  2. yarn比npm在依赖提升更智能一点,加入了并行安装和离线缓存的机制,节约磁盘体积好一点
© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容