npm 是如何影响 node_modules 的目录结构的 ?

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

好比你的项目 yxxx,有以下依赖:npm

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

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

A@1.0.0 -> B@1.0.0

C@1.0.1 -> B@2.0.0

npm v2 时代

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

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 有以下依赖:code

D@1.0.0 -> B@1.0.0

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

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 存在了两份,这显然是浪费的,这也是被吐槽最多的点,一个项目中存在太多相同版本的模块的副本。class

想一想 require 在寻找模块时候的机制,它会向上级目录去寻找,所以 npm 3 作了改变。require

npm v3 时代

安装 A@1.0.0 模块,如今的目录结构变为:module

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 是安装字母顺序排列的。

相关文章
相关标签/搜索