事前提醒:阅读本文存在不同想法时,可以在评论中表达,但请勿使用过激的措辞。
问题概述:(慢慢进入话题,不要太快哦!)
1、作为一名前端开发工程师,我们只关注工程代码,往往却忽略了依赖文件、以及node、npm、yarn、pnpm等 具体是个什么。
2、什么是幽灵(幻影)依赖?什么是依赖提升? npm的发展历程经历了那些阶段,发生了什么故事? yarn是什么?它的出现干什么用?pnpm又是干什么的?
3、怎么去理解package.json? node的寻址过程是什么样的?
威武!!!开聊吧:
首先自我说明身份,像我这种前端小白龙(什么都不懂的)在开发中,我们经尝遇到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处理方式如图
如果没有,根据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 **
然后a中node_modules会有a_1的存在、b中node_modules会有b_1的存在、 c同理...... 会像是一棵树,tree一样,深.......大..... 浪费空间!
这个就是初代npm 的实现方式。 经过迭代 还是没改变这种问思路,思考一下,如果是你,你怎么设计??
此时就出现一个问题,我要重复引用一个依赖 ,就要深度的重复。
比如说:a依赖vue@1.0, b也依赖vue@1.0, 早期npm的构造就是下图
可以想一下这里的vue 体积,又或者其他库的体积。。。。。
那怎么去解决这个重复引用的问题呢,此时一个叫yarn的做出了创新 先看图
由之前的tree-----> 把vue@1.0从内部抽出来,扁平到同一级,这样是不是就解决了重复的问题。哈哈哈哈 好傻的感觉......
当时npm没考虑到的,yarn想到了,然后yarn那段时间大火于开发界。此时更有趣了,npm说:'我敲?赶上我了?我抄!'。
继而 npm的再次迭代,拥有了这一特点。然后开发界就呈现了,双龙会。那么把树级结构打平?就没有问题了嘛? 继续往下聊。
此刻 我们文章的主题 “幽灵依赖成功浮出水面”,打个比方 ,
看图
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 找不到 直接抛出对应错误。
此时举个例子:
在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 自创了一套依赖管理方式,很好地解决了这个问题,保证了安全性。
既然 yarn install 花费的时间是 npm install 的一半(不使用缓存的前提下)
缓存和脱机模式使构建过程几乎不花费时间。那么pnpm 岂不是降维打击??? 它的出现,可以直接完全打败npm、yarn嘛?
答案是:不会的,看看目前的使用量来看,npm、yarn 还是一龙一虎。。。。。只不过pnpm是个人,手中有枪杆子