1. 先来看问题
今天运行一个ts的项目,准备学习,npm install上来就报错:
谷歌后用了句npm install --legacy-peer-deps后顺利解决问题,但当然不能止步于此,所以决定去研究了解下为什么会出现该问题和为什么npm install --legacy-peer-deps就能解决问题。
2. npm怎么处理依赖
首先来看npm是如何处理依赖的,经过翻阅大佬的文章,了解到npm下载以及一些被常提及的npm版本之间的不同。
npm如何下载?
npm install命令输入 > 检查node_modules目录下是否存在指定的依赖 > 如果已经存在则不必重新安装 > 若不存在,继续下面的步骤 > 向 registry(本地电脑的.npmrc文件里有对应的配置地址)查询模块压缩包的网址 > 下载压缩包,存放到根目录里的.npm目录里 > 解压压缩包到当前项目的node_modules目录中。
那么npm如何处理依赖之间的关系呢?比如下载的两个包都引用了同一个A包但是版本不同一个是A1.1一个是A2.0,这时候该如何处理?
这部分了解到的主要就是npm2的递归依次安装依赖到npm3新增的扁平依赖,和npm6到npm7新增的默认安装peerDependencies。下面两个都会记录下来,本次遇到的问题是peerDependencies问题,所以先讲这个(我的npm版本8.19.2)。
3. npm6到npm7的peerDependencies
什么是peerDependencies?
在日常开发中我们接触到的是dependencies和devDependencies:
但这两位在这里不是重点,就不做介绍了。在npm v7中默认安装peerDependencies。它可以避免核心依赖库被重复下载的问题。
举个例子:
假设现在有一个helloWorld工程,已经在其package.json的dependencies中声明了 packageA,有两个插件plugin1和plugin2他们也依赖packageA,如果在插件中使用dependencies而不是peerDependencies来声明packageA,那么npm install安装完plugin1和plugin2之后的依赖图是这样的:
├── helloWorld
│ └── node_modules
│ ├── packageA
│ ├── plugin1
│ │ └── nodule_modules
│ │ └── packageA
│ └── plugin2
│ │ └── nodule_modules
│ │ └── packageA
可以看到在helloworld里面的node_modules已经有packageA,但是plugin1和plugin2的nodule_modules里面还是又安装了一次packageA,这是冗余的,而peerDependencies可以解决该问题,只需要在plugin1和plugin2的package.json中用peerDependencies来声明:
// plugin1 plugin2一样
{
"peerDependencies": {
"packageA": "1.0.1"
}
}
主系统中的node_modules声明一下
{
"dependencies": {
"packageA": "1.0.1"
}
}
此时再在主系统中install后生成的依赖图就像这样
├── helloWorld
│ └── node_modules
│ ├── packageA
│ ├── plugin1
│ └── plugin2
可以看出来这时候生成的依赖图是扁平的且packageA只被安装了一次。
总结下在插件使用dependencies声明依赖库的特点:
- 如果用户显式依赖了核心库,则可以忽略各插件的peerDependencies声明;
- 如果用户没有显式依赖核心库,则按照插件peerDependencies中声明的版本将库安装到项目根目录中;
- 当用户依赖的版本、各插件依赖的版本之间不相互兼容,会报错让用户自行修复。
为什么npm install --legacy-peer-deps能解决问题
上面讲过在NPM v7中,现在默认安装peerDependencies.
在很多情况下,这会导致版本冲突,从而中断安装过程。--legacy-peer-deps标志是在v7中引入的,目的是绕过peerDependency自动安装;它告诉 NPM 忽略项目中引入的各个modules之间的相同modules但不同版本的问题并继续安装,保证各个引入的依赖之间对自身所使用的不同版本modules共存。
自己理解下来就是:
npm install --legacy-peer-deps里面的legacy的意思:遗产/(软件或硬件)已过时但因使用范围广而难以替代的;
npm install xxxx --legacy-peer-deps命令用于绕过peerDependency里依赖的自动安装;它告诉npm忽略项目中引入的各个依赖模块之间依赖相同但版本不同的问题,以npm v3-v6的方式去继续执行安装操作。所以其实该命令并没有真的解决冲突,而是忽略了冲突,以“过时”(v3-v6)的方式进行下载操作。
所以解决该问题还有一个办法就是安装低版本npm=-=。
4. npm2的递归依次安装依赖到npm3新增的扁平依赖
了解完npm7的peerDependency,来到npm2到npm3的区别。这里简单写下来就权当小笔记了。
npm2简单粗暴
首先是npm2处理依赖的方式,它会严格按照根目录下package.json文件的结构以及各个子依赖包的package.json文件的结构,递归地把依赖安装到它们各自的node_modules目录里。不理会层级和重复,装就完事了。
这样的依赖处理方式有很多问题:
- 依赖树的层级非常深。如果需要定位某依赖的依赖,很难找到该依赖的文件所在(例如,如果想定位模块 E,就不得不先知道他在依赖树中的位置);
- 不同的依赖树分支里,可能有大量实际上是同样版本的依赖(例如,A 目录下的 C 和 B 目录下面的 C 如果版本一致,实际上完全一样);
- 安装时额外下载或拷贝了大量重复的资源,并且实际上也占用了大量的硬盘空间资源等(例如,C 模块在依赖目录中出现了两次);
- 安装速度慢,甚至因为目录层级太深导致文件路径太长的缘故,在 windows 系统下删除 node_modules 文件夹也可能失败!
npm3扁平化
npm3通过把一些secondary依赖项和primary依赖项平铺在同一层级的方式来实现这一点。
简单来说就是在根目录已经有的,下面的子项目/插件就不用下载了,但是根目录下和子项目/插件引用相同的包,版本不同,却不兼容,子项目/插件还是继续下载。例子太长,直接放参考文章: