成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊一聊 NPM 依賴(lài)管理的復(fù)雜性

開(kāi)發(fā) 前端
本文希望更聚焦討論 Node 場(chǎng)景下的依賴(lài) —— 或者更直觀的說(shuō)是 NPM Package 結(jié)構(gòu)的不穩(wěn)定性所帶來(lái)的被嚴(yán)重低估的質(zhì)量風(fēng)險(xiǎn),以及相應(yīng)的應(yīng)對(duì)策略。

這是一個(gè)很少被提及的話(huà)題 —— 「依賴(lài)管理」(Dependencies Management) 。

在開(kāi)源文化盛行的現(xiàn)代,多數(shù)時(shí)候我們都不必從零開(kāi)始搭建一套軟件系統(tǒng),轉(zhuǎn)而可以借助諸多開(kāi)放的代碼片段及其他資源更快速高效開(kāi)發(fā)軟件應(yīng)用,這算的上軟件工程發(fā)展史上一次巨大革命,因?yàn)樗艽蠓嵘浖I(yè)的生產(chǎn)效率,我們不必再?gòu)牡讓娱_(kāi)始編寫(xiě)所有代碼,大部分問(wèn)題與常見(jiàn)的編程模式都能在社區(qū)找到相應(yīng)的解決方案,且這些被反復(fù)消費(fèi)錘煉的軟件包通常有更高的穩(wěn)定性與性能,你需要做的只是花一些時(shí)間了解學(xué)習(xí)這些開(kāi)源資源,并在項(xiàng)目使用它們,“ 「依賴(lài)」 ”它們即可,這已經(jīng)是一種被不斷實(shí)踐,不斷被驗(yàn)證為行之有效的開(kāi)發(fā)模式。

但現(xiàn)實(shí)比預(yù)想的復(fù)雜許多,如果你開(kāi)發(fā)的只是規(guī)模較小,或生命周期非常短的項(xiàng)目時(shí),依賴(lài)的狀態(tài)并不會(huì)造成多大問(wèn)題,你只要確保當(dāng)下執(zhí)行 ok,功能符合預(yù)期即可。但,任何軟件項(xiàng)目一旦疊加“「規(guī)模」”與“「時(shí)間」”兩個(gè)變量之后,依賴(lài)網(wǎng)絡(luò)就很容易變得復(fù)雜混亂,如果不及時(shí)施加恰當(dāng)?shù)墓芾硎侄蝿t遲早會(huì)引發(fā)諸多晦澀難懂的穩(wěn)定性、性能、安全等諸多方面的問(wèn)題。因此,我們需要學(xué)習(xí)了解“依賴(lài)管理”的基本含義與潛在風(fēng)險(xiǎn),需要掌握一些軟件工程管理方面的方式方法,確保依賴(lài)網(wǎng)絡(luò)及其隨時(shí)間發(fā)生的變化都在可控范圍內(nèi),讓依賴(lài)網(wǎng)絡(luò)盡可能保持一個(gè)清晰有條理,且具備一定程度的健壯性。

在展開(kāi)具體內(nèi)容之前,我們先明確一下“依賴(lài)”這個(gè)概念,嚴(yán)格來(lái)說(shuō),你的代碼所需要消費(fèi)的任何直接與間接資源都?xì)w屬于“依賴(lài)”范疇,往小了說(shuō),包括系統(tǒng)時(shí)間、語(yǔ)言特性(如事件循環(huán)、閉包)、執(zhí)行環(huán)境(如瀏覽器接口、node 接口)等;往大了說(shuō)還包括:操作系統(tǒng)、網(wǎng)絡(luò),甚至硬件設(shè)備(如 GPU),但這些并不在本文討論范圍內(nèi),誠(chéng)然這些要素也可能帶來(lái)性能、安全性各方面的影響,但多數(shù)屬于基礎(chǔ)設(shè)施,穩(wěn)定性還是比較有保障的。

本文希望更聚焦討論 Node 場(chǎng)景下的依賴(lài) —— 或者更直觀的說(shuō)是 NPM Package 結(jié)構(gòu)的不穩(wěn)定性所帶來(lái)的被嚴(yán)重低估的質(zhì)量風(fēng)險(xiǎn),以及相應(yīng)的應(yīng)對(duì)策略。

第三方依賴(lài)帶來(lái)的問(wèn)題

09 年 NodeJS + NPM 的出現(xiàn),不僅讓 JavaScript 擁有了脫離瀏覽器環(huán)境執(zhí)行的能力,也帶來(lái)一套相對(duì)體系化的依賴(lài)管理方案,在此之前的依賴(lài)管理多數(shù)由“人”手工完成,需要用到什么就手動(dòng) copy 代碼進(jìn)倉(cāng)庫(kù),或者 copy cdn 鏈接到 HTML 頁(yè)面。

而 NPM(Node Package Manager) 讓這件事情 「盡可能」 做到了自動(dòng)化,我們只需要執(zhí)行 npm install 命令即可自動(dòng)完成下述工作:

  • 解析依賴(lài)樹(shù):根據(jù)項(xiàng)目 package.json 文件中的依賴(lài)項(xiàng)列表,遞歸檢查每個(gè)依賴(lài)項(xiàng)及子依賴(lài)項(xiàng)的名稱(chēng)和版本要求,構(gòu)建出依賴(lài)樹(shù)并計(jì)算每一個(gè)依賴(lài)需要安裝的確切版本(這個(gè)并不容易做到,參考:Version SAT);
  • 參考:https://research.swtch.com/version-sat
  • 下載依賴(lài)項(xiàng):構(gòu)建出完整的依賴(lài)樹(shù)后,npm 會(huì)根據(jù)依賴(lài)項(xiàng)的名稱(chēng)和版本,下載相應(yīng)的依賴(lài)包,下載過(guò)程還會(huì)對(duì)依賴(lài)包做一系列安全檢查,防止被篡改;
  • 安裝依賴(lài)項(xiàng):當(dāng)依賴(lài)項(xiàng)下載完成后,npm 將它們安裝到項(xiàng)目的 node_modules 目錄中。它會(huì)在該目錄下創(chuàng)建一個(gè)與依賴(lài)項(xiàng)名稱(chēng)相對(duì)應(yīng)的文件夾,并將軟件包的文件和目錄解壓復(fù)制到相應(yīng)的位置(不同包管理器最終產(chǎn)出的包結(jié)構(gòu)不同);
  • 解決依賴(lài)沖突:在安裝依賴(lài)項(xiàng)的過(guò)程中,可能會(huì)出現(xiàn)依賴(lài)沖突,即不同依賴(lài)項(xiàng)對(duì)同一軟件包的版本有不同的要求。npm 會(huì)嘗試解決這些沖突,通常采用版本回退或更新來(lái)滿(mǎn)足所有依賴(lài)項(xiàng)的要求;
  • 更新 package-lock.json:在安裝完成后,npm 會(huì)更新項(xiàng)目目錄下的 package-lock.json 文件。該文件記錄了實(shí)際安裝的軟件包和版本信息,以及確切的依賴(lài)關(guān)系樹(shù),可用于確保在后續(xù)安裝過(guò)程中保持一致的依賴(lài)項(xiàng)狀態(tài)(npm ci);

PS: 本文僅以 NPM 舉例,yarn、pnpm 的執(zhí)行算法雖差異較大,但整體遵循上述過(guò)程,因此不再贅述。

相比于過(guò)往人工管理的各種低效且容易出錯(cuò)的騷操作,NPM 這類(lèi)包管理器能以極低的成本,更規(guī)范化、自動(dòng)化完成依賴(lài)包的檢索、安裝、更新等管理動(dòng)作,更容易搭建出一個(gè)相對(duì)穩(wěn)定且安全可靠的工程環(huán)境,也更容易復(fù)用外部那些經(jīng)過(guò)良好封裝、充分測(cè)試的代碼片段。

But,伴隨著工程能力的提升,依賴(lài)之間的復(fù)雜度也在急劇增長(zhǎng),當(dāng)前我們正面臨著更多依賴(lài)管理相關(guān)的工程問(wèn)題,例如:幽靈依賴(lài)、版本沖突、依賴(lài)地獄等等,這些問(wèn)題很少被討論卻時(shí)時(shí)刻刻影響著工程項(xiàng)目的穩(wěn)定性、開(kāi)發(fā)效率、性能等要素,接下來(lái)我會(huì)盡可能完整討論依賴(lài)管理的方方面面,幫助大家更深入了解這些潛藏在日常開(kāi)發(fā)之下很少被察覺(jué)的各類(lèi)問(wèn)題,并討論相關(guān)的應(yīng)對(duì)方案。

依賴(lài)管理潛在的問(wèn)題

1. semver 并不穩(wěn)定

先從依賴(lài)管理中最淺顯直觀的視角講起,當(dāng)我們決定使用某一個(gè) NPM 包時(shí),需要做的第一件事就是在項(xiàng)目 package.json 文件中定義 dependencies ,類(lèi)似于:

{
  "name": "foo",
  "dependencies": {
    "lodash": "^1.0.0"
  }
}

這似乎已經(jīng)是一種簡(jiǎn)單而自然,不需要過(guò)多討論的常識(shí),but,我們應(yīng)該依賴(lài)于 Package 的那些版本呢?

答案取決于具體的功能需求、穩(wěn)定性、性能等諸多因素,但一個(gè)大致通用的實(shí)踐是:「盡可能使用最新版本的范圍版本」,例如假定 React 最新版本為 18.2.0,在項(xiàng)目中可以聲明依賴(lài)為 "react": "^18.2.0",這種方法一方面能夠應(yīng)用最新版本 —— 這可能意味著更多的功能,以及更好的性能等;另一方面,借助 ^ 聲明該依賴(lài)接受 >= 18.2.0 < 19 的版本范圍,在 React 下次發(fā)布 18.2.1 或更大版本時(shí)都能自動(dòng)匹配應(yīng)用,以此獲得一定范圍內(nèi)動(dòng)態(tài)更新依賴(lài)的能力。

PS:補(bǔ)充一個(gè)知識(shí)點(diǎn),當(dāng)前多數(shù)框架都遵循 semver 版本號(hào)(https://semver.org/)規(guī)則,即包含 Major.Minor.Patch 三段版本號(hào),Major 代表較大范圍的功能迭代,通常意味著破壞性更新;Minor 代表小版本迭代,可能帶來(lái)若干新接口但“承諾”向后兼容;Patch 代表補(bǔ)丁版本,通常意味著沒(méi)有明顯的接口變化。

這看似很完美,但實(shí)踐卻漏洞百出。首先,部分 NPM 包作者并沒(méi)有嚴(yán)格遵守 semver 定義的規(guī)則迭代版本號(hào),特別是許多公司內(nèi)部依賴(lài)的版本管理更是混亂不堪,Patch 可能破壞原本的接口定義(多一個(gè)參數(shù)少一個(gè)參數(shù)),Minor 可能導(dǎo)致向后不兼容,等等,致使舊代碼無(wú)法正常執(zhí)行。

其次,即使完全按照 semver 語(yǔ)義嚴(yán)格管理版本號(hào),誰(shuí)又能保證每次版本迭代都能完美符合用戶(hù)預(yù)期呢?例如按照 semver 語(yǔ)義,Patch 只用作 bug 修復(fù),但難保會(huì)在一些邊界情況發(fā)生變化,比如:「日志」,講道理用戶(hù)不應(yīng)該直接依賴(lài)代碼包的日志輸出,但可能有一些輸入輸出沒(méi)有覆蓋用戶(hù)需求,或者用戶(hù)沒(méi)有了解到正確的使用方式,致使消費(fèi)者傾向于直接從運(yùn)行日志解讀信息(只要用戶(hù)體量足夠大,總會(huì)出現(xiàn)一些意料之外的使用方法),若此時(shí) Patch 版本更改了日志內(nèi)容 —— 這看似很合理,卻可能導(dǎo)致日志解析失敗。從這個(gè)示例來(lái)說(shuō),日志算不算補(bǔ)丁更新呢?這種情況下,Patch 還是安全的嗎?

那么,能不能放棄范圍版本,寫(xiě)死版本號(hào)呢?例如上例中只要把依賴(lài)關(guān)系寫(xiě)死成 "react": "18.2.0" 似乎就能規(guī)避版本變化帶來(lái)的不確定性?某種程度上確實(shí)如此,但這又會(huì)帶來(lái)新的風(fēng)險(xiǎn):版本累積可能帶來(lái)更大的破壞性更新!我們必須承認(rèn)一個(gè)事實(shí):無(wú)論你有多強(qiáng)的惰性,「軟件項(xiàng)目只要存活的時(shí)間足夠長(zhǎng),就總會(huì)有一天需要升級(jí)依賴(lài)」,升級(jí)的主因可能是:安全、合規(guī)、性能、架構(gòu)調(diào)整等等,如果你從一開(kāi)始就在使用某個(gè)固定版本,直到不得不更新的時(shí)刻到來(lái)時(shí),新版本的使用方案、功能表現(xiàn)等可能都已經(jīng)發(fā)生了劇變(例如,從 React 17 => 18),很可能會(huì)導(dǎo)致你原本運(yùn)行良好的程序漏洞百出,質(zhì)量風(fēng)險(xiǎn)、回歸成本都很高。

因此,「良好的依賴(lài)管理策略應(yīng)該在保證穩(wěn)定的前提下,定期跟進(jìn)依賴(lài)包的更新」,小步快進(jìn)將升級(jí)風(fēng)險(xiǎn)分?jǐn)偟矫恳淮涡“姹镜校瑸檫_(dá)成這一效果,一個(gè)比較 「常見(jiàn)」 的實(shí)踐是在開(kāi)發(fā)環(huán)境中使用適當(dāng)?shù)姆秶姹荆跍y(cè)試 & 生產(chǎn)環(huán)境使用固定版本,以 NPM 為例,可以繼續(xù)沿用 "react": "^18.2.0",在開(kāi)發(fā)態(tài)中使用 npm install 安裝依賴(lài),在測(cè)試 & 生產(chǎn)環(huán)境則使用 npm ci 命令,兩者區(qū)別在于 npm install 會(huì)嘗試更新依賴(lài),觸發(fā)依賴(lài)結(jié)構(gòu)樹(shù)變化并記錄到 package-lock.json 文件;而 npm ci 則嚴(yán)格按照 package-lock.json 內(nèi)容準(zhǔn)確安裝各個(gè)依賴(lài)版本,在 CI/CD 環(huán)境中能獲得更強(qiáng)的穩(wěn)定性,確保代碼行為與開(kāi)發(fā)環(huán)境盡可能一致。

2. 依賴(lài)類(lèi)型

在確定依賴(lài)版本之后,接下來(lái)需要決定將依賴(lài)注冊(cè)到那個(gè) dependencies 節(jié)點(diǎn),按 package.json 規(guī)則,可選類(lèi)型有:

  • dependencies:生產(chǎn)依賴(lài),指在軟件包執(zhí)行時(shí)必需的依賴(lài)項(xiàng)。這些依賴(lài)項(xiàng)是你的應(yīng)用程序或模塊的核心組成部分,當(dāng)你部署到生產(chǎn)或測(cè)試環(huán)境時(shí),這些依賴(lài)項(xiàng)都需要被安裝消費(fèi);
  • devDependencies:開(kāi)發(fā)依賴(lài),僅在開(kāi)發(fā)過(guò)程中需要使用的依賴(lài)項(xiàng),通常包括測(cè)試框架、構(gòu)建工具、代碼檢查器、TS 類(lèi)型庫(kù)等。開(kāi)發(fā)依賴(lài)項(xiàng)不需要在生產(chǎn)環(huán)境安裝;
  • peerDependencies:對(duì)等依賴(lài),用于指定當(dāng)前 package 希望宿主環(huán)境提供的依賴(lài),這解釋有點(diǎn)繞,下面我們會(huì)展開(kāi)解釋?zhuān)?/li>
  • optionalDependencies:可選依賴(lài),當(dāng)滿(mǎn)足特定條件時(shí)可以選擇性安裝的依賴(lài),且即使安裝失敗,安裝命令也不會(huì)中斷。可選依賴(lài)項(xiàng)通常用于提供額外的功能或優(yōu)化,并不是必需的;
  • bundledDependencies:捆綁依賴(lài),用于指定需要一同打包發(fā)布的依賴(lài)項(xiàng),用的比較少。

根據(jù)我們正在開(kāi)發(fā)的軟件包的用途及對(duì)依賴(lài)的使用方式,這里會(huì)有不同的決策邏輯。

假設(shè)正在開(kāi)發(fā)的是“頂層”應(yīng)用(Web APP、Service、CLI 等),那么多數(shù)依賴(lài)都可以注冊(cè)到 devDependencies 或 dependencies 節(jié)點(diǎn),這也是我們?nèi)粘?yīng)用比較多的依賴(lài)類(lèi)型。兩者主要差異在于:dependencies 是生產(chǎn)環(huán)境依賴(lài),是確保軟件包正常運(yùn)行的必要依賴(lài)項(xiàng);而 devDependencies 則是僅在開(kāi)發(fā)階段需要使用的依賴(lài)項(xiàng)。

舉個(gè)例子,假設(shè)你應(yīng)用邏輯中直接使用了 lodash 的方法,那么 lodash 必然是 dependencies;但假設(shè)你只是在一些構(gòu)建腳本之類(lèi)的非應(yīng)用邏輯中使用了 lodash ,那么應(yīng)該將其注冊(cè)到 devDependencies 中。

PS:對(duì)于需要將代碼和依賴(lài)全部打包在一起的應(yīng)用 —— 例如常見(jiàn)的基于 Webpack 的 web 應(yīng)用,從功效上 dependencies 與 devDependencies 并無(wú)差別,但建議還是根據(jù)語(yǔ)義對(duì)依賴(lài)做好分類(lèi)管理。

換個(gè)視角,假設(shè)正在編寫(xiě)的代碼最終會(huì)被發(fā)布成 NPM Package 供其他方消費(fèi),那么我們必須慎重許多,因?yàn)槟愕臎Q策會(huì)深刻影響消費(fèi)者的使用體驗(yàn)。首先,你必須非常謹(jǐn)慎地使用 dependencies,因?yàn)?NPM 在安裝你這個(gè) Package 會(huì)順帶將你的 package.json 中的 dependencies 也都安裝一遍,錯(cuò)誤的依賴(lài)分類(lèi)可能會(huì)帶來(lái)一些影響開(kāi)發(fā)體驗(yàn)的 Bad Case:

  • 需要占用更多的安裝依賴(lài)的時(shí)間;
  • 依賴(lài)結(jié)構(gòu)更復(fù)雜,容易導(dǎo)致“菱形依賴(lài)”(后面會(huì)會(huì)展開(kāi)解釋)問(wèn)題;

舉個(gè)例子,@vue/cli 的 package.json 部分內(nèi)容如下:

{
  "name": "@vue/cli",
  "version": "5.0.8",
  ...
  "dependencies": {
    ...
    "vue": "^2.6.14",
    ...
  },
  ...
}

那么使用者安裝 @vue/cli 之后,還會(huì)強(qiáng)制安裝 vue@^2.6.14 版本 —— 即使用戶(hù)消費(fèi)的可能是其他 Vue 版本,這種行為無(wú)疑都會(huì)給用戶(hù)增加不必要的負(fù)擔(dān),因此,在開(kāi)發(fā) Package 時(shí),除非有非常明確且強(qiáng)烈的訴求,否則都應(yīng)該優(yōu)先使用 devDependencies!

那么,假設(shè)你的 Package 確實(shí)存在一些必要,但又不適合注冊(cè)到 dependencies 的依賴(lài),該怎么辦呢?這種 Case 也非常常見(jiàn),例如 Webpack 插件通常對(duì) Webpack 存在強(qiáng)依賴(lài),但并不適合直接使用 dependencies,否則可能導(dǎo)致用戶(hù)安裝多份 Webpack 副本。針對(duì)這種情況 NPM 提供了另外一種依賴(lài)類(lèi)型:peerDependencies,語(yǔ)義上可以理解為:Package 希望宿主環(huán)境提供的“對(duì)等”依賴(lài),NPM 對(duì)這種類(lèi)型的處理邏輯稍微有點(diǎn)復(fù)雜:

若宿主提供了對(duì)等依賴(lài)聲明(無(wú)論是 dependencies 還是 devDependencies),則優(yōu)先使用宿主版本,若版本沖突則報(bào)出警告:

若宿主未提供對(duì)等依賴(lài),則嘗試自動(dòng)安裝對(duì)應(yīng)依賴(lài)版本(NPM 7.0 之后支持)。

PS:正是因?yàn)?nbsp;peerDependencies 的復(fù)雜性,不同包管理器,甚至同一包管理器的不同版本對(duì)其處理邏輯都有所不同,例如 NPM 在 3.0 之前支持自動(dòng)安裝 peerDependencies,但這一特性帶來(lái)的問(wèn)題比較多,3.0 之后取消了自動(dòng)下載,交由消費(fèi)者自行維護(hù),一直到 7.0 版本設(shè)計(jì)了一種更高效的依賴(lài)推算算法之后,才又重新引入這一特性。

peerDependencies 能幫助我們實(shí)現(xiàn):“「即要」”確保 Package 能正常運(yùn)行,“「又要」”避免給用戶(hù)帶來(lái)額外的依賴(lài)結(jié)構(gòu)復(fù)雜性,在開(kāi)發(fā) NPM Package,特別是一些“框架”插件、組件時(shí)可以多加使用,實(shí)踐中通常還會(huì):

  • 使用 peerDependencies 聲明 Wepack 為對(duì)等依賴(lài),要求宿主環(huán)境安裝對(duì)應(yīng)依賴(lài)副本。
  • 同時(shí)使用 devDependencies 聲明 Wepack 為開(kāi)發(fā)依賴(lài),確保開(kāi)發(fā)過(guò)程中能正確安裝必要依賴(lài)項(xiàng)。

接下來(lái)聊一個(gè)相對(duì)冷門(mén)的類(lèi)型:optionalDependencies,也就是“可選”依賴(lài),雖然多數(shù)時(shí)候我們對(duì) Package 的依賴(lài)應(yīng)該是比較明確的:要么有要么沒(méi)有,但某些特定場(chǎng)景下也可能是“可以有也可以沒(méi)有”。

舉個(gè)例子,fsevents 是一個(gè)針對(duì) 「Mac OSX」 系統(tǒng)的文件系統(tǒng)事件監(jiān)控庫(kù) —— 注意啊,它只適用于 「Mac OSX」 系統(tǒng),因此在其他操作系統(tǒng)上都不能使用 —— 自自然然的也不需要安裝這個(gè) Package,因此可以是一個(gè)“可選”依賴(lài),實(shí)際上在知名構(gòu)建工具 rollup 中就是以 optionalDependencies 方式引入 fsevents 的:

{
  "name": "rollup",
  "version": "4.1.4",
  // ...
  "optionalDependencies": {
    "fsevents": "~2.3.2"
  },
  // ...
}

需要注意,optionalDependencies 意味著“可能有也可能沒(méi)有”,因此消費(fèi)方式上也需要加以區(qū)分,例如 rollup 是這么導(dǎo)入 fsevents 的:

import type FsEvents from 'fsevents';

export async function loadFsEvents(): Promise<void> {
  try {
    // 使用 `import` 函數(shù)異步導(dǎo)入,并做好異常判斷
    ({ default: fsEvents } = await import('fsevents'));
  } catch (error: any) {
    fsEventsImportError = error;
  }
}

// ...

代碼位置:rollup/src/watch/fsevents-importer.ts

optionalDependencies 非常適合用作處理“平臺(tái)”強(qiáng)相關(guān)的依賴(lài),除此之外還可用于性能兜底、交互功能兜底等場(chǎng)景,這里就不一一贅述了。

簡(jiǎn)單總結(jié)下,package.json 提供了若干影響安裝行為的依賴(lài)類(lèi)型屬性,以應(yīng)對(duì)不同場(chǎng)景的管理需求,開(kāi)發(fā)者需要基于性能、可用性、穩(wěn)定性等角度考慮謹(jǐn)慎判斷依賴(lài)類(lèi)型。當(dāng)然,也有一些基本規(guī)則能幫助我們快速識(shí)別依賴(lài)類(lèi)型,包括:

  • 常見(jiàn)的各類(lèi)工程化工具,如 eslint、vitest、vite、jest、webpack 等等都適合放在 devDependencies。
  • 各類(lèi) TS 類(lèi)型包,例如 @types/react、@types/react-dom 一般也可以放在 devDependencies 中。
  • 開(kāi)發(fā)框架插件時(shí),盡可能將框架聲明為 peerDependencies,例如 webpack 與 cache-loader。
  • 平臺(tái)強(qiáng)相關(guān)的依賴(lài),可以考慮使用 optionalDependencies,之后配合 postinstall 鉤子執(zhí)行平臺(tái)相關(guān)的依賴(lài)安裝 or 編譯動(dòng)作。
  • 等等。

3. 失控的依賴(lài)結(jié)構(gòu)

思考一下:「安裝某個(gè)依賴(lài)時(shí),需要附帶安裝多少子孫依賴(lài)」?很多同學(xué)此前可能沒(méi)關(guān)注過(guò)這一塊,這個(gè)問(wèn)題并沒(méi)有具體的通用答案,取決于你實(shí)際安裝的包,但這個(gè)數(shù)量通常都不會(huì)很小。舉個(gè)例子,知名的 React 組件庫(kù) antd 的依賴(lài)結(jié)構(gòu)是這樣的:

這張圖肉眼可見(jiàn)的復(fù)雜。。。一旦我們決定使用 antd 則必須引入這一坨復(fù)雜的依賴(lài)結(jié)構(gòu),而這并不是孤例,不少知名框架都有類(lèi)似問(wèn)題,包括 jest、webpack、http-parser 等等,當(dāng)我們依賴(lài)這些 Package 時(shí),依賴(lài)結(jié)構(gòu)最終會(huì)合并成一張龐大、復(fù)雜,且沖突不斷的網(wǎng)絡(luò)。

造成這一現(xiàn)象的原因其實(shí)不難理解,在當(dāng)下開(kāi)源文化環(huán)境下,跨組織的代碼共享變得如此簡(jiǎn)單平常,即使是非常小的代碼片段都可以以極低的成本貢獻(xiàn)到社區(qū)供人使用。舉個(gè)例子,在 NPM 上有一個(gè)這么一個(gè) Package:escape-string-regexp,它的核心代碼算上注釋才不到十行,但周下載量達(dá)到驚人的一億次:

export default function escapeStringRegexp(string) {
        if (typeof string !== 'string') {
                throw new TypeError('Expected a string');
        }

        // Escape characters with special meaning either inside or outside character sets.// Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.return string
                .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
                .replace(/-/g, '\\x2d');
}

在此背景下,多數(shù)時(shí)候 —— 包括開(kāi)發(fā) Package 時(shí),多數(shù)開(kāi)發(fā)者在解決特定需求時(shí)自然都會(huì)傾向于使用開(kāi)源代碼片段,這可以幫助作者跳過(guò)代碼「設(shè)計(jì)、開(kāi)發(fā)、測(cè)試、調(diào)試、維護(hù)」等步驟,進(jìn)而提升開(kāi)發(fā)效率。

這本是一種良好實(shí)踐,但當(dāng)它被廣泛采用時(shí),不可避免的會(huì)帶來(lái)一個(gè)副作用:依賴(lài)粒度變得非常細(xì)小,依賴(lài)網(wǎng)絡(luò)結(jié)構(gòu)變得無(wú)比復(fù)雜龐大,而這又容易(或者說(shuō)必然)觸發(fā)更多負(fù)面效應(yīng),包括:

  • 需要計(jì)算依賴(lài)包之間的關(guān)系并下載大量依賴(lài)包,CPU 與 IO 占用都非常高,導(dǎo)致項(xiàng)目初始化與更新性能都比較差,我就曾經(jīng)歷過(guò)初始 yarn install 需要跑兩個(gè)小時(shí),加一個(gè)依賴(lài)需要跑半個(gè)小時(shí)的巨石項(xiàng)目。。。開(kāi)發(fā)體驗(yàn)一言難盡;
  • 多個(gè) Package 的依賴(lài)網(wǎng)絡(luò)可能存在版本沖突,輕則導(dǎo)致重復(fù)安裝,或重復(fù)打包,嚴(yán)重時(shí)可能導(dǎo)致 Package 執(zhí)行邏輯與預(yù)期不符,引入一些非常難以定位的 bug,這個(gè)問(wèn)題比較隱晦卻重要,后面我們還會(huì)展開(kāi)細(xì)講;
  • 由于可能存在大量沖突,項(xiàng)目的依賴(lài)網(wǎng)絡(luò)可能變得非常脆弱,某些邊緣節(jié)點(diǎn)的微小變化可能觸發(fā)依賴(lài)鏈條上層大量 Package 的版本發(fā)生變化,引起雪崩效應(yīng),進(jìn)而影響軟件最終執(zhí)行效果,這同樣可能引入一些隱晦的 bug;
  • 等等吧。

那么,如何應(yīng)對(duì)這些問(wèn)題呢?先說(shuō)結(jié)論:沒(méi)有一勞永逸完美方案,只能盡力降低問(wèn)題出現(xiàn)的范圍和影響。首先我們不應(yīng)該因噎廢食,即使存在上述問(wèn)題,依賴(lài)外置更有助于提升模塊之間的低耦合高內(nèi)聚,保持更佳的可維護(hù)性,在此基礎(chǔ)上,可以適當(dāng)引入一些管理措施緩解癥狀,包括:

  • 設(shè)定更嚴(yán)格的開(kāi)源包審核規(guī)則:除了周下載量、Star 數(shù)這些指標(biāo)外,可以適當(dāng)打開(kāi)倉(cāng)庫(kù)看看代碼結(jié)構(gòu)是否合理,是否有單測(cè),單測(cè)覆蓋率多少,是否能通過(guò)單測(cè),Issue 持續(xù)時(shí)間,二級(jí)依賴(lài)網(wǎng)絡(luò)結(jié)構(gòu)是否合理等等,確保依賴(lài)的質(zhì)量是穩(wěn)定可信賴(lài)的;
  • 盡可能減少不必要的依賴(lài):在引入第三方庫(kù)時(shí),仔細(xì)審查其功能,看看是否真的需要使用整個(gè)庫(kù),或者我們僅需要其中的部分功能,有時(shí)我們可能自己實(shí)現(xiàn)(甚至 Copy)這些功能更為快速、更為簡(jiǎn)單,同時(shí)也減少了對(duì)第三方庫(kù)的依賴(lài);
  • 分層依賴(lài):如果項(xiàng)目較大(monorepo?),可以將項(xiàng)目分層,每一層只能依賴(lài)相同層級(jí)或更基礎(chǔ)的層級(jí)的庫(kù),這樣可以降低各層之間的相互依賴(lài),也有助于分層級(jí)管理依賴(lài)結(jié)構(gòu),減小變動(dòng)對(duì)上游的影響;
  • 避免循環(huán)依賴(lài):循環(huán)依賴(lài)絕對(duì)是一種可怕的災(zāi)難!它不僅會(huì)急劇提升依賴(lài)網(wǎng)絡(luò)的結(jié)構(gòu)復(fù)雜度,還很可能導(dǎo)致一些難以預(yù)料的問(wèn)題,因此在做依賴(lài)結(jié)構(gòu)審計(jì)時(shí)務(wù)必盡可能規(guī)避這類(lèi)情況。

4. 幽靈依賴(lài)

“幽靈依賴(lài)”是指我們明明沒(méi)有在 package.json 中注冊(cè)聲明某個(gè)依賴(lài)包,卻能在代碼中引用消費(fèi)該 Package,之所以出現(xiàn)這個(gè)問(wèn)題,歸根到底主要是兩個(gè)因素引起的:

  • NodeJS 的模塊尋址邏輯;
  • 包管理器執(zhí)行 install 命令后,安裝下來(lái)的 node_modules 文件目錄結(jié)構(gòu)。

眾所周知(吧?),在 NodeJS 以及 Webpack、Babel 等常見(jiàn)工程化工具中,當(dāng)我們使用 require/import 導(dǎo)入外部依賴(lài)包時(shí),NodeJS 會(huì)首先嘗試在當(dāng)前目錄下的 node_modules 尋找同名模塊,若未找到則沿著目錄結(jié)構(gòu)逐級(jí)向上遞歸查找 node_modules 直至系統(tǒng)根目錄,例如在 /home/user/project/foo.js 文件中查找模塊時(shí),可能會(huì)在如下目錄嘗試尋找 Package:

/home/user/project/node_module
/home/user/node_module
/home/node_module
/node_module

若此時(shí)某些 Package 被安裝在項(xiàng)目 project 路徑的上層,則必然會(huì)被尋址邏輯命中,導(dǎo)致代碼中能夠“錯(cuò)誤”引用到這個(gè)包。其次,即使不考慮這個(gè)目錄遞歸尋址邏輯,NPM 與 Yarn 的扁平化 node_modules 結(jié)構(gòu)也非常容易引起幽靈依賴(lài)問(wèn)題。這里補(bǔ)充點(diǎn)歷史知識(shí),在 NPM@3 之前,每個(gè)模塊的依賴(lài)項(xiàng)都會(huì)被放置在自己專(zhuān)屬的 node_modules 文件夾內(nèi),即所謂的"「嵌套依賴(lài)」",例如:

  • 依賴(lài)結(jié)構(gòu):
- A
  - B 
    - C 
- D
  - C
  • node_module 結(jié)構(gòu):
- node_modules
  - A
    - node_modules
      - B
        - node_modules
          - C
  - D
    - node_modules
      - C

這種方案非常容易導(dǎo)致依賴(lài)結(jié)構(gòu)深度過(guò)大,最終可能導(dǎo)致文件路徑超過(guò)了一些系統(tǒng)的最大文件路徑長(zhǎng)度限制(主要是Windows 系統(tǒng)),導(dǎo)致奔潰。這就引入 NPM@3 的優(yōu)化策略:扁平化依賴(lài)結(jié)構(gòu),也就是將所有的模塊 —— 無(wú)論是頂層依賴(lài)還是子依賴(lài),都會(huì)直接寫(xiě)入到在項(xiàng)目頂層的 node_modules 目錄中,例如:

  • 依賴(lài)結(jié)構(gòu):
- A
  - B 
    - C 
- D
  - C
  • node_module 結(jié)構(gòu):
- node_modules
  - A
  - B
  - C
  - D

這種目錄結(jié)構(gòu)看起來(lái)更簡(jiǎn)潔清晰,也確實(shí)解決了目錄過(guò)深的問(wèn)題。「但是」,根據(jù) NodeJS 的尋址邏輯,這也就意味著我們可以引用到任意子孫依賴(lài)!這種不明確的依賴(lài)關(guān)系是非常不穩(wěn)定的,可能觸發(fā)很多問(wèn)題:

  • 不一致性:幽靈依賴(lài)可能導(dǎo)致應(yīng)用程序的行為在不同的環(huán)境中表現(xiàn)不一致,因?yàn)椴煌h(huán)境中可能缺少或包含不同版本的幽靈依賴(lài);
  • 不可預(yù)測(cè)性:本質(zhì)上,幽靈依賴(lài)的是頂層依賴(lài)的依賴(lài)網(wǎng)絡(luò)的一部分,你很難精細(xì)控制這些子孫依賴(lài)的版本,完全隨緣;
  • 難以維護(hù):若你的代碼中存在幽靈依賴(lài),在依賴(lài)庫(kù)升級(jí)或遷移時(shí),幽靈依賴(lài)可能導(dǎo)致意外的兼容性問(wèn)題或升級(jí)困難。

那么如何解決幽靈依賴(lài)問(wèn)題呢?其實(shí)也比較簡(jiǎn)單,核心準(zhǔn)則:請(qǐng)務(wù)必確保依賴(lài)關(guān)系是清晰明確的,一旦消費(fèi)則必須在項(xiàng)目工程內(nèi)注冊(cè)依賴(lài)!有許多工具能幫我們達(dá)成這一點(diǎn):

  • 使用 pnpm:與 yarn、npm 不同,pnpm 不是簡(jiǎn)單的扁平化結(jié)構(gòu),而是使用符號(hào)鏈接將物理存儲(chǔ)的依賴(lài)鏈接到項(xiàng)目的 node_modules 目錄,確保每個(gè)項(xiàng)目只能訪(fǎng)問(wèn)在其 package.json 中明確聲明的依賴(lài);
  • 使用 ESLint:ESLint 提供了不少規(guī)則用于檢測(cè)幽靈依賴(lài),例如 import/no-extraneous-dependencies,只需要在項(xiàng)目中啟用即可;
  • 使用 depcheck:這是一個(gè)用于檢測(cè)未使用的或缺失的 npm 包依賴(lài),可以協(xié)助發(fā)現(xiàn)現(xiàn)存代碼可能存在的幽靈依賴(lài),類(lèi)似的還有:npm-check 等。

5. 依賴(lài)沖突

依賴(lài)沖突通常發(fā)生在兩個(gè)或多個(gè)包依賴(lài)不同版本的同一庫(kù)時(shí)。設(shè)想這樣一個(gè)場(chǎng)景:包 app 依賴(lài)了 lib-a、lib-b,而 lib-a、lib-b 又依賴(lài)了 lib-d,此時(shí)這幾個(gè)實(shí)體之間之間形成了一種菱形依賴(lài)關(guān)系:

圖解:菱形依賴(lài)

ok,菱形依賴(lài)本身是一種非常常見(jiàn)且合理的依賴(lài)結(jié)構(gòu),這不是問(wèn)題,真正的問(wèn)題出現(xiàn)在若此時(shí) lib-a/lib-b 所依賴(lài)的 lib-d 版本不一致時(shí),就會(huì)產(chǎn)生依賴(lài)沖突現(xiàn)象:

圖解:依賴(lài)沖突

而這輕則導(dǎo)致 lib-d 被重復(fù)安裝;嚴(yán)重時(shí)可能導(dǎo)致如構(gòu)建失敗、應(yīng)用運(yùn)行錯(cuò)誤(例如 bundle 中同時(shí)存在兩個(gè) react 實(shí)例)等問(wèn)題。其次,更大的隱患在于,依賴(lài)沖突會(huì)使得依賴(lài)網(wǎng)絡(luò)的復(fù)雜度進(jìn)一步提升惡化,降低項(xiàng)目的可維護(hù)性和擴(kuò)展性,長(zhǎng)期難以維護(hù)。

圖解:進(jìn)一步劣化的結(jié)構(gòu)

比較難受的是,依賴(lài)沖突問(wèn)題多數(shù)時(shí)候出現(xiàn)在次級(jí)依賴(lài)中,我們通常無(wú)法細(xì)粒度地管控好這些底層依賴(lài),悲觀地說(shuō),我們還無(wú)法從根本上解決這些問(wèn)題,只能采取一些手段盡可能緩解:

  • 打包構(gòu)建時(shí),可以借助 webpack alias 之類(lèi)的手段,強(qiáng)制指定版本包位置。
  • 可以借助 package.json 的 resolution 字段強(qiáng)制綁定版本號(hào)。
  • 必要時(shí),借助 patch-package 或 pnpm patch 對(duì)依賴(lài)包做微調(diào)。

6. 循環(huán)依賴(lài)

循環(huán)依賴(lài)是指兩個(gè)或多個(gè) Package 之間相互依賴(lài),形成鏈?zhǔn)介]環(huán)的情況。這種循環(huán)結(jié)構(gòu)可能很明顯也可能很隱蔽,但總之在依賴(lài)鏈條上形成了一個(gè)環(huán)狀的結(jié)構(gòu)關(guān)系。

循環(huán)依賴(lài)的問(wèn)題在于,它會(huì)使得依賴(lài)關(guān)系變得非常復(fù)雜 —— 從有向無(wú)環(huán)到更復(fù)雜的有向有環(huán)圖,這會(huì)增加依賴(lài)網(wǎng)絡(luò)解析成本,包管理器通常需要為此編寫(xiě)復(fù)雜的循環(huán)依賴(lài)安裝算法;也會(huì)增加“開(kāi)發(fā)者”的理解成本 —— 而這必然也會(huì)進(jìn)一步降低項(xiàng)目的可維護(hù)性。

其次,循環(huán)依賴(lài)的更新邏輯也會(huì)變得特別啰嗦,假設(shè)存在 A=>B=>C=>A 這樣的循環(huán)依賴(lài)鏈條,那么 B 的更新可能會(huì)導(dǎo)致 C/A 需要同步更新,整體結(jié)構(gòu)的穩(wěn)定性變得非常脆弱。

7. 依賴(lài)更新鏈路長(zhǎng)

設(shè)想一個(gè)場(chǎng)景,存在依賴(lài)鏈條:A => B => C => D,若底層 D 包發(fā)布了一個(gè)新版本(比如修復(fù)了一個(gè)重要的安全問(wèn)題),那么有時(shí)候可能需要鏈條上的 B 與 C 包都隨之更新版本之后,A 才能得到相應(yīng)更新。關(guān)鍵問(wèn)題在于,中間節(jié)點(diǎn)越多,完成更新所需要的時(shí)間往往越長(zhǎng),如果中間某些節(jié)點(diǎn)的更新活躍度并不高的時(shí)候,延遲問(wèn)題必然會(huì)更嚴(yán)重,這些風(fēng)險(xiǎn)點(diǎn)最終都會(huì)嫁接到頂層 A 包身上。

當(dāng)然,當(dāng)下的開(kāi)源依賴(lài)包也并沒(méi)有如上述設(shè)想的那般脆弱,質(zhì)量“良好”的開(kāi)源 Package 往往有較強(qiáng)的容錯(cuò)性,對(duì)底層的依賴(lài)往往也會(huì)優(yōu)先遵循 semver 的范圍版本規(guī)則。但“閉源”軟件包通常就沒(méi)這么高的質(zhì)量要求了,可能會(huì)設(shè)置一些拙劣的兼容策略,甚至為了避免向前向后兼容的麻煩,直接“鎖死”核心依賴(lài)版本,導(dǎo)致底層包出現(xiàn)問(wèn)題時(shí),頂層依賴(lài)可能難以得到更新。

8. 大型應(yīng)用中的依賴(lài)更新

設(shè)想我們正在維護(hù)代碼總量超過(guò) 10w 行且持續(xù)迭代的一個(gè)大型應(yīng)用,若此時(shí)需要對(duì)某些基礎(chǔ)依賴(lài)做比較大的版本升級(jí),那么你所面臨工作量與復(fù)雜度都會(huì)非常高。

首先,你需要細(xì)致地梳理出新舊版本之間的接口、行為差異,這一步需要做許多調(diào)研工作,甚至可能需要仔細(xì)比對(duì)兩個(gè)版本源碼之間的區(qū)別;其次,按照這些差異點(diǎn)對(duì) 10w 行代碼都做一次更新適配,以使得代碼在新版本中能夠正常運(yùn)行,某些命中 Breaking change 的地方可能還需要重新設(shè)計(jì)實(shí)現(xiàn)方案。

這個(gè)過(guò)程隱含著非常大的開(kāi)發(fā)與測(cè)試的工作量,通常需要持續(xù)投入一段時(shí)間做開(kāi)發(fā),但問(wèn)題是業(yè)務(wù)本身還在持續(xù)迭代,不可能把所有事情停下來(lái)等著你慢慢把版本升上去;也通常,這件事情很難僅僅通過(guò)“增加人力”就能提高執(zhí)行效率,因?yàn)楦脑爝^(guò)程隨時(shí)可能出現(xiàn)一些始料未及的新問(wèn)題,需要有足夠技術(shù)功力的人才能高效做出新的判斷與決策。

應(yīng)對(duì)這些問(wèn)題,一個(gè) 「理所當(dāng)然」 的解決方案是 Case by case 地設(shè)計(jì)一些技術(shù)方案來(lái)實(shí)現(xiàn)漸進(jìn)式代碼升級(jí),例如在微前端場(chǎng)景中可以通過(guò)子應(yīng)用方式,將頁(yè)面與模塊逐個(gè)遷移到新的依賴(lài)版本,直至整體升級(jí)完畢;此外,也可以適當(dāng)設(shè)計(jì)一些接口適配器,盡可能減少直接改動(dòng)頂層代碼。

其次,對(duì)于一些工程能力比較強(qiáng)的團(tuán)隊(duì),推薦引入一些 E2E 技術(shù)(彩蛋:為什么這里不是 UT?)并持續(xù)維護(hù)一套至少覆蓋核心鏈路的測(cè)試用例,發(fā)生變更時(shí)由自動(dòng)化測(cè)試技術(shù)確保應(yīng)用狀態(tài)符合功能預(yù)期。這是一種一本萬(wàn)利的技術(shù)投入,同樣適用于驗(yàn)證日常業(yè)務(wù)迭代中的代碼變動(dòng)。

一些最佳實(shí)踐

綜上,依賴(lài)管理是一個(gè)復(fù)雜問(wèn)題,天然存在著許多復(fù)雜性與不可控因素,并且當(dāng)下并沒(méi)有任何解決方案能普適地解決所有問(wèn)題。不過(guò),也有一些值得在日常工作中遵循的最佳實(shí)踐,能夠一定程度上緩解各種問(wèn)題的影響面。

1. 嚴(yán)格審查

在引入新的三方依賴(lài)時(shí),不要輕易做決定!雖然 NPM 已經(jīng)注冊(cè)了數(shù)不勝數(shù)的各種類(lèi)型的依賴(lài),足以覆蓋我們?nèi)粘S龅降亩鄶?shù)開(kāi)發(fā)場(chǎng)景,并且使用成本都非常低,但這并不意味著我們可以未經(jīng)思考通通采用!請(qǐng)記住,在軟件工程中,治理問(wèn)題的成本與復(fù)雜度多數(shù)時(shí)候比開(kāi)發(fā)一個(gè)新功能特性要高出許多,一個(gè)錯(cuò)誤的決策在未來(lái)可能需要花十倍力氣解決問(wèn)題(總是要還的)。

因此,在使用某個(gè) Package 之前,我們至少應(yīng)該對(duì)它做一些基礎(chǔ)的調(diào)研,雖然很難完全準(zhǔn)確評(píng)估一個(gè) Package 的好壞,但某些關(guān)鍵特性還是有助于側(cè)面了解它的質(zhì)量,例如:

  • 是否有完備詳盡的 Readme:這體現(xiàn)了作者的用心程度與專(zhuān)業(yè)度,也同時(shí)決定了我們使用這個(gè)包的成本。理想的 Readme 應(yīng)該至少包含這個(gè)包的使用方法與基本原理,內(nèi)容越詳細(xì)越好;
  • 更新頻率:更新頻率越高通常證明作者或者社區(qū)的活躍度越高,也通常意味著出現(xiàn) Issue 時(shí)解決速度越快,你也不想在遇到問(wèn)題時(shí)沒(méi)有被及時(shí)解決吧?
  • 單測(cè):作為開(kāi)源框架,穩(wěn)定性是一個(gè)非常重要的指標(biāo),而單測(cè)又是一種能夠確保穩(wěn)定性的重要工具,因此可以在做決策時(shí)建議看看框架源碼本身的單測(cè)覆蓋率,以及單測(cè)斷言的使用情況;反之,如果連單測(cè)都沒(méi)做好,建議慎重!
  • Benchmark:與單測(cè)類(lèi)似,若源碼中包含一定比例的 Benchmark,則意味著作者對(duì)作品的性能有一定要求,那么自然地質(zhì)量相對(duì)更值得信任一些;
  • 下載或 Star 量:這兩個(gè)指標(biāo)通常意味著這個(gè)開(kāi)源作品被使用的頻率,頻率越高通常意味著被越多人消費(fèi)、驗(yàn)證過(guò),也就越能證明這個(gè)框架不會(huì)存在一些基本的質(zhì)量問(wèn)題 —— 至少能跑的通嘛;不過(guò)請(qǐng)注意,不要迷信這兩個(gè)指標(biāo),有許多場(chǎng)外因素(例如發(fā)布時(shí)間、作者影響力等)都會(huì)影響這些數(shù)量的變化,數(shù)量大不足以證明質(zhì)量高;
  • 代碼結(jié)構(gòu):如果時(shí)間允許,非常建議審查開(kāi)源框架的代碼結(jié)構(gòu),如果發(fā)現(xiàn)明顯的 Bad Smell,例如圈復(fù)雜度明顯很高,或者有許多重復(fù)代碼,則建議慎重采用;

2. 定期清理無(wú)用依賴(lài)

隨項(xiàng)目迭代,依賴(lài)列表通常會(huì)逐漸增加,但很少被及時(shí)清理,導(dǎo)致無(wú)用依賴(lài)逐漸增多,甚至可能引發(fā)上述諸多依賴(lài)問(wèn)題,因此建議有一套機(jī)制,定期掃描 & 刪除項(xiàng)目中的無(wú)用依賴(lài)。社區(qū)已經(jīng)提供了不少依賴(lài)掃描工具,例如 depcheck,借助這些工具我們能快速找出無(wú)用依賴(lài)。

3. 定期 review 依賴(lài)結(jié)構(gòu)圖

同樣,隨項(xiàng)目迭代,依賴(lài)結(jié)構(gòu)圖持續(xù)發(fā)生變化,且通常會(huì)越來(lái)越復(fù)雜,可能多數(shù)開(kāi)發(fā)者體感上覺(jué)得依賴(lài)安裝的時(shí)間越來(lái)越長(zhǎng),但沒(méi)有深究或觀察過(guò)依賴(lài)結(jié)構(gòu)正在出現(xiàn)一些不合理的劣化,可能那天想起來(lái)要優(yōu)化的時(shí)候,問(wèn)題已經(jīng)變得非常復(fù)雜,難以糾偏。

因此,建議在日常工作中關(guān)注依賴(lài)結(jié)構(gòu)的變化情況,是否出現(xiàn)上述異常,例如:重復(fù)依賴(lài)、依賴(lài)沖突等。一個(gè)比較簡(jiǎn)單的方式,是觀察 pnpm-lock.yaml、yarn.lock 等文件的內(nèi)容,可以考慮借助 CI,寫(xiě)腳本,在合碼之前對(duì)比 Merge Request 前后的結(jié)構(gòu)圖,檢查是否出現(xiàn)一些 bad case。

4. 使用 Pnpm

在 JS 社區(qū),目前比較主流的包管理器有:NPM、Yarn、Pnpm 三種,從底層實(shí)現(xiàn)邏輯來(lái)說(shuō),更推薦使用 Pnpm (Performance NPM),它安裝下來(lái)的依賴(lài)結(jié)構(gòu)更合理,能避開(kāi)大多數(shù)幽靈依賴(lài)問(wèn)題,更重要的,它的緩存結(jié)構(gòu)更合理,也因此有更好的安裝、更新性能。

結(jié)語(yǔ)

綜上,社區(qū)開(kāi)源能切實(shí)提升整個(gè)軟件工業(yè)的發(fā)展速度,極大降低開(kāi)發(fā)成本,但不可忽視的也帶來(lái)了一些新的復(fù)雜性 —— 依賴(lài)管理,這其中隱含著許多很少被關(guān)注的隱患,多數(shù)時(shí)候這并不會(huì)直接造成問(wèn)題,但疊加時(shí)間與規(guī)模兩個(gè)因素后,通常會(huì)慢慢會(huì)演變的越來(lái)越復(fù)雜,積重難返!所以,一方面日常需要警惕依賴(lài)結(jié)構(gòu)的劣化,一方面真遇到問(wèn)題時(shí),可以參照上面梳理的各種 case,分析具體問(wèn)題,予以解決。

責(zé)任編輯:姜華 來(lái)源: Tecvan
相關(guān)推薦

2020-06-28 09:30:37

Linux內(nèi)存操作系統(tǒng)

2021-04-20 08:40:11

內(nèi)存管理Lwip

2018-04-19 10:22:06

數(shù)據(jù)中心連接性托管

2022-08-22 09:20:05

Kubernetes工作負(fù)載管理

2023-07-25 15:06:39

2022-08-30 10:15:27

Kubernetes數(shù)據(jù)持久化管理

2022-05-18 16:35:43

Redis內(nèi)存運(yùn)維

2023-07-06 13:56:14

微軟Skype

2020-09-08 06:54:29

Java Gradle語(yǔ)言

2023-06-25 09:44:00

一致性哈希數(shù)據(jù)庫(kù)

2020-05-22 08:16:07

PONGPONXG-PON

2021-01-28 22:31:33

分組密碼算法

2023-09-22 17:36:37

2020-06-02 15:06:13

Tomcat配置頁(yè)面

2018-06-07 13:17:12

契約測(cè)試單元測(cè)試API測(cè)試

2021-08-01 09:55:57

Netty時(shí)間輪中間件

2023-09-27 16:39:38

2024-10-28 21:02:36

消息框應(yīng)用程序

2021-12-06 09:43:01

鏈表節(jié)點(diǎn)函數(shù)

2023-09-20 23:01:03

Twitter算法
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 成人在线免费观看 | 男女深夜网站 | 一区二区三区不卡视频 | 在线观看日韩av | 91精品国产91久久久久久密臀 | 日韩欧美在线一区 | 成人av一区二区在线观看 | 国产福利资源 | 亚洲精品视频在线观看免费 | 午夜精品久久久久久久久久久久 | 国产一区高清 | 国产成人综合一区二区三区 | 国产精品永久久久久 | 亚洲欧洲日韩精品 中文字幕 | 国产免费一区二区 | xxx国产精品视频 | 久久久久久国产精品免费免费 | 久久无毛 | 中文字幕一区二区三区四区五区 | 国产欧美一区二区三区在线播放 | 欧美日韩综合一区 | 日韩免费网站 | 国产一区三区在线 | 免费日本视频 | 激情一区二区三区 | 一级毛片黄片 | 成人a视频 | 三级视频网站 | 久久精品久久精品久久精品 | 永久免费av | 亚洲福利在线视频 | 成人区精品一区二区婷婷 | 久久av一区二区三区 | 日韩乱码一二三 | 91精品在线观看入口 | 久久蜜桃精品 | 国产精品久久久久久久久久 | 国产一伦一伦一伦 | 一区二区三区四区国产 | 国产一级在线观看 | 国产99久久 |