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