幽灵依赖所引出的知识!

5,137 阅读8分钟

事前提醒:阅读本文存在不同想法时,可以在评论中表达,但请勿使用过激的措辞。

问题概述:(慢慢进入话题,不要太快哦!)

1、作为一名前端开发工程师,我们只关注工程代码,往往却忽略了依赖文件、以及node、npm、yarn、pnpm等 具体是个什么。

2、什么是幽灵(幻影)依赖什么是依赖提升? npm的发展历程经历了那些阶段发生了什么故事? yarn是什么?它的出现干什么用?pnpm又是干什么的?

3、怎么去理解package.json? node的寻址过程是什么样的?

威武!!!开聊吧:

image.png

首先自我说明身份,像我这种前端小白龙什么都不懂的)在开发中,我们经尝遇到node、npm、项目依赖版本不对应所引发的大面积爆红,这个时候我们需要补充一下依赖的寻址、以及node_modules 存在的意义,看一下下面这个文章,再回来

node依赖的寻址:Node.js模块查找规则。点击查看文章

在项目中,例如vue的整体项目中,代码中引用依赖文件,这个时候就会发生一个寻址的过程: 查找本js文件所在目录下的node_modules目录,若不存在该目录,则向上一级目录查找node_modules目录。 node_modules目录中查找名为packageName的目录,若不存在,向上一级目录中查找node_modules模块。 在packageName目录中查找package.json文件,查找该文件中main属性标注的入口js文件。若不存在该入口文件,查找packageName目录中是否有index.js文件,若存在则将index.js作为入口js文件。 若连index.js文件都不存在,则再向上一级目录查找node_modules。 没有找到的话,控制台最终就会报错。

好的 理解了这个,我们下边就聊一聊 npm install 的执行 发生了什么事 友情文章

文章中介绍了 npm 在安装的时候

npm install执行后,会检查并获取npm配置,优先级为

项目级别的.npmrc文件 > 用户级别的.npmrc文件 > 全局的.npmrc文件 > npm内置的.npmrc文件

然后检查项目中是否有package-lock.json文件。
如果有,检查package-lock.json和package.json中声明的依赖是否一致

  • 一致:直接使用package-lock.json中声明的依赖,从缓存或者网络中加载依赖
  • 不一致:各个版本的npm处理方式如图

npm安装流程
如果没有,根据package.json递归构建依赖树,然后根据依赖树下载完整的依赖资源,在下载时会检查是否有相关的资源缓存

  • 存在:将缓存资源解压到node_modules中
  • 不存在:从远程仓库下载资源包,并校验完整性,并添加到缓存,同时解压到node_modules中

最后生成package-lock.json文件。

正题来咯

构建依赖树时,不管是直接依赖还是子依赖,都会按照扁平化的原则,优先将其放置在node_modules根目录中(最新的npm规范),在这个过程中,如果遇到相同的模块,会检查已放置在依赖树中的模块是否符合新模块的版本范围,如果符合,则跳过,不符合,则在当前模块的node_modules下放置新模块

首先npm 是怎么知道install那些依赖文件的?

当然是根据package.json的dependencies:{} 和 devDependencies:{} 他们中声明了你项目中所要使用 和 已经安装的依赖名称和版本信息。通常常见的就是 插件、加载器、外部js 库文件封装的依赖库。devDependencies中一般是声明插件和加载器的,而dependencies中就是用的的库文件。举个例子----vue、vuex、vue-router、element-ui、webpack、less-loader、day.js 应该被声明到哪里? 正确是这样:

dependencies:{ vue、vuex、vue-router、element-ui、day.js }

devDependencies: { webpack、less、less-loader }

还是不懂得可以 扩充一下package.json中其他属性的意思和作用

最后 npm安装依赖时,会下载到缓存当中,然后解压到项目的node_modules中。
再次安装依赖的时候,会根据package-lock.json中存储的 integrity、version、name 信息生成一个唯一的 key,然后拿着key去目录中查找对应的缓存记录,如果有缓存资源,就会找到tar包的hash值,根据 hash 再去找缓存的 tar 包,并把对应的二进制文件解压到相应的项目 node_modules 下面,省去了网络下载资源的开销。

看到这里 有一个问题: package.json中定义了10个 那么node_modules就会有10个嘛?

答案是不会,这个时候就出现了我们开篇提到的幽灵依赖问题了。下边有风趣简介,耐心看

如果让你来设计npm 你怎么设计? 我们再来了解一下node_modules 初版什么样子的,为什么会有yarn、pnpm的出现 ,下面就会看到答案

我们使用nvm 去安装一个老版本node 可以去测试一下 不同版本下的node_modules 有什么差别,npm 版本有什么差别。

假如说package.json里依赖了3个包a、b、c 然后a依赖了a_1, b依赖了b_1 , c依赖了c_1

如果**初版npm 去install 的话 你会发现 node_modules中只有3个包 a、b、c **

image.png

然后a中node_modules会有a_1的存在b中node_modules会有b_1的存在c同理...... 会像是一棵树,tree一样,深.......大..... 浪费空间!

这个就是初代npm 的实现方式。 经过迭代 还是没改变这种问思路,思考一下,如果是你,你怎么设计??

此时就出现一个问题,我要重复引用一个依赖 ,就要深度的重复。

比如说:a依赖vue@1.0, b也依赖vue@1.0, 早期npm的构造就是下图

image.png

可以想一下这里的vue 体积,又或者其他库的体积。。。。。

那怎么去解决这个重复引用的问题呢,此时一个叫yarn的做出了创新 先看图

image.png

由之前的tree-----> 把vue@1.0从内部抽出来,扁平到同一级,这样是不是就解决了重复的问题。哈哈哈哈 好傻的感觉......

image.png

当时npm没考虑到的,yarn想到了,然后yarn那段时间大火于开发界。此时更有趣了,npm说:'我敲?赶上我了?我抄!'。 image.png image.png 继而 npm的再次迭代,拥有了这一特点。然后开发界就呈现了,双龙会。那么把树级结构打平?就没有问题了嘛? 继续往下聊。

此刻 我们文章的主题 “幽灵依赖成功浮出水面”,打个比方 ,image.png 看图

image.png

A的package.json的dependencies里有dayjs@1.0

B的package.json的dependencies里有dayjs@1.0

Myproject的package.json的dependencies里没有dayjs@1.0

但是Myproject的view.js有import dayjs@1.0 这个时候项目是可以跑起来的,为什么? 下边补充一下上面提到的 node的寻包过程

首先在当前目录下的node_modules寻找,找到了就直接使用,找不到就会往上一级的node_modules寻找,直到找寻到磁盘根目录的node_modules 找不到 直接抛出对应错误。

此时举个例子:

image.png

在view.js 中import vue from 'vue' 是那个版本?

答案是1.0,

这个时候 能够想到什么是幽灵依赖了吗? 其实简单来说 就是 项目里使用了,但是未在项目的package.json 中定义的包。就是幽灵依赖。 按着原理来说 未在package.json中定义的包 不会被下载,但是它偏偏就存在了,也被下载了,这又是为什么呢? 因为依赖提升,造成的副作用。所以说yarn的这个方式,就是把相同的依赖包进行提升的方式,就会造成幽灵依赖。 换个言简意赅的说法就是:孙子突然和儿子一个地位、同一个级别, 本来想喊儿子的,发现有个孙子的名字和儿子的名字一样,好家伙 直接把这个孙子当成儿子了,这不就差辈分了嘛,也就是官网说的一个 安全问题。 此时这个问题又该怎么解决呢? 突然天空惊雷一响,pnpm 闪亮登场,

概念

performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”

速度快、节约磁盘空间、支持monorepo、安全性高

pnpm 相比较于 yarn/npm 这两个常用的包管理工具在性能上也有了极大的提升,根据目前官方提供的 benchmark 数据可以看出在一些综合场景下比 npm/yarn 快了大概两倍。

存储管理:

按内容寻址、采用symlink

pnpm 跳出npm和yarn两者为了一坨屎而争斗的战场。直接推翻重建工程化,不继续维护屎山、跳出圈子。node的包寻址思路不变,目前的办法已经不能在成为追求极限的标准了,pnpm开始进行更新。

npm1、npm2采用递归管理,npm3、npm3+、yarn依赖扁平化管理消除依赖提升。

pnpm依赖策略:消除依赖提升、规范拓扑结构

安全

之前在使用 npm/yarn 的时候,由于 node_module 的扁平结构,如果 A 依赖 B, B 依赖 C,那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。 但 pnpm 自创了一套依赖管理方式,很好地解决了这个问题,保证了安全性。

21c3f0c919a23f07a5e26855e80789c.jpg

既然 yarn install 花费的时间是 npm install 的一半(不使用缓存的前提下)
缓存和脱机模式使构建过程几乎不花费时间。那么pnpm 岂不是降维打击??? 它的出现,可以直接完全打败npm、yarn嘛?

答案是:不会的,看看目前的使用量来看,npm、yarn 还是一龙一虎。。。。。只不过pnpm是个人,手中有枪杆子

image.png

散会下次再见!!!!! @Suning张凯杰

image.png