同一个npm包,怎么有的装在项目node_modules有的装在自己的node_modules

2,865 阅读5分钟

D-Chat_20211130151244.png

前因

事情是这个样子滴,昨天当我忙于写需求无法自(摸)拔(鱼)的时候,有个同事跑过来问:他有若干个项目,ABC,他们都依赖了同一个 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"
  }
}
  • Apackage.json 如下
{
  "name": "A",
  "dependencies": {
    "joker-js": "^1.0.2"
  }
}

  • Bpackage.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-jsmy-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-jspackage.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/Cjoker-js 的安装路径:my-project/node_modules/B/node_modules/joker-jsC 的也是同理。

3.3 npm 如何决定把哪个 joker-js 装在项目 node_modules

fan.jpeg

特喵的 npm 是如何翻牌子的呢,到底选哪个版本的包?npm 的原文是这样说的:

.... but different trees may be produced if two dependencies are requested for installation in a different order.

用人话说就是安装顺序不同,最后被装在项目 node_modulesjoker-js 的版本就不同了,总结就是:先入为主。所以很显然,在执行 npm install 的时候,最先被解析到的 joker-js 就会被 npm 放到 node_modules 了,剩下的各回各家,各找各妈。

关键是这个顺序怎么得来呢?前面排查的时候我有使用过 npm ls joker-js 查找一来关系,最后 terminal 输出的顺序就是 npm 的解析顺序,他会按这个顺序去安装依赖。还有一个更简单的方法,看 package.json 文件,如果没有手动的改动过 package.jsondependencies 的顺序,你会发现 package.json 也不是胡乱组织的,目测是字典序排列的包的名称,目前看来,npm ls 的结果是按照 package.json 的顺序组织的。

总结

首先,我告诉老哥,目前看起来没戏,你想让 Ajoker-js 自己用没戏了(后面勘误)。另外,你这么搞我们的包体积受不了,你这 n 多版本,就会被打包 n 多份,尽快统一一个版本号,拿个排期出来(老哥表示,新版本2.xx已经在测试了,稳定了就会都升级起来)。

总结起来,npm 会安装解析 package.json 所得,把第一个解析得到的 joker-js 装在项目目录下,后面如果解析到同版本的就不再安装,如果解析到不同版本的,就装在该依赖自己的 node_modules 下面。

最后附 npm.js 官方文档传送门

七、勘误

前面总结时说没办法解决私有 node_modules/ 中的 joker-js 和项目的一样,最近发现了解决方案,即 package.json 中的 overrides 字段,附送传送门