

依賴樹表面的邏輯結構與依賴樹真實的物理結構
依賴樹表面的邏輯結構與依賴樹真實的物理結構並不一定相同!
這里要先提到兩個命令:tree -d(linux)和npm ls(npm)
在一個npm項目下:
tree -d命令以樹狀圖的方式列出一個項目下所有依賴的物理結構
npm ls命令以樹狀圖的方式列出一個項目下所有依賴的邏輯結構
以官方文檔為例子:
項目example1有兩個依賴模塊:mod-a模塊和mod-c模塊;
mod-a模塊有一個依賴模塊mod-b@1.0.0模塊
mod-c模塊有一個依賴模塊mod-b@2.0.0模塊
tree -d 和npm ls運行結果如下:(注意npm版本為npm3而非npm2)

先看看下面那個紅框的結果,這應該是“最符合我們理解”的依賴樹,首先項目下形成了一級依賴——mod-a模塊和mod-b模塊,然后以這兩個模塊為父模塊再追加二級依賴模塊mod-b@1.0.0和mod-b@2.0.0
但是!這卻並不是物理上真實形成的依賴樹的模樣,物理上真實形成的依賴樹是上面的那個紅色框。mod-a,mod-c和mod-b竟然同為同一級的依賴。
你可能會問,為什么會形成這樣的依賴樹呢?下面我就來解釋一番
【注意】:下面的圖示全部為依賴樹的物理結構,而不是邏輯結構
關於npm模塊安裝機制的一點猜想
安裝模塊時,可能的方式有兩種:平級式的安裝或嵌套式的安裝(此處僅僅是猜想和假設)

能不能完全采取平級的安裝方式呢?——不能
我們取和上面相似的一個例子:項目APP下有兩個依賴模塊A和B;A又有一個依賴模塊Cv1.0;而B也有一個依賴模塊Cv2.0。顯然,它們並不能同時存在於同一個node_modules下,當安裝的時候,由於npm的作用機制,只能有一個版本的依賴模塊被安裝,其中一個將覆蓋另外一個。

但如果我們僅僅只安裝一個版本的C依賴模塊,將可能會導致A模塊和B模塊不兼容

基於以上原因,npm2選擇了嵌套的安裝方式——
npm2下的模塊安裝機制
npm2安裝多級的依賴模塊采用嵌套的安裝方式:

優點和弊端
優點:解決了版本單一時存在的存在的不兼容問題,實現多版本兼容
弊端:可能造成相同模塊大量冗余的問題,如下:
以上面例子為例,下面這種情況也是合理存在的:

憑感覺也知道,這絕不是什么好現象,那我們如何能在實現依賴間多版本兼容的前提下,減少這種模塊冗余呢?於是npm3做了一下改進
npm3下的模塊安裝機制:
npm3和npm2的不同主要體現在二級模塊的安裝上:
npm3會"盡量"把邏輯上某個層級的模塊在物理結構上"全部"放在項目的第一層級里,具體我概括為以下三種情況:
1.在安裝某個二級模塊時,若發現第一層級還沒有相同名稱的模塊,便把這第二層級的模塊放在第一層級
2.在安裝某個二級模塊時,若發現第一層級有相同名稱,相同版本的模塊,便直接復用那個模塊
3.在安裝某個二級模塊時,若發現第一層級有相同名稱,但版本不同的模塊,便只能嵌套在自身的父模塊下方
這一開始可能有些難理解,所以讓我們看圖說話吧!
先說1:在安裝某個二級模塊時,若發現第一層級還沒有相同名稱的模塊,便把這第二層級的模塊放在第一層級
我們先簡化一下上面的例子:現在項目APP下只有一個一級依賴模塊A,它下面有一個二級依賴模塊C,但npm install的時候,項目下安裝依賴的

npm3中的二級模塊(C v1.0),在項目的一級目錄(node_modules)下沒有相同名稱的模塊時,會被安裝到一級目錄下,從而跟它的父模塊A同級。這就是本文一開始中依賴樹的邏輯結構和物理結構不同的起因。
也就是說:
在npm2中,依賴樹的邏輯結構和它的物理結構相同
在npm3中,依賴樹的邏輯結構和它的物理結構可能不同
再說2:在安裝某個二級模塊時,若發現第一層級有相同名稱,相同版本的模塊,便直接復用那個模塊
在1的基礎上,我們把1的例子還原回之前的復雜一些的場景::項目APP下有兩個依賴模塊A和B;A又有一個依賴模塊Cv1.0;而B也有一個依賴模塊C v1.0(兩個C模塊版本相同)

對npm2,兩個C包是相同的,造成模塊冗余
在npm3中,因為A模塊下的C模塊被安裝到了第一級,這使得B模塊能夠復用處在同一級下;且名稱,版本,均相同的C模塊
npm3就是用這種方式,部分地解決了npm2的痛點(部分)
【從1,2到3的過渡】我在這一小節的開始說:“npm3會"盡量"把邏輯上某個層級的模塊"全部"放在項目的第一層級里”,我想你看完1,2后應該多少有些理解了“盡量”的含義了,但我說了“盡量”,同時也就意味着npm3存在着不能把二級依賴放在第一層級的情況。對此,請看3:
最后說3:在安裝某個二級模塊時,若發現第一層級有相同名稱,但版本不同的模塊,便只能嵌套在自身的父模塊下方
在2中,A,B所依賴的兩個C模塊是相同的,但如果兩個C模塊的版本不同呢?,項目npm install情況如下:

在npm3中,因為B和A所要求的依賴模塊不同,(B下要求是v1.0的C,A下要求是v2.0的C )所以B不能像2中那樣復用A下的C v1.0模塊
(看到這里我想應該能解答你對文章開頭那個例子的疑惑了吧,這個例子和那個例子是幾乎完全一樣的哦)
看到這里,你對npm2和npm3下的模塊工作機制,以及npm3針對npm2的優化有個大體的了解了吧,但請思考一個問題:npm3是否已經把npm2的模塊冗余的缺陷優化到極致了呢? ———答案是沒有,請往下看:
實際上:npm3中仍然可能出現模塊冗余的情況,因為一級目錄下已經有v1.0的C模塊了,所以所有的v2.0只能作為二級依賴模塊被安裝,這樣你就會看到如下的情況

並且在上圖所示的這種特殊情況里,npm3和npm2表現得似乎並沒什么區別
【過渡】那么這有沒有什么解決的方式呢?當然是有的,當A模塊下的C v1.0模塊被更新至C v2.0的前提下,我們可以通過npm dedupe把所有C v2.0的二級依賴模塊“重定向”到一級目錄下的那個C v1.0
利用npm dedupe去除冗余模塊:
npm dedupe做了什么?它能夠把凡是能夠去除的冗余的二級依賴模塊,“重定向”到名稱/版本相同的一級模塊

參考資料 npm官方文檔第二節(how npm works ):https://docs.npmjs.com/how-npm-works/packages
【溫馨提醒】:任何一篇膾炙人口的博客,都比不上一本厚重的書籍和枯燥的文檔
【完】
每天記10個小單詞吧,重在積累哦!
memory:內存 Dependency:依賴 constraints:約束 deploy: 部署 parameter:參數 scope:作用域
ecosystems:生態系統 prefix:前綴 Prior:優先/之前 revoke:撤銷
