7

一个大型项目常常要依赖很多第三方的模块,而第三方的模块又有自己的依赖,假如其中有两个模块依赖了同一个模块的不同版本,这个时候该模块就要存在两个不同版本,那么它们在 node_modules 中是如何存在的呢? npm 的大量工作都是在处理这样的版本依赖问题。

比如你的项目 yxxx,有如下依赖:

"dependencies": {
    A: "1.0.0",
    C: "1.0.0"
}

而 A 和 C 两个模块有如下的依赖关系。

A@1.0.0 -> B@1.0.0

C@1.0.1 -> B@2.0.0

npm v2 时代

在 npm v2 时代,执行 npm install 后 node_modules 会是这样的:

node_modules
├── A@1.0.0
│   └── node_modules
│       └── B@1.0.0
└── C@1.0.0
    └── node_modules
        └── B@2.0.0

这个时候如果再安装一个模块 D@1.0.0,D 有如下依赖:

D@1.0.0 -> B@1.0.0

安装完成之后,node_modules 会是这样的:

node_modules
├── A@1.0.0
│   └── node_modules
│       └── B@1.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── D@1.0.0
    └── node_modules
        └── B@1.0.0

B@1.0.0 存在了两份,这显然是浪费的,这也是被吐槽最多的点,一个项目中存在太多相同版本的模块的副本。

想想 require 在寻找模块时候的机制,它会向上级目录去寻找,因此 npm 3 做了改变。

npm v3 时代

安装 A@1.0.0 模块,现在的目录结构变为:

node_modules
├── A@1.0.0
└── B@1.0.0

可以看到他们存在于同一级目录,这个时候 A 中的 js 脚本在 A 中找不到 node_modules 后会在父级目录中找到 B 模块。

继续安装 C@1.0.0 模块,因为 C@1.0.0 依赖的是 B@2.0.0 模块,而此时在 node_modules 中已经存在了 B@1.0.0,因此安装后的目录结构是这样的:

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
    └── node_modules
        └── B@2.0.0

现在继续安装一个模块 E@1.0.0,它有如下依赖:

E@1.0.0 -> B@2.0.0

其实 B@2.0.0 已经存在了,只是它位于 C@1.0.0 模块下,

安装完成后目录结构变为了:

node_modules
├── A@1.0.0
├── B@1.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── E@1.0.0
    └── node_modules
        └── B@2.0.0

这个时候 B@2.0.0 又存在了两份。Ok,继续安装一个模块 F@1.0.0,它的依赖关系如下:

F@1.0.0 -> B@1.0.0

这个时候因为 B@1.0.0 已经存在于项目根目录下的 node_modules 中了,因此目录结构是这样的:

├── A@1.0.0
├── B@1.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── D@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── E@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── F@1.0.0

npm v3 去重

好了,这个时候突然 A@1.0.0 需要升级到 2.0.0 版本,依赖关系也变为了:

A@2.0.0 -> B@2.0.0

安装后目录结构变为了:

node_modules
├── A@2.0.0
│   └── node_modules
│       └── B@2.0.0
├── B@1.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── D@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── E@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── F@1.0.0

随后 F 模块也升级至 2.0.0 版本了,依赖关系也变为了:

F@2.0.0 -> B@2.0.0

执行安装,在这个过程中首先会移除掉,F@1.0.0 然后发现,B@1.0.0 已经没有模块依赖它了,因此也移除了 B@1.0.0,然后安装 F@2.0.0,并安装其依赖 B@2.0.0,发现项目根目录的 node_modules 中并没有 B 模块的任何版本,于是就安装在了根目录的 node_modules 中。

得到目录结构为:

node_modules
├── A@2.0.0
│   └── node_modules
│       └── B@2.0.0
├── B@2.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── D@1.0.0
│   └── node_modules
│       └── B@2.0.0
├── E@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── F@2.0.0

坑爹呢,B@2.0.0 存在了很多个副本了。但也不要紧张,通常 npm 会利用链接来将多个副本指向同一个模块。这样的目录结构虽然觉得有些浪费,但是对代码运行没有丝毫影响。也许你想让他好看一点,没有问题,执行命令:

npm dedupe

该命令会遍历模块依赖树,根据模块之间的依赖关系,移动模块的位置,去除重复,让整个 node_modules 的目录结构更加扁平一些。

node_modules
├── A@2.0.0
├── B@2.0.0
├── C@1.0.0
├── D@1.0.0
├── E@1.0.0
└── F@2.0.0

node_modules 目录结构的不确定性

模块的安装次序决定了 node_modules 中的目录结构,这也是为什么明明 dependencies 中依赖的模块但得到的目录结构不同,假如有如下两个模块需要安装:

A@1.0.0 -> B@1.0.0
C@1.0.0 -> B@2.0.0

安装 A 和 C 的次序不同得到的 node_modules 也就不同,因为 npm 会优先将模块放置在根目录下的 node_modules 中,所以先安装 A 和 C 中的哪一个决定了在 根目录下的 node_modules 中存在的是 B 的 2.0.0 版本还是 1.0.0 版本。

只有在手动使用 npm i <package> --save 的时候才会出现这种情况,使用 npm i,npm 会去读取 package.json 中的 dependencies,而 dependencies 是安装字母顺序排列的。


即刻出发_
382 声望11 粉丝

扯淡


« 上一篇
npm 基本用法