可惡的 node_modules

作為前端工程師應(yīng)該都知道這個東東际长,在我的電腦里存有幾年積攢下來的各種工程約一百G,現(xiàn)在換了新電腦兴泥,導(dǎo)資料真麻煩工育,因為這個東東太大且文件太多,復(fù)制很慢搓彻。

node_modules 目錄結(jié)構(gòu)

npm install 執(zhí)行完畢后如绸,我們可以在 nodemodules 中看到所有依賴的包。雖然使用者無需關(guān)注這個目錄里的文件夾結(jié)構(gòu)細節(jié)旭贬,只管在業(yè)務(wù)代碼中引用依賴包即可怔接,但了解 nodemodules 的內(nèi)容可以幫我們更好理解 npm 如何工作,了解從 npm 2 到 npm 5 有哪些變化和改進稀轨。

為簡單起見扼脐,我們假設(shè)應(yīng)用目錄為 app, 用兩個流行的包 webpack, nconf 作為依賴包做示例說明。并且為了正常安裝靶端,使用了“上古” npm 2 時期的版本 webpack@1.15.0, nconf@0.8.5.

npm 2 - 樹狀結(jié)構(gòu)

npm 2 在安裝依賴包時谎势,采用簡單的遞歸安裝方法。執(zhí)行 npm install 后杨名,npm 2 依次遞歸安裝 webpack和 nconf 兩個包到 nodemodules 中。執(zhí)行完畢后猖毫,我們會看到 ./nodemodules 這層目錄只含有這兩個子目錄台谍。

node_modules/
├── nconf/
└── webpack/

進入更深一層 nconf 或 webpack 目錄,將看到這兩個包各自的 nodemodules 中吁断,已經(jīng)由 npm 遞歸地安裝好自身的依賴包趁蕊。包括 ./node_modules/webpack/node_modules/webpack-core , ./node_modules/conf/node_modules/async 等等。而每一個包都有自己的依賴包仔役,每個包自己的依賴都安裝在了自己的 nodemodules 中掷伙。依賴關(guān)系層層遞進,構(gòu)成了一整個依賴樹又兵,這個依賴樹與文件系統(tǒng)中的文件結(jié)構(gòu)樹剛好層層對應(yīng)任柜。

最方便的查看依賴樹的方式是直接在 app 目錄下執(zhí)行 npm ls 命令。

app@0.1.0
├─┬ nconf@0.8.5
│ ├── async@1.5.2
│ ├── ini@1.3.5
│ ├── secure-keys@1.0.0
│ └── yargs@3.32.0
└─┬ webpack@1.15.0
  ├── acorn@3.3.0
  ├── async@1.5.2
  ├── clone@1.0.3
  ├── ...
  ├── optimist@0.6.1
  ├── supports-color@3.2.3
  ├── tapable@0.1.10
  ├── uglify-js@2.7.5
  ├── watchpack@0.2.9
  └─┬ webpack-core@0.6.9
    ├── source-list-map@0.1.8
    └── source-map@0.4.4

這樣的目錄結(jié)構(gòu)優(yōu)點在于層級結(jié)構(gòu)明顯沛厨,便于進行傻瓜式的管理:

  1. 例如新裝一個依賴包宙地,可以立即在第一層 node_modules 中看到子目錄
  2. 在已知所需包名和版本號時,甚至可以從別的文件夾手動拷貝需要的包到 node_modules 文件夾中逆皮,再手動修改 package.json 中的依賴配置
  3. 要刪除這個包宅粥,也可以簡單地手動刪除這個包的子目錄,并刪除 package.json 文件中相應(yīng)的一行即可

實際上电谣,很多人在 npm 2 時代也的確都這么實踐過秽梅,的確也都可以安裝和刪除成功抹蚀,并不會導(dǎo)致什么差錯。

但這樣的文件結(jié)構(gòu)也有很明顯的問題:

  1. 對復(fù)雜的工程, node_modules 內(nèi)目錄結(jié)構(gòu)可能會太深企垦,導(dǎo)致深層的文件路徑過長而觸發(fā) windows 文件系統(tǒng)中环壤,文件路徑不能超過 260 個字符長的錯誤
  2. 部分被多個包所依賴的包,很可能在應(yīng)用 node_modules 目錄中的很多地方被重復(fù)安裝竹观。隨著工程規(guī)模越來越大镐捧,依賴樹越來越復(fù)雜,這樣的包情況會越來越多臭增,造成大量的冗余懂酱。

——在我們的示例中就有這個問題, webpack 和 nconf 都依賴 async 這個包誊抛,所以在文件系統(tǒng)中列牺,webpack 和 nconf 的 node_modules 子目錄中都安裝了相同的 async 包,并且是相同的版本拗窃。

npm 3 - 扁平結(jié)構(gòu)

主要為了解決以上問題瞎领,npm 3 的 node_modules 目錄改成了更加扁平狀的層級結(jié)構(gòu)。文件系統(tǒng)中 webpack, nconf, async 的層級關(guān)系變成了平級關(guān)系随夸,處于同一級目錄中九默。

雖然這樣一來 webpack/nodemodules 和 nconf/nodemodules 中都不再有 async 文件夾,但得益于 node 的模塊加載機制宾毒,他們都可以在上一級 node_modules 目錄中找到 async 庫驼修。所以 webpack 和 nconf 的庫代碼中 require('async') 語句的執(zhí)行都不會有任何問題。

這只是最簡單的例子诈铛,實際的工程項目中乙各,依賴樹不可避免地會有很多層級,很多依賴包幢竹,其中會有很多同名但版本不同的包存在于不同的依賴層級耳峦,對這些復(fù)雜的情況, npm 3 都會在安裝時遍歷整個依賴樹,計算出最合理的文件夾安裝方式焕毫,使得所有被重復(fù)依賴的包都可以去重安裝蹲坷。

npm 文檔提供了更直觀的例子解釋這種情況:

假如 package{dep} 寫法代表包和包的依賴,那么 A{B,C}, B{C}, C{D} 的依賴結(jié)構(gòu)在安裝之后的 node_modules 是這樣的結(jié)構(gòu):

A
+-- B
+-- C
+-- D

這里之所以 D 也安裝到了與 B C 同一級目錄咬荷,是因為 npm 會默認會在無沖突的前提下冠句,盡可能將包安裝到較高的層級。

如果是 A{B,C}, B{C,D@1}, C{D@2} 的依賴關(guān)系幸乒,得到的安裝后結(jié)構(gòu)是:

A
+-- B
+-- C
   `-- D@2
+-- D@1

這里是因為懦底,對于 npm 來說同名但不同版本的包是兩個獨立的包笙隙,而同層不能有兩個同名子目錄挥唠,所以其中的 D@2 放到了 C 的子目錄而另一個 D@1 被放到了再上一層目錄。

很明顯在 npm 3 之后 npm 的依賴樹結(jié)構(gòu)不再與文件夾層級一一對應(yīng)了。想要查看 app 的直接依賴項糕韧,要通過 npm ls 命令指定 --depth 參數(shù)來查看:

npm ls --depth 1

PS: 與本地依賴包不同酱吝,如果我們通過 npm install--global 全局安裝包到全局目錄時禽车,得到的目錄依然是“傳統(tǒng)的”目錄結(jié)構(gòu)艇搀。而如果使用 npm 3 想要得到“傳統(tǒng)”形式的本地 node_modules 目錄,使用 npm install--global-style 命令即可亲桦。

npm 5 - package-lock 文件

npm 5 發(fā)布于 2017 年也是目前最新的 npm 版本崖蜜,這一版本依然沿用 npm 3 之后扁平化的依賴包安裝方式,此外最大的變化是增加了 package-lock.json 文件客峭。

package-lock.json 的作用是鎖定依賴安裝結(jié)構(gòu)豫领,如果查看這個 json 的結(jié)構(gòu),會發(fā)現(xiàn)與 node_modules 目錄的文件層級結(jié)構(gòu)是一一對應(yīng)的舔琅。

以依賴關(guān)系為: app{webpack} 的 'app' 項目為例, 其 package-lock 文件包含了這樣的片段等恐。

"express": {
  "version": "4.15.4",
  "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz",
  "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=",
  "requires": {
    "accepts": "1.3.3",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.2",
    "content-type": "1.0.2",
    "cookie": "0.3.1",
    "cookie-signature": "1.0.6",
    "debug": "2.6.8",
    "depd": "1.1.1",
    "encodeurl": "1.0.1",
    "escape-html": "1.0.3",
    "etag": "1.8.0",
    "finalhandler": "1.0.4",
    "fresh": "0.5.0",
    "merge-descriptors": "1.0.1",
    "methods": "1.1.2",
    "on-finished": "2.3.0",
    "parseurl": "1.3.1",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "1.1.5",
    "qs": "6.5.0",
    "range-parser": "1.2.0",
    "send": "0.15.4",
    "serve-static": "1.12.4",
    "setprototypeof": "1.0.3",
    "statuses": "1.3.1",
    "type-is": "1.6.15",
    "utils-merge": "1.0.0",
    "vary": "1.1.1"
  }
},

看懂 package-lock 文件并不難,其結(jié)構(gòu)是同樣類型的幾個字段嵌套起來的备蚓,主要是 version, resolved, integrity, requires, dependencies 這幾個字段而已课蔬。

  • version, resolved, integrity 用來記錄包的準確版本號、內(nèi)容hash郊尝、安裝源的二跋,決定了要安裝的包的準確“身份”信息
  • 假設(shè)蓋住其他字段,只關(guān)注文件中的 dependencies:{} 我們會發(fā)現(xiàn)流昏,整個文件的 JSON 配置里的 dependencies 層次結(jié)構(gòu)與文件系統(tǒng)中 node_modules 的文件夾層次結(jié)構(gòu)是完全對照的
  • 只關(guān)注 requires:{} 字段又會發(fā)現(xiàn)同欠,除最外層的 requires 屬性為 true 以外, 其他層的 requires 屬性都對應(yīng)著這個包的 package.json 里記錄的自己的依賴項

因為這個文件記錄了 nodemodules 里所有包的結(jié)構(gòu)、層級和版本號甚至安裝源横缔,它也就事實上提供了 “保存” nodemodules 狀態(tài)的能力。只要有這樣一個 lock 文件衫哥,不管在那一臺機器上執(zhí)行 npm install 都會得到完全相同的 node_modules 結(jié)果茎刚。

這就是 package-lock 文件致力于優(yōu)化的場景:在從前僅僅用 package.json 記錄依賴,由于 semver range 的機制撤逢;一個月前由 A 生成的 package.json 文件膛锭,B 在一個月后根據(jù)它執(zhí)行 npm install 所得到的 node_modules 結(jié)果很可能許多包都存在不同的差異,雖然 semver 機制的限制使得同一份 package.json 不會得到大版本不同的依賴包蚊荣,但同一份代碼在不同環(huán)境安裝出不同的依賴包初狰,依然是可能導(dǎo)致意外的潛在因素。

相同作用的文件在 npm 5 之前就有互例,稱為 npm shrinkwrap 文件奢入,二者作用完全相同,不同的是后者需要手動生成媳叨,而 npm 5 默認會在執(zhí)行 npm install 后就生成 package-lock 文件腥光,并且建議你提交到 git/svn 代碼庫中关顷。

package-lock.json 文件在最初 npm 5.0 默認引入時也引起了相當(dāng)大的爭議。在 npm 5.0 中武福,如果已有 package-lock 文件存在议双,若手動在 package.json 文件新增一條依賴,再執(zhí)行 npm install, 新增的依賴并不會被安裝到 node_modules 中, package-lock.json 也不會做相應(yīng)的更新捉片。這樣的表現(xiàn)與使用者的自然期望表現(xiàn)不符平痰。在 npm 5.1 的首個 Release 版本中這個問題得以修復(fù)。這個事情告訴我們伍纫,要升級宗雇,不要使用 5.0。

——但依然有反對的聲音認為 package-lock 太復(fù)雜翻斟,對此 npm 也提供了禁用配置:

npm config set package-lock false

清除所有

了解了node_modules目錄結(jié)構(gòu)后逾礁,知道這東東都是一些重復(fù)的依賴包,干脆全部刪除便于復(fù)制工程項目访惜,手動刪除好像不太現(xiàn)實嘹履,太多了,還要一個個找债热,通過命令可以一鍵完成砾嫉。

find . -name "node_modules" -print | xargs rm -rf

命令前部分是通過find遞歸找到當(dāng)前目錄下所有的node_modules目錄并打印出來,后部分是通過管道將前部分查找的結(jié)果當(dāng)作參數(shù)并強制刪除窒篱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焕刮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子墙杯,更是在濱河造成了極大的恐慌配并,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件高镐,死亡現(xiàn)場離奇詭異溉旋,居然都是意外死亡,警方通過查閱死者的電腦和手機嫉髓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門观腊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人算行,你說我怎么就攤上這事梧油。” “怎么了州邢?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵儡陨,是天一觀的道長。 經(jīng)常有香客問我,道長迄委,這世上最難降的妖魔是什么褐筛? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮叙身,結(jié)果婚禮上渔扎,老公的妹妹穿的比我還像新娘。我一直安慰自己信轿,他們只是感情好晃痴,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著财忽,像睡著了一般倘核。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上即彪,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天紧唱,我揣著相機與錄音,去河邊找鬼隶校。 笑死漏益,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的深胳。 我是一名探鬼主播绰疤,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舞终!你這毒婦竟也來了轻庆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤敛劝,失蹤者是張志新(化名)和其女友劉穎余爆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夸盟,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡龙屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了满俗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡作岖,死狀恐怖唆垃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痘儡,我是刑警寧澤辕万,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響渐尿,放射性物質(zhì)發(fā)生泄漏醉途。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一砖茸、第九天 我趴在偏房一處隱蔽的房頂上張望隘擎。 院中可真熱鬧,春花似錦凉夯、人聲如沸货葬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽震桶。三九已至,卻和暖如春征绎,著一層夾襖步出監(jiān)牢的瞬間蹲姐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工人柿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柴墩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓顷扩,卻偏偏與公主長得像拐邪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子隘截,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容