npm 依賴管理中被忽略的那些細節

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提起 npm,大家第一個想到的應該就是 npm install 了,但是 npm install 之後生成的 node_modules 大家有觀察過嗎?package-lock.json 文件的作用大家知道嗎?除了 dependencies 和 devDependencies,其他的依賴有什麼作用呢?接下來,本文將針對 npm 中的你可能忽略的細節和大家分享一些經驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"npm 安裝機制"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A 和 B 同時依賴 C,C 這個包會被安裝在哪裏呢?C 的版本相同和版本不同時安裝會有什麼差異呢?package.json 中包的前後順序對於安裝時有什麼影響嗎?這些問題平時大家可能沒有注意過,今天我們就來一起研究一下吧。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"A 和 B 同時依賴 C,這個包會被安裝在哪裏呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如有 A 和 B 兩個包,兩個包都依賴 C 這個包,npm 2 會依次遞歸安裝 A 和 B 兩個包及其子依賴包到 node_modules 中。執行完畢後,我們會看到 "},{"type":"codeinline","content":[{"type":"text","text":".\/node_modules"}]},{"type":"text","text":" 這層目錄只含有這兩個子目錄:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"node_modules\/\n├─┬ A\n│ ├── C\n├─┬ B\n│ └── C\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果使用 npm 3 來進行安裝的話,"},{"type":"codeinline","content":[{"type":"text","text":".\/node_modules"}]},{"type":"text","text":" 下的目錄將會包含三個子目錄:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"node_modules\/\n├─┬ A\n├─┬ B\n├─┬ C\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼會出現這樣的區別呢?這就要從 npm 的工作方式說起了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"npm 2 和 npm 3 模塊安裝機制的差異"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然目前最新的 npm 版本是 npm 6,但 npm 2 到 npm 3 的版本變更中實現了目錄打平,與其他版本相比差別較大。因此,讓我們具體看下這兩個版本的差異。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"npm 2 在安裝依賴包時,採用簡單的遞歸安裝方法。執行 "},{"type":"codeinline","content":[{"type":"text","text":"npm install"}]},{"type":"text","text":" 後,npm 根據 dependencies 和 devDependencies 屬性中指定的包來確定第一層依賴,npm 2 會根據第一層依賴的子依賴,遞歸安裝各個包到子依賴的 node_modules 中,直到子依賴不再依賴其他模塊。執行完畢後,我們會看到 "},{"type":"codeinline","content":[{"type":"text","text":".\/node_modules"}]},{"type":"text","text":" 這層目錄中包含有我們 package.json 文件中所有的依賴包,而這些依賴包的子依賴包都安裝在了自己的 node_modules 中 ,形成類似於下面的依賴樹:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/68\/68d449e0a83df2341553e5f982cf2fa8.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣的目錄有較爲明顯的好處:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)層級結構非常明顯,可以清楚的在第一層的 node_modules 中看到我們安裝的所有包的子目錄;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)在已知自己所需包的名字以及版本號時,可以複製粘貼相應的文件到 node_modules 中,然後手動更改 package.json 中的配置;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)如果想要刪除某個包,只需要簡單的刪除 package.json 文件中相應的某一行,然後刪除 node_modules 中該包的目錄;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是這樣的層級結構也有較爲明顯的缺陷,當我的 A,B,C 三個包中有相同的依賴 D 時,執行 "},{"type":"codeinline","content":[{"type":"text","text":"npm install"}]},{"type":"text","text":" 後,D 會被重複下載三次,而隨着我們的項目越來越複雜,node_modules 中的依賴樹也會越來越複雜,像 D 這樣的包也會越來越多,造成了大量的冗餘;在 windows 系統中,甚至會因爲目錄的層級太深導致文件的路徑過長,觸發文件路徑不能超過 280 個字符的錯誤;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決以上問題,npm 3 的 node_modules 目錄改成了更爲扁平狀的層級結構,儘量把依賴以及依賴的依賴平鋪在 node_modules 文件夾下共享使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"npm  3 對於同一依賴的不同版本會怎麼處理呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"npm 3 會遍歷所有的節點,逐個將模塊放在 node_modules 的第一層,當發現有重複模塊時,則丟棄, 如果遇到某些依賴版本不兼容的問題,則繼續採用 npm 2 的處理方式,前面的放在 node_modules 目錄中,後面的放在依賴樹中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉個🌰:A,B,依賴 D(v 0.0.1),C 依賴 D(v 0.0.2):"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b4\/b4001c7c47f74009244335b7a9560e64.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是 npm 3 會帶來一個新的問題:由於在執行 "},{"type":"codeinline","content":[{"type":"text","text":"npm install"}]},{"type":"text","text":" 的時候,按照 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 裏依賴的順序依次解析,上圖如果 C 的順序在 A,B 的前邊,node_modules 樹則會改變,會出現下邊的情況:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b3\/b37cdcde7c9fdd851bfecb3d4c64374f.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此可見,npm 3 並未完全解決冗餘的問題,甚至還會帶來新的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"爲什麼會出現 package-lock.json 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼會有 package-lock.json 文件呢?這個我們就要先從 package.json 文件說起了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"package.json 的不足之處"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"npm install 執行後,會生成一個 node_modules 樹,在理想情況下, 希望對於同一個 package.json 總是生成完全相同 node_modules 樹。在某些情況下,確實如此。但在多數情況下,npm 無法做到這一點。有以下兩個原因:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)某些依賴項自上次安裝以來,可能已發佈了新版本 。比如:A 包在團隊中第一個人安裝的時候是 1.0.5 版本,package.json 中的配置項爲 "},{"type":"codeinline","content":[{"type":"text","text":"A: '^1.0.5'"}]},{"type":"text","text":";團隊中第二個人把代碼拉下來的時候,A 包的版本已經升級成了 1.0.8,根據 package.json 中的 semver-range version 規範,此時第二個人 npm install 後 A 的版本爲 1.0.8;可能會造成因爲依賴版本不同而導致的 bug;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)針對 1)中的問題,可能有的小夥伴會是把 A 的版本號固定爲 "},{"type":"codeinline","content":[{"type":"text","text":"A: '1.0.5'"}]},{"type":"text","text":" 不就可以了嗎?但是這樣的做法其實並沒有解決問題, 比如 A 的某個依賴在第一個人下載的時候是 2.1.3 版本,但是第二個人下載的時候已經升級到了 2.2.5 版本,此時生成的 node_modules 樹依舊不完全相同 ,固定版本只是固定來自身的版本,依賴的版本無法固定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"針對 package.json 不足的解決方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決上述問題以及 npm 3 的問題,在 npm 5.0 版本後,npm install 後都會自動生成一個 package-lock.json 文件 ,當包中有 package-lock.json 文件時,npm install 執行時,如果 package.json 和 package-lock.json 中的版本兼容,會根據 package-lock.json 中的版本下載;如果不兼容,將會根據 package.json 的版本,更新 package-lock.json 中的版本,已保證 package-lock.json 中的版本兼容 package.json。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"package-lock.json 文件的結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"package-lock.json 文件中的 name、version 與 package.json 中的 name、version 一樣,描述了當前包的名字和版本,dependencies 是一個對象,該對象和 node_modules 中的包結構一一對應,對象的 key 爲包的名稱,值爲包的一些描述信息, 根據 "},{"type":"link","attrs":{"href":"https:\/\/docs.npmjs.com\/configuring-npm\/package-lock-json.html#requires","title":"xxx","type":null},"content":[{"type":"text","text":"package-lock-json官方文檔"}]},{"type":"text","text":" ,主要的結構如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"version"}]},{"type":"text","text":" :包版本,即這個包當前安裝在 "},{"type":"codeinline","content":[{"type":"text","text":"node_modules"}]},{"type":"text","text":" 中的版本"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"resolved"}]},{"type":"text","text":" :包具體的安裝來源"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"integrity"}]},{"type":"text","text":" :包 "},{"type":"codeinline","content":[{"type":"text","text":"hash"}]},{"type":"text","text":" 值,驗證已安裝的軟件包是否被改動過、是否已失效"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"requires"}]},{"type":"text","text":" :對應子依賴的依賴,與子依賴的 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中 "},{"type":"codeinline","content":[{"type":"text","text":"dependencies"}]},{"type":"text","text":" 的依賴項相同"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"dependencies"}]},{"type":"text","text":" :結構和外層的 "},{"type":"codeinline","content":[{"type":"text","text":"dependencies"}]},{"type":"text","text":" 結構相同,存儲安裝在子依賴 "},{"type":"codeinline","content":[{"type":"text","text":"node_modules"}]},{"type":"text","text":" 中的依賴包"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,並不是所有的子依賴都有 "},{"type":"codeinline","content":[{"type":"text","text":"dependencies"}]},{"type":"text","text":" 屬性,只有子依賴的依賴和當前已安裝在根目錄的 "},{"type":"codeinline","content":[{"type":"text","text":"node_modules"}]},{"type":"text","text":" 中的依賴衝突之後,纔會有這個屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"package-lock.json 文件的作用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在團隊開發中,確保每個團隊成員安裝的依賴版本是一致的,確定一棵唯一的 node_modules 樹;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"node_modules 目錄本身是不會被提交到代碼庫的,但是 package-lock.json 可以提交到代碼庫,如果開發人員想要回溯到某一天的目錄狀態,只需要把 package.json 和 package-lock.json 這兩個文件回退到那一天即可。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 package-lock.json 和 node_modules 中的依賴嵌套完全一致,可以更加清楚的瞭解樹的結構及其變化。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在安裝時,npm 會比較 node_modules 已有的包,和 package-lock.json 進行比較,如果重複的話,就跳過安裝 ,從而優化了安裝的過程。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"依賴的區別與使用場景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"npm 目前支持以下幾類依賴包管理包括"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"dependencies"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"devDependencies"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"optionalDependencies 可選擇的依賴包"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"peerDependencies 同等依賴"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"bundledDependencies 捆綁依賴包"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們來看一下這幾種依賴的區別以及各自的應用場景:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"dependencies"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"dependencies 是無論在開發環境還是在生產環境都必須使用的依賴,是我們最常用的依賴包管理對象,例如 React,Loadsh,Axios 等,通過 "},{"type":"codeinline","content":[{"type":"text","text":"npm install XXX"}]},{"type":"text","text":" 下載的包都會默認安裝在 dependencies 對象中,也可以使用  "},{"type":"codeinline","content":[{"type":"text","text":"npm install XXX --save"}]},{"type":"text","text":" 下載  dependencies 中的包;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/9c\/9cadf4fa78c702784b23d21fc19008e1.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"devDependencies"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"devDependencies 是指可以在開發環境使用的依賴,例如 eslint,debug 等,通過 "},{"type":"codeinline","content":[{"type":"text","text":"npm install packageName --save-dev"}]},{"type":"text","text":" 下載的包都會在 devDependencies 對象中;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/57\/57ed1bcf1fcc15891aa7173f378a9ecf.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"dependencies 和 devDependencies 最大的區別是在打包運行時,執行 "},{"type":"codeinline","content":[{"type":"text","text":"npm install"}]},{"type":"text","text":" 時默認會把所有依賴全部安裝,但是如果使用 "},{"type":"codeinline","content":[{"type":"text","text":"npm install --production"}]},{"type":"text","text":" 時就只會安裝 dependencies 中的依賴,如果是 node 服務項目,就可以採用這樣的方式用於服務運行時安裝和打包,減少包大小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"optionalDependencies"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"optionalDependencies 指的是可以選擇的依賴,當你希望某些依賴即使下載失敗或者沒有找到時,項目依然可以正常運行或者 npm 繼續運行的時,就可以把這些依賴放在 optionalDependencies 對象中,但是 optionalDependencies 會覆蓋 dependencies 中的同名依賴包,所以不要把一個包同時寫進兩個對象中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"optionalDependencies 就像是我們的代碼的一種保護機制一樣,如果包存在的話就走存在的邏輯,不存在的就走不存在的邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"try { \n  var axios = require('axios') \n  var fooVersion = require('axios\/package.json').version \n} catch (er) { \n  foo = null \n} \n\/\/ .. then later in your program .. \nif (foo) { \n  foo.doFooThings() \n} "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"peerDependencies"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"peerDependencies 用於指定你當前的插件兼容的宿主必須要安裝的包的版本,這個是什麼意思呢?舉個例子🌰:我們常用的 react 組件庫 "},{"type":"text","marks":[{"type":"strong"}],"text":"[email protected]"},{"type":"text","text":" 的 "},{"type":"text","marks":[{"type":"strong"}],"text":"package.json"},{"type":"text","text":" 中的配置如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\"peerDependencies\": { \n  \"react\": \">=16.9.0\", \n  \"react-dom\": \">=16.9.0\" \n }, \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設我們創建了一個名爲 project 的項目,在此項目中我們要使用 [email protected] 這個插件,此時我們的項目就必須先安裝 "},{"type":"codeinline","content":[{"type":"text","text":"React >= 16.9.0"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"React-dom >= 16.9.0"}]},{"type":"text","text":" 的版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 npm 2 中,當我們下載 [email protected] 時,peerDependencies 中指定的依賴會隨着 [email protected] 一起被強制安裝,所以我們不需要在宿主項目的 package.json 文件中指定 peerDependencies 中的依賴,但是在 npm 3 中,不會再強制安裝 peerDependencies 中所指定的包,而是通過警告的方式來提示我們,此時就需要手動在 package.json 文件中手動添加依賴;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"bundledDependencies"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個依賴項也可以記爲 bundleDependencies,與其他幾種依賴項不同,他不是一個鍵值對的對象,而是一個數組,數組裏是包名的字符串,例如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"{ \n  \"name\": \"project\", \n  \"version\": \"1.0.0\", \n  \"bundleDependencies\": [ \n    \"axios\",  \n    \"lodash\" \n  ] \n} \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當使用 npm pack 的方式來打包時,上述的例子會生成一個 project-1.0.0.tgz 的文件,在使用了 bundledDependencies 後,打包時會把 Axios 和 Lodash 這兩個依賴一起放入包中,之後有人使用 "},{"type":"codeinline","content":[{"type":"text","text":"npm install project-1.0.0.tgz"}]},{"type":"text","text":" 下載包時,Axios 和 Lodash 這兩個依賴也會被安裝。需要注意的是安裝之後 Axios 和 Lodash 這兩個包的信息在 dependencies 中,並且不包括版本信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\"bundleDependencies\": [ \n    \"axios\", \n    \"lodash\" \n  ], \n  \"dependencies\": { \n    \"axios\": \"*\", \n    \"lodash\": \"*\" \n  }, \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們使用常規的 npm publish 來發布的話,這個屬性是不會生效的,所以日常情況中使用的較少。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文介紹的是 npm 2,npm 3,package-lock.json 以及幾種依賴的區別和使用場景,希望能夠讓大家對 npm 的瞭解更加多一點,有什麼不清楚的地方或者不足之處歡迎大家在評論區留言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:劉靜"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/JHDVh9wGj_YaTLtSz4AH7g"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:npm 依賴管理中被忽略的那些細節"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:政採雲前端團隊 - 微信公衆號 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章