一個大型項目經常要依賴不少第三方的模塊,而第三方的模塊又有本身的依賴,假如其中有兩個模塊依賴了同一個模塊的不一樣版本,這個時候該模塊就要存在兩個不一樣版本,那麼它們在 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 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
安裝 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
好了,這個時候忽然 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 是安裝字母順序排列的。