本文首發於政採雲前端團隊博客: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 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 會帶來一個新的問題:因爲在執行 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.json
中dependencies
的依賴項相同 -
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.0
和 React-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/)
看完兩件事
招賢納士
ZooTeam@cai-inc.com
本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。