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

👆   這是第  66  篇 不摻水的原創 ,想要了解更多 ,請戳上方藍色字體: 政採雲前端團隊  關注咱們吧~

本文首發於政採雲前端團隊博客:npm 依賴管理中被忽略的那些細節html

https://www.zoo.team/article/npm-details


前言

提起 npm,你們第一個想到的應該就是 npm install 了,可是 npm install 以後生成的 node_modules 你們有觀察過嗎?package-lock.json 文件的做用你們知道嗎?除了 dependencies 和 devDependencies,其餘的依賴有什麼做用呢?接下來,本文將針對 npm 中的你可能忽略的細節和你們分享一些經驗。前端

npm 安裝機制

A 和 B 同時依賴 C,C 這個包會被安裝在哪裏呢?C 的版本相同和版本不一樣時安裝會有什麼差別呢?package.json 中包的先後順序對於安裝時有什麼影響嗎?這些問題平時你們可能沒有注意過,今天咱們就來一塊兒研究一下吧。node

A 和 B 同時依賴 C,這個包會被安裝在哪裏呢?

假若有 A 和 B 兩個包,兩個包都依賴 C 這個包,npm 2 會依次遞歸安裝 A 和 B 兩個包及其子依賴包到 node_modules 中。執行完畢後,咱們會看到 ./node_modules 這層目錄只含有這兩個子目錄:react

node_modules/
├─┬ A
│ ├── C
├─┬ B
│ └── C

若是使用 npm 3 來進行安裝的話,./node_modules 下的目錄將會包含三個子目錄:ios

node_modules/
├─┬ A
├─┬ B
├─┬ C

爲何會出現這樣的區別呢?這就要從 npm 的工做方式提及了:git

npm 2 和 npm 3 模塊安裝機制的差別

雖然目前最新的 npm 版本是 npm 6,但 npm 2 到 npm 3 的版本變動中實現了目錄打平,與其餘版本相比差異較大。所以,讓咱們具體看下這兩個版本的差別。github

npm 2 在安裝依賴包時,採用簡單的遞歸安裝方法。執行 npm install 後,npm 根據 dependencies 和 devDependencies 屬性中指定的包來肯定第一層依賴,npm 2 會根據第一層依賴的子依賴,遞歸安裝各個包到子依賴的 node_modules 中,直到子依賴再也不依賴其餘模塊。執行完畢後,咱們會看到 ./node_modules 這層目錄中包含有咱們 package.json 文件中全部的依賴包,而這些依賴包的子依賴包都安裝在了本身的 node_modules 中 ,造成相似於下面的依賴樹:

這樣的目錄有較爲明顯的好處:web

1)層級結構很是明顯,能夠清楚的在第一層的 node_modules 中看到咱們安裝的全部包的子目錄;npm

2)在已知本身所需包的名字以及版本號時,能夠複製粘貼相應的文件到 node_modules 中,而後手動更改 package.json 中的配置;json

3)若是想要刪除某個包,只須要簡單的刪除 package.json 文件中相應的某一行,而後刪除 node_modules 中該包的目錄;

可是這樣的層級結構也有較爲明顯的缺陷,當個人 A,B,C 三個包中有相同的依賴 D 時,執行 npm install 後,D 會被重複下載三次,而隨着咱們的項目愈來愈複雜,node_modules 中的依賴樹也會愈來愈複雜,像 D 這樣的包也會愈來愈多,形成了大量的冗餘;在 windows 系統中,甚至會由於目錄的層級太深致使文件的路徑過長,觸發文件路徑不能超過 280 個字符的錯誤;

爲了解決以上問題,npm 3 的 node_modules 目錄改爲了更爲扁平狀的層級結構,儘可能把依賴以及依賴的依賴平鋪在 node_modules 文件夾下共享使用。

npm  3 對於同一依賴的不一樣版本會怎麼處理呢?

npm 3 會遍歷全部的節點,逐個將模塊放在 node_modules 的第一層,當發現有重複模塊時,則丟棄, 若是遇到某些依賴版本不兼容的問題,則繼續採用 npm 2 的處理方式,前面的放在 node_modules 目錄中,後面的放在依賴樹中。舉個🌰:A,B,依賴 D(v 0.0.1),C 依賴 D(v 0.0.2):

可是 npm 3 會帶來一個新的問題:因爲在執行 npm install 的時候,按照 package.json 裏依賴的順序依次解析,上圖若是 C 的順序在 A,B 的前邊,node_modules 樹則會改變,會出現下邊的狀況:

因而可知,npm 3 並未徹底解決冗餘的問題,甚至還會帶來新的問題。

爲何會出現 package-lock.json 呢?

爲何會有 package-lock.json 文件呢?這個咱們就要先從 package.json 文件提及了。

package.json 的不足之處

npm install 執行後,會生成一個 node_modules 樹,在理想狀況下, 但願對於同一個 package.json 老是生成徹底相同 node_modules 樹。在某些狀況下,確實如此。但在多數狀況下,npm 沒法作到這一點。有如下兩個緣由:

1)某些依賴項自上次安裝以來,可能已發佈了新版本 。好比:A 包在團隊中第一我的安裝的時候是 1.0.5 版本,package.json 中的配置項爲 A: '^1.0.5';團隊中第二我的把代碼拉下來的時候,A 包的版本已經升級成了 1.0.8,根據 package.json 中的 semver-range version 規範,此時第二我的 npm install 後 A 的版本爲 1.0.8;可能會形成由於依賴版本不一樣而致使的 bug;

2)針對 1)中的問題,可能有的小夥伴會是把 A 的版本號固定爲 A: '1.0.5' 不就能夠了嗎?可是這樣的作法其實並無解決問題, 好比 A 的某個依賴在第一我的下載的時候是 2.1.3 版本,可是第二我的下載的時候已經升級到了 2.2.5 版本,此時生成的 node_modules 樹依舊不徹底相同 ,固定版本只是固定來自身的版本,依賴的版本沒法固定。

針對 package.json 不足的解決方法

爲了解決上述問題以及 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。

package-lock.json 文件的結構

package-lock.json 文件中的 name、version 與 package.json 中的 name、version 同樣,描述了當前包的名字和版本,dependencies 是一個對象,該對象和 node_modules 中的包結構一一對應,對象的 key 爲包的名稱,值爲包的一些描述信息, 根據 package-lock-json官方文檔 (https://docs.npmjs.com/configuring-npm/package-lock-json.html#requires),主要的結構以下:

  • version :包版本,即這個包當前安裝在 node_modules 中的版本
  • resolved :包具體的安裝來源
  • integrity :包 hash 值,驗證已安裝的軟件包是否被改動過、是否已失效
  • requires :對應子依賴的依賴,與子依賴的 package.jsondependencies 的依賴項相同
  • dependencies :結構和外層的 dependencies 結構相同,存儲安裝在子依賴 node_modules 中的依賴包

須要注意的是,並非全部的子依賴都有 dependencies 屬性,只有子依賴的依賴和當前已安裝在根目錄的 node_modules 中的依賴衝突以後,纔會有這個屬性。

package-lock.json 文件的做用

  • 在團隊開發中,確保每一個團隊成員安裝的依賴版本是一致的,肯定一棵惟一的 node_modules 樹;
  • node_modules 目錄自己是不會被提交到代碼庫的,可是 package-lock.json 能夠提交到代碼庫,若是開發人員想要回溯到某一天的目錄狀態,只須要把 package.json 和 package-lock.json 這兩個文件回退到那一天便可。
  • 因爲 package-lock.json 和 node_modules 中的依賴嵌套徹底一致,能夠更加清楚的瞭解樹的結構及其變化。
  • 在安裝時,npm 會比較 node_modules 已有的包,和 package-lock.json 進行比較,若是重複的話,就跳過安裝 ,從而優化了安裝的過程。

依賴的區別與使用場景

npm 目前支持如下幾類依賴包管理包括

  • dependencies
  • devDependencies
  • optionalDependencies 可選擇的依賴包
  • peerDependencies 同等依賴
  • bundledDependencies 捆綁依賴包

下面咱們來看一下這幾種依賴的區別以及各自的應用場景:

dependencies

dependencies 是不管在開發環境仍是在生產環境都必須使用的依賴,是咱們最經常使用的依賴包管理對象,例如 React,Loadsh,Axios 等,經過 npm install XXX 下載的包都會默認安裝在 dependencies 對象中,也可使用  npm install XXX --save 下載  dependencies 中的包;

devDependencies

devDependencies 是指能夠在開發環境使用的依賴,例如 eslint,debug 等,經過 npm install packageName --save-dev 下載的包都會在 devDependencies 對象中;

dependencies 和 devDependencies 最大的區別是在打包運行時,執行 npm install 時默認會把全部依賴所有安裝,可是若是使用 npm install --production 時就只會安裝 dependencies 中的依賴,若是是 node 服務項目,就能夠採用這樣的方式用於服務運行時安裝和打包,減小包大小。

optionalDependencies

optionalDependencies 指的是能夠選擇的依賴,當你但願某些依賴即便下載失敗或者沒有找到時,項目依然能夠正常運行或者 npm 繼續運行的時,就能夠把這些依賴放在 optionalDependencies 對象中,可是 optionalDependencies 會覆蓋 dependencies 中的同名依賴包,因此不要把一個包同時寫進兩個對象中。

optionalDependencies 就像是咱們的代碼的一種保護機制同樣,若是包存在的話就走存在的邏輯,不存在的就走不存在的邏輯。

try { 
  var axios = require('axios'
  var fooVersion = require('axios/package.json').version 
catch (er) { 
  foo = null 

// .. then later in your program .. 
if (foo) { 
  foo.doFooThings() 

peerDependencies

peerDependencies 用於指定你當前的插件兼容的宿主必需要安裝的包的版本,這個是什麼意思呢?舉個例子🌰:咱們經常使用的 react 組件庫 ant-design@3.x 的 package.json (https://github.com/ant-design/ant-design/blob/master/package.json#L37) 中的配置以下:

"peerDependencies": { 
  "react"">=16.9.0"
  "react-dom"">=16.9.0" 
 }, 

假設咱們建立了一個名爲 project 的項目,在此項目中咱們要使用 ant-design@3.x 這個插件,此時咱們的項目就必須先安裝 React >= 16.9.0React-dom >= 16.9.0 的版本。

在 npm 2 中,當咱們下載 ant-design@3.x 時,peerDependencies 中指定的依賴會隨着 ant-design@3.x 一塊兒被強制安裝,因此咱們不須要在宿主項目的 package.json 文件中指定 peerDependencies 中的依賴,可是在 npm 3 中,不會再強制安裝 peerDependencies 中所指定的包,而是經過警告的方式來提示咱們,此時就須要手動在 package.json 文件中手動添加依賴;

bundledDependencies

這個依賴項也能夠記爲 bundleDependencies,與其餘幾種依賴項不一樣,他不是一個鍵值對的對象,而是一個數組,數組裏是包名的字符串,例如:


  "name""project"
  "version""1.0.0"
  "bundleDependencies": [ 
    "axios",  
    "lodash" 
  ] 

當使用 npm pack 的方式來打包時,上述的例子會生成一個 project-1.0.0.tgz 的文件,在使用了 bundledDependencies 後,打包時會把 Axios 和 Lodash 這兩個依賴一塊兒放入包中,以後有人使用 npm install project-1.0.0.tgz 下載包時,Axios 和 Lodash 這兩個依賴也會被安裝。須要注意的是安裝以後 Axios 和 Lodash 這兩個包的信息在 dependencies 中,而且不包括版本信息。

"bundleDependencies": [ 
    "axios"
    "lodash" 
  ], 
  "dependencies": { 
    "axios""*"
    "lodash""*" 
  }, 

若是咱們使用常規的 npm publish 來發布的話,這個屬性是不會生效的,因此平常狀況中使用的較少。

總結

本文介紹的是 npm 2,npm 3,package-lock.json 以及幾種依賴的區別和使用場景,但願可以讓你們對 npm 的瞭解更加多一點,有什麼不清楚的地方或者不足之處歡迎你們在評論區留言。

參考文獻

package.json官方文檔 (https://docs.npmjs.com/files/package.json#peerdependencies)

package-lock-json官方文檔 (https://docs.npmjs.com/configuring-npm/package-lock-json.html#requires)

npm文檔總結 (https://juejin.im/post/6844903582337237006#heading-0)

npm-pack (https://www.npmjs.cn/cli/pack/)

看完兩件事

若是你以爲這篇內容對你挺有啓發,我想邀請你幫我兩件小事
1.點個「 在看」,讓更多人也能看到這篇內容(點了 在看 」,bug -1 😊
2.關注公衆號「 政採雲前端團隊」,持續爲你推送精選好文

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索