前因
事情是这个样子滴,昨天当我忙于写需求无法自(摸)拔(鱼)的时候,有个同事跑过来问:他有若干个项目,A
、B
、C
,他们都依赖了同一个 npm 包,姑且叫joker-js
。但是 A/B/C 中的版本号都不同。但是有的 joker-js
装在了 项目目录的 node_modules
下,即 node_modules/joker-js
,有的装在了他自己的项目中 node_modules/B/joker-js
(这里多做一点解释,我和这个同事不是一个团队,他们负责了我们项目的一个部分,他们的项目以 npm 包的形式加入到我们的项目中,也就是说他们的项目 A/B/C 最后都会被安装到项目的 node_modules 中)。
老哥蒙了,这老哥就想知道为啥?另外他还有个诉求,他想让最近维护的 A 有一个相对低版本的 joker-js 安装到 A 自己的 node_modules。
当我收到这个问题的时候,我淡定了说了一句——"好问题"(≈我不会啊,你这个时候来问我,我这需求也写不下去了啊,要不要去查一查,好烦啊😂😂)
重新梳理
- 项目
my-project
,package.json
如下
{
"name": "my-project",
"dependencies": {
"A": "^1.0.7",
"B": "^2.1.3",
"C": "^0.0.2"
}
}
A
的package.json
如下
{
"name": "A",
"dependencies": {
"joker-js": "^1.0.2"
}
}
B
的package.json
如下
{
"name": "B",
"dependencies": {
"joker-js": "^1.0.7"
}
}
C
的 package.json 如下
{
"name": "C",
"dependencies": {
"joker": "2.0.3"
}
}
有了上面的 package.json
,我们执行 npm i
命令,得到了项目的 node_modules
,及 my-project/node_modules
my-project
node_modules
├── A
├── B
│ └── node_modules
│ └── joker-js
├── C
│ └── node_modules
│ └── joker-js
└── joker-js
经过反复的删除 node_modules
和重新 npm i
的步骤后,我们发现结果是稳定的,一定有一个 joker-js
的包会装在项目目录下(my-project/node_modules/joker-js
),另外两个包B/C
则稳定的有自己的 node_modules
,并且包含自己的 joker-js
包,即(my-project/node_modules/B/node_modules/joker-js
和 my-project/node_modules/C/node_modules/joker-js
)。
开始排查
1. 项目 my-project 也依赖了 joker-js
我猜测是我们的项目依赖了这个包,这才导致这个包被安装到了项目的 node_modules
,但是之后我排查了my-project
的 package.json
,发现并没有直接依赖 joker-js
,所以这条路走不通了。
2. 是 A/B/C 中的某一个的 joker-js 被安装到了项目的 node_modules 中
通过 npm ls joker-js
命令查找项目中所有的关于 joker-js
的依赖关系,得到所有关于 joker-js
的依赖关系图。
├─┬ A@ 1.0.2
│ └── (joker-js)
├─┬ B@ 1.0.7
│ └── (joker-js)
├─┬ C@ 2.0.3
└── (joker-js)
通过查看node_modules/joker-js
的 package.json
的版本号为 1.0.2
,这就实锤了,看起来就是特喵的依赖的依赖被提升了。
既然是稳定的这个结果,说明这一定是 npm 处理的,这是什么操作,疑惑(才疏学浅)?
3.查找答案
3.1 关键词
查找过程中通过搜索引擎查询 npm package version conflict
搜索到正确的答案,这是 npm
从 v3 以后的一个操作,意在减少 node_modules
中依赖目录的层级。就是说如果 项目依赖了 A
,而 A
依赖了 joker-js
,那么 joker-js
不会安装到 node_modules/A/node_modules
下,而是被装到项目的 node_modules
中,也就是上文中的问题。
3.2 多项目依赖同一个包
但是还有个问题没有解决,如果多个项目都依赖了 joker-js
咋办,因为项目 node_modules
下不可能存在多个 joker-js
目录啊?
如果被依赖的 joker-js
版本号相同,这就好办了,npm
会安装一份到项目的 node_modules
下。但是如果各个版本号不同的话,这个问题的答案就是前文中问题的表现了,npm
只会把某一个装到项目的 node_modules
下,其余的都装在各自的 node_modules
中,即上面的 B/C
项目,最终 B/C
的 joker-js
的安装路径:my-project/node_modules/B/node_modules/joker-js
,C
的也是同理。
3.3 npm 如何决定把哪个 joker-js 装在项目 node_modules
特喵的 npm
是如何翻牌子的呢,到底选哪个版本的包?npm
的原文是这样说的:
.... but different trees may be produced if two dependencies are requested for installation in a different order.
用人话说就是安装顺序不同,最后被装在项目 node_modules
的 joker-js
的版本就不同了,总结就是:先入为主
。所以很显然,在执行 npm install
的时候,最先被解析到的 joker-js
就会被 npm
放到 node_modules
了,剩下的各回各家,各找各妈。
关键是这个顺序怎么得来呢?前面排查的时候我有使用过 npm ls joker-js
查找一来关系,最后 terminal 输出的顺序就是 npm
的解析顺序,他会按这个顺序去安装依赖。还有一个更简单的方法,看 package.json
文件,如果没有手动的改动过 package.json
中 dependencies
的顺序,你会发现 package.json
也不是胡乱组织的,目测是字典序排列的包的名称,目前看来,npm ls
的结果是按照 package.json
的顺序组织的。
总结
首先,我告诉老哥,目前看起来没戏,你想让 (后面勘误)。另外,你这么搞我们的包体积受不了,你这 n 多版本,就会被打包 n 多份,尽快统一一个版本号,拿个排期出来(老哥表示,新版本2.xx已经在测试了,稳定了就会都升级起来)。A
的 joker-js
自己用没戏了
总结起来,npm
会安装解析 package.json
所得,把第一个解析得到的 joker-js
装在项目目录下,后面如果解析到同版本的就不再安装,如果解析到不同版本的,就装在该依赖自己的 node_modules
下面。
最后附 npm.js 官方文档传送门
七、勘误
前面总结时说没办法解决私有 node_modules/ 中的 joker-js 和项目的一样,最近发现了解决方案,即 package.json 中的 overrides 字段,附送传送门