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

相關文章
相關標籤/搜索