引言
現在的前端開發幾乎都離不開nodejs的包管理器npm
,比如前端在搭建本地開發服務以及打包編譯前端代碼等都會用到。在前端開發過程中,經常用到npm install
來安裝所需的依賴,至於其中的技術細節未做過多的理解,下面就來說說node包管理器npm。
依賴安裝npm install
使用npm來管理nodejs的包依賴,需要在項目根目錄下提供一個package.json
文件,其中與包依賴相關的字段有:
- dependencies: 指定項目運行時所依賴的模塊
- devDependencies: 指定項目開發時所需要的模塊
- peerDependencies:指定當前模塊所在的宿主環境所需要的模塊及其版本
其中,大家應該都知道:通過命令npm install --save $package
來安裝運行時依賴的模塊,npm install --save-dev $package
來安裝本地開發時所依賴的模塊。
需要指出的是,通過npm install
來安裝包依賴時,dependencies
和devDependencies
是有區別的,具體如下:
-
dependencies指定的包只在以下兩種情況下被安裝:
- 在一個含有
package.json
的目錄下執行npm install - 在任意一個目錄中執行
npm install $package
- 在一個含有
-
devDependencies指定的包被安裝的情況如下:
- 在一個含有
package.json
的目錄下執行npm install;但是,執行npm install --production
該命令是不會安裝devDependencies指定的包的 - 執行
npm install $package --dev
時會安裝對應的依賴包; 但是, 執行npm install $package
時是不會安裝devDependencies指定的包的
- 在一個含有
所以:
我們經常在通過
npm install $package
來安裝一個依賴包時,npm只會安裝該依賴包的package.json文件中的dependencies
所指定的依賴包,devDependencies
是不會被安裝的。
另外,通過npm install $package
在安裝指定依賴包時,該包package.json中的peerDependencies
所指定的依賴包及其版本,則是對依賴該$package包的宿主環境的要求。
若宿主環境沒有安裝對應的包或者安裝的版本不滿足要求,npm在安裝過程中給出錯誤警告。
npm2與npm3+ 安裝依賴的區別
npm在安裝依賴包時,會將依賴包下載到當前的node_modules目錄中。每個包安裝過后都會有自己的node_modules嗎?這又涉及到不同版本的npm其對包依賴的目錄組織結構有所不同。
npm2 依賴安裝
npm2依賴安裝的時候比較簡單直接,直接按照包依賴的樹形結構下載填充本地目錄結構,也就是說每個包都會將該包的依賴組織到當前包所在的node_modules目錄中。
npm2這樣設計的原因可能是引用文章[2]的一句話:
因為 npm 設計的初衷就是考慮到了包依賴的版本錯綜復雜的關系,同一個包因為被依賴的關系原因會出現多個版本,簡單地填充結構保證了無論是安裝還是刪除都會有統一的行為和結構。
這樣,一個項目App 里模塊 A 和 C 都依賴 B,無論被依賴的 B 是否是同一個版本,都會生成下面的目錄結構:
很明顯:
這種依賴的組織結構,雖然簡單的實現多版本兼容,但是可能造成目錄結構嵌套比較深,並且很可能造成相同模塊的大量冗余問題。
npm3+依賴安裝
npm3則對依賴安裝進行了改造,采用”扁平結構“的思路來組織依賴包的目錄結構。具體的就是npm install
時:
按照 package.json 里依賴的順序依次解析,遇到新的包就把它放在第一級目錄,后面如果遇到一級目錄已經存在的包,會先判斷版本,如果版本一樣則忽略,否則會按照 npm2 的方式依次掛在依賴包目錄下。
以上面的例子看一下對比結果:
這樣,npm3+采用這種扁平結構部分的解決了npm2的痛點。
為什么說是部分解決呢,npm3+並沒有完美解決npm2中的問題,在某些情況下甚至會退化到npm2的行為。
例如項目App里依賴模塊A、C、D、E, 其中A、C、D依賴模塊B v2.0, E依賴模塊B v1.0,生成的npm3結構如下
可以看到B、C、D模塊下包含了各自依賴的B v2.0,存在代碼冗余的情況。
那么是否可以解決這代碼冗余問題呢,在E模塊依賴的模塊B升級到V2.0前提下,我們可以通過npm dedupe
把所有二級的依賴模塊B v2.0重定向到一級模塊B下,如下圖所示:
node_modules路徑查找
上面說到了npm2與npm3依賴包組織結構的不同;那么如何找到對應的依賴包呢,例如項目訪問webapck依賴包:
const webapck = require('webpack')
那么nodejs是怎么找到webpack模塊的呢,這就是涉及到依賴包的路徑查找問題。具體如下:
如果傳遞給
require()
的參數不是nodejs的核心模塊,也不是以/
、../
或./
開頭,
那么nodejs會嘗試從當前模塊所在目錄開始,嘗試在它的 node_modules 文件夾里加載相應模塊,根據模塊的package.json來加載對應的js文件;如果沒有找到,那么就再向上一級目錄移動,直到文件系統的根目錄為止。
例如,假設在/home/wonyun/projects/foo.js
文件里調用了 require('bar.js')
,那么 nodejs 查找其位置的順序依次為:
/home/wonyun/projects/node_modules/bar.js
/home/wonyun/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
若果追蹤到文件系統的根目錄也沒有找到對應的依賴,那么nodejs就會找不到對應模塊的報錯。
聲明一下:以上圖片使用文獻[2]的圖片加以說明。
參考文獻
1、【npm】詳解npm的模塊安裝機制
2、npm2 npm3 yarn 的故事
3、npm&&npmscript&&gulp&&webpack
4、What's the difference between dependencies, devDependencies and peerDependencies in npm package.json file?