执行了一万遍 npm install ,依然不知道 node_modules 经历了什么?

516 阅读3分钟

业务开发中,我们几乎不需要关注 npm install/yarn 执行之后,node_modules 中的结构是什么样的,但某些时刻,不知道 node_modules 的安装规则,可能让你的代码写起来很顺滑,build 很艰涩。

比如,如果你的项目是基于 create-react-app 创建的,为了避免一些构建问题,react-scripts build 时会对指定的几个包进行版本校验,一旦校验失败,会毫不留情地退出 build 。

你不知道发生了什么,即使按照控制台给的 1、2、3、4、5 的提示操作了无数遍,你依然不知道怎么办才能走出这个泥潭。

你需要理解 npm 包的安装规则。

npm3 之前

npm 3 之前,处理依赖包的方式简单粗暴——只有 app 的直接依赖包被安装在顶层 node_modules 下,依赖包的依赖包,都按照各自的依赖顺序层层叠叠安装在各自的 node_modules 下。

这样的树结构,简单、清晰、符合预期,但对大型项目不友好,浪费空间不说,还会形成嵌套地狱。

npm3

npm 3 之后,node_modules 的结构不再是嵌套的,而是优先扁平安装,所谓“优先扁平安装”,意味着嵌套还是存在的,只是少了很多。

安装包

我们新建了一个小项目,App ,我们的 App 依赖 A ,A 依赖 B,npm3 中形成的依赖树会是这样的:

一个月过去了,功能需要,我们得在 App 里加个 C ,然而 C 依赖的 B 与 A 依赖的 B 版本不同,npm3 怎么安装呢?

$ npm install C --save

C 安装时,由于顶层 node_modules 中已经有一个 1.0 的 B 了,npm3 会把 C 所依赖的 B 2.0 安装在 C 的 node_modules 下,没办法,顶层黄金位置,先到先得:

又过了一个月,功能迭代需要,App 现在需要加一个 D 包,更巧的是, D 竟然也依赖 B 2.0 ,执行

$ npm install D --save

得到如下结构:

而后又以同样的方式安装了 E ,感人的是,E 依赖了 B 2.0 ,现在,我们的 node_modules 结构如下:

而我们的 package.json 里可能有这么一个片段:

{
    A: "1.0",
    C: "1.0",
    D: "1.0",
    E: "1.0"
}

更新包

时光匆匆,A 发布了新版本 2.0,我们迫不及待地想尝试新特性,我们发现 A 2.0 依赖了 B 2.0 ,执行安装 A 的命令后,npm3 会做哪些事情?

  • 删除 A 1.0
  • 安装 A 2.0
  • 留下 B 1.0 ,因为 E 1.0 还在依赖
  • 把 B 2.0 安装在 A 2.0 下,因为顶层已经有了一个 B 1.0

新人来了

有一天,组里来了一名新人,新人在自己的电脑上,clone 了 App ,执行

$ npm install

新人机器里的 node_modules 树结构和我这个老人的结构一样吗?不一样。

还记得我们的 package.json 片段吗?

{
    A: "2.0",
    C: "1.0",
    D: "1.0",
    E: "1.0"
}

如果本地不存在 node_modules ,执行安装命令之后,A 会首先安装,因此 B 2.0 会直接占据顶层位置,体会高处不胜寒的快感。

所以新人会得到这样一个依赖树:

新人果然清新有型。

老人继续升级

又过了两个月,E 2.0 发布了,并且 E 2.0 同样依赖了 B 2.0 ,npm3 更新 E 时会怎么做呢?

  • 删除 E 1.0
  • 安装 E 2.0
  • 删除 B 1.0
  • 在顶层 node_modules 中安装 B 2.0 ,因为现在顶层没有任何版本的 B 了

太可怕了,我想把重复的包都搞掉,因为电脑空间越来越不够用了,我可以执行

$ npm dedupe

就能得到一个清爽的扁平依赖结构:

当然,我也可以假装自己是一个新人,删掉 node_modules ,重新按照 package.json 声明的顺序安装所有包。

如果你的项目中使用的是 yarn ,yarn install 会自动去重。

npm 包里的 vip

终于有一天,我们的 App,需要直接依赖 B 3.0 。

那么 B 3.0 会被安装在哪里?

答案是:顶层 node_modules 下。

直接依赖的 B 3.0 ,就像手持顶层钥匙的房东,在自己需要时,把原来占据黄金位置的 B 2.0 赶了出去,使它们只好各自蜗居在视野狭窄的小房间里。

总结

npm3 安装包时,记住两个规则:依赖路径优先,依赖顺序次之。即项目直接依赖的包优先安装在顶层,不论顺序;对非项目直接依赖的包,先安装的会占据顶层位置。