魔方基礎(chǔ)依賴環(huán)境隔離實踐
魔方是轉(zhuǎn)轉(zhuǎn)內(nèi)部的一個可視化搭建平臺,用于快速搭建一個活動頁面。本次主要分享下在做環(huán)境隔離時遇到的一些問題以及解決辦法。
魔方基礎(chǔ)依賴介紹
- A提供了本地運行組件的能力以及組件需要的所有第三方依賴
- B提供了配置區(qū)的一些常用表單項,如跳轉(zhuǎn)配置、展示終端配置等
- C提供了預(yù)覽區(qū)的一些常用能力,如跳轉(zhuǎn)、埋點上報等
- A依賴B和C,B和C又依賴A
一個魔方組件,通常只需要依賴A即可,因為在安裝A的時候會自動將B & C的內(nèi)容打包生成到A中。
"dependencies": {
"A": "^1.0.0"
}
為什么要做環(huán)境隔離
之前,我們在編譯某個基礎(chǔ)依賴(例如 B)時:
- 會發(fā)布一個正式包(B@1.0.1)
- 并將測試服務(wù)器上專門存放公共依賴的文件下的node_modules刪除,然后執(zhí)行npm install重新安裝依賴
- 在安裝中會使用我們最新發(fā)布的B@1.0.1。(此處實際是在一個臨時文件夾中操作,然后安裝后再復(fù)制出來的,可以減少測試環(huán)境的不可用時間)
舉一個常見的場景來說明下這種模式的問題——小明在開發(fā)B,小紅在開發(fā)C,兩人都在測試環(huán)境進(jìn)行了編譯,導(dǎo)致發(fā)出去了兩個正式包B@1.0.1 & C@1.0.1 。那么此時,如果小明開發(fā)測試完了,想要上個線,那么在服務(wù)器上執(zhí)行到npm i的 時候,就會把小紅還未測試完成的包C@1.0.1給安裝到線上環(huán)境去。(其實在測試服務(wù)器上兩個人的代碼也是混合在一起的,不過畢竟是測試環(huán)境,影響較小)
從這個場景分析,可以發(fā)現(xiàn)有兩個主要的問題:
- 測試環(huán)境發(fā)布正式包,導(dǎo)致線上無法區(qū)分
- npm install正常只會安裝正式包,不會安裝beta包
設(shè)計思路
針對上述的兩個問題,對應(yīng)的解決辦法就是:
- 測試環(huán)境發(fā)beta包,線上發(fā)正式包
- 使用npm install package@version(-beta) 替換npm install
第一點就不說了,大致就是先npm view packageName versions獲取包的所有版本,然后根據(jù)環(huán)境去獲取最新的正式包版本或者是最新的beta版本,然后修改版本號再發(fā)包。
第二點,在更新依賴的時候,通過指定版本號的形式去安裝我們最新發(fā)布的包。只不過在線上環(huán)境中,安裝的是正式包,測試環(huán)境中安裝的beta包。
看起來一切是那么的美好,但現(xiàn)實并不總是一帆風(fēng)順......
問題復(fù)現(xiàn)&解決
初始化一個文件temp,并執(zhí)行npm i先將所有依賴裝一遍。
此時temp/node_modules下的情況為(此處只舉例A和B,C與B情況相同,不再重復(fù))
A: "A@1.0.0" A/node_modules下:無其他依賴
B: "B@1.0.0" B/node_modules下:無其他依賴
接下來,執(zhí)行 npm i B@1.0.0-beta.1去單獨更新B。
執(zhí)行結(jié)果:
A: "A@1.0.0" A/node_modules下:B@1.0.0
B: "B@1.0.0-beta.1" B/node_modules下:無其他依賴
根據(jù)npm包安裝的機制,默認(rèn)情況下是不會使用beta包的,A依賴的B: "^1.0.0"需要使用穩(wěn)定的版本,所以beta版本被放在最外層,而將之前的B@1.0.0放在了A/node_modules下。
魔方的基礎(chǔ)依賴在使用前會在 A下執(zhí)行一個externals命令(postinstall:npm run externals)將B的內(nèi)容打成dist放在A目錄下。但是node_modules依賴的查找順序是先從當(dāng)前文件目錄下查找的,所以生成dist文件時使用的將會是A/node_modules/下的B@1.0.0,而不是最外層的B@1.0.0-beta.1
所以,我需要手動刪除A/node_modules/B@1.0.0,再去執(zhí)行externals命令。
那接下來我們再試試在此基礎(chǔ)上更新A,npm i A@1.0.0-beta.1。
結(jié)果就是出現(xiàn)了更多冗余的依賴。。。
A: "A@1.0.0-beta.1" A/node_modules下:B@1.0.0、A@1.0.0
B: "B@1.0.0-beta.1" B/node_modules下:B@1.0.0、A@1.0.0
原因跟之前一樣,我們安裝的beta版本的A不符合B所依賴的A: "^1.0.0",就導(dǎo)致B下的node_modules中又多了一個A@1.0.0,然后這個A@1.0.0的又依賴一個穩(wěn)定版本的B,所以在同級目錄下還會再多一個B@1.0.0
同樣的,我們?nèi)孕枰仁謩拥娜h除這些冗余的、不符合我們要求的依賴。
綜上,為了確保我們項目中使用的都是我們剛發(fā)布的beta包,我們需要在每一次更新依賴時都執(zhí)行一下這兩條命令去清除冗余的依賴,然后再去執(zhí)行打包命令。
rm -rf ./node_modules/A/node_modules/
rm -rf ./node_modules/B/node_modules/
rm -rf ./node_modules/C/node_modules/
cd node_modules/A
npm run externals
然而,到這一步還沒完事,我將代碼部署到測試服務(wù)器上后,經(jīng)常出現(xiàn)依賴沒有安裝完成或安裝完沒有生成dist文件的情況,總是執(zhí)行到一半就“中斷”了。但是我在本地測試的時候卻不會出現(xiàn)這種問題。
經(jīng)過一步一步的排查,最終將問題定位到了這一行代碼
await shelljs.shellExec()
查看shelljs.shellExec方法:
exports.shellExec = function (command, options = {}) {
return new Promise((resolve, reject) => {
Object.assign(options, {timeout: 300000});
shell.exec(command, options, () => {});
});
};
經(jīng)常中斷,難道是過了超時時間?我試著將超時時間從5min改到10min,部署至測試服務(wù)器,再次更新依賴,一切正常了......(不得不吐槽這個測試服務(wù)器的性能甚至不如我的Mac)
再一看編譯時間,耗時>10min,!真棒。(取反??)
項目名 | 編譯耗時 |
A | 10:08 |
B | 10:38 |
優(yōu)化
如此低效率的更新顯然不能讓人滿意,而且由于魔方自身的原因,當(dāng)編譯基礎(chǔ)依賴時,其他人是不能再部署其他魔方服務(wù)的,這就會阻塞其他人的流程,對開發(fā)人員的體驗是十分差的。
「首先就是先分析問題找出原因:」
- 很容易想到的,當(dāng)安裝beta版本的依賴時,總是會額外產(chǎn)生很多冗余的穩(wěn)定版本的依賴。
- 安裝依賴耗時么?耗時,但是至于這么耗時么?不至于。耗時中的大頭另有其因,其實就是A中的externals命令,打包,這個是比較耗費時間的通常需要1-2分鐘。
所以在一定程度上是問題1導(dǎo)致了問題2——安裝了冗余的依賴,其中冗余的A會自動執(zhí)行externals 命令導(dǎo)致耗時過久。
「所以我們首先需要解決的就是避免安裝冗余的依賴:」
A需要依賴B,是因為需要將B的內(nèi)容打包生成到A下使用。
B需要依賴A只是因為本地開發(fā)時方便調(diào)試。
如果去掉B的依賴項,那就可以在安裝B@beta時避免額外安裝A。但是,本地開發(fā)B的場景還是很多的,因此需要想個辦法盡可能的減少由此帶來的對開發(fā)體驗的影響。
于是我在腳本中加入了一個自動檢查并安裝依賴的命令depcheck。
// dev之前先檢查A:
// list可以列出當(dāng)前工程下的A情況以及版本
// 如果沒有會返回一個假值并走到npm i命令去安裝A
"predev": "npm list A || npm i --no-save A"
這樣,在安裝B@beta的時候就不會額外安裝A也不會額外執(zhí)行externals命令了。
那么在安裝A@beta的時候呢?還是會額外安裝冗余的B然后自動執(zhí)行externals,然后刪除冗余的B,再手動執(zhí)行一次externals。
「接下來需要針對A再次進(jìn)行優(yōu)化:」
由于A是強依賴B的所以不能去掉依賴項,冗余的B肯定是避免不了的,不過這又有什么關(guān)系呢,安裝再刪除一共也影響不了幾秒鐘。
但重點是A中有這樣一個腳本命令:postinstall:npm run externals該命令是為了在開發(fā)時安裝依賴等場景可以自動執(zhí)行externals以減少操作次數(shù)&降低學(xué)習(xí)成本。
這就會導(dǎo)致第一次執(zhí)行externals的時候?qū)嶋H使用的是冗余的穩(wěn)定版本B,而非我們需要的最外層的B@beta,所以還需要刪掉冗余依賴然后額外執(zhí)行一次externals,這才是最耗時的部分。
“要是在npm i的時候可以不執(zhí)行postinstall就好了”,帶著這個期許,我找到了一個好用的參數(shù)——--ignore-scripts(忽略依賴中的腳本命令,不去執(zhí)行任何腳本)
npm i A@beta --ignore-scripts
這樣一來,在安裝A的時候,也不會額外執(zhí)行externals命令了!
「優(yōu)化前后編譯耗時對比:」
項目名 | 優(yōu)化前編譯耗時 | 優(yōu)化后編譯耗時 |
A | 10:08 | 04:42 |
B | 10:38 | 04:17 |
總結(jié)
以上,就是在對魔方基礎(chǔ)依賴環(huán)境隔離改造的思路和問題的解決:
- 通過發(fā)布beta版本的包來區(qū)分測試環(huán)境與線上環(huán)境
- 但是帶來了編譯速度嚴(yán)重下降的問題
- 通過去掉B中的依賴項來避免安裝冗余的依賴
- 針對A,在npm i的使用加入--ignore-scripts命令來避免額外執(zhí)行打包命令externals