前端開發(fā)由于單頁應(yīng)用的興起,前端是越來越工程化刻肄,代碼量越來越大瓤球。不可避免的需要將工程解構(gòu),然后分派給各個團(tuán)隊單獨(dú)開發(fā)敏弃。
我們希望能類似后端微服務(wù)的效果卦羡,獨(dú)立開發(fā),獨(dú)立部署麦到,而對用戶而言是無感知的绿饵。
First Split
于是我們基于頁面按照團(tuán)隊拆分,將不同的業(yè)務(wù)頁面按業(yè)務(wù)分成不同的業(yè)務(wù)線瓶颠,然后獨(dú)立開發(fā)拟赊,獨(dú)立部署。本質(zhì)上不同團(tuán)隊已經(jīng)在開發(fā)不同網(wǎng)站了粹淋,不過通過一層代理層將各個站點(diǎn)連接起來吸祟,讓終端用戶認(rèn)為他們?nèi)栽谑褂猛环N應(yīng)用。
此拆分可以將業(yè)務(wù)部分拆分較細(xì)粒度桃移,允許團(tuán)隊獨(dú)立開發(fā)屋匕,最后僅僅需要測試人員根據(jù)業(yè)務(wù)要求驗(yàn)證各個業(yè)務(wù)集成時候是否符合需求即可。
對于大部分開發(fā)場景而言借杰,此方案就足夠了过吻。不過既然不同團(tuán)隊開發(fā)的站點(diǎn)其實(shí)是同一個應(yīng)用,頁面上可能有很多共同元素(比如常見的DashBoard應(yīng)用常常有用戶Profile元素蔗衡,以及切換業(yè)務(wù)用的Menu)纤虽。業(yè)務(wù)切換回導(dǎo)致頁面的Reload,共有元素必定會導(dǎo)致瀏覽器重新執(zhí)行绞惦,而且除此之外逼纸,單頁應(yīng)用框架現(xiàn)在都比較重,首屏加載中框架的啟動會占據(jù)大部分時間济蝉,從而每次業(yè)務(wù)切換Reload總會導(dǎo)致一部分JS引擎執(zhí)行時間花費(fèi)在這些公共部分的重新渲染上杰刽,造成用戶體驗(yàn)上感覺加載性能慢呻纹。
Next Split
雖然上訴方案帶來了非常大的開發(fā)便利性,但是導(dǎo)致了的用戶體驗(yàn)的下降专缠。
我們希望能保持上訴方案中,既能保持各個團(tuán)隊獨(dú)立開發(fā)淑仆,獨(dú)立部署的好處涝婉,又能不犧牲頁面切換中,由單頁應(yīng)用拆分后帶來的應(yīng)用框架重復(fù)價值的性能損失蔗怠。
所以我們在前訴方案的基礎(chǔ)上嘗試了我們稱為Dynamic Route Loading的方案嘗試墩弯,確實(shí)提升了用戶體驗(yàn)的上升,但也帶來了很大的缺陷寞射,可供大家作為思路參考渔工。
本思路的核心為:當(dāng)前的單頁應(yīng)用均具有路由功能,并且均能夠提供Lazy Load Route的特性桥温。如果我們能夠通過某種手段欺騙單頁應(yīng)用的框架引矩,讓他能夠拿到拆分過的Lazy Load Route文件,進(jìn)而去加載對應(yīng)頁面侵浸,那么我們就可以做到無需應(yīng)用框架重啟旺韭,便可以加載不同業(yè)務(wù)。
那么我們可以通過Nginx的反向代理功能掏觉,讓應(yīng)用能夠順利拿到對應(yīng)路由的代碼区端,此方案的關(guān)鍵在于如何確保共有代碼絕對一致,從而保證框架能夠一直認(rèn)為其為一個完整的應(yīng)用澳腹。
自然可以得出织盼,我們必須保證開發(fā)中加入一些限制條件,才能保證本方案能夠被順利執(zhí)行酱塔。
- 每個工程必須擁有所有業(yè)務(wù)的全量路由表:因?yàn)榧热辉跒g覽器上需要保證為一個單頁應(yīng)用的形式沥邻,那么應(yīng)用必須知道所有應(yīng)用的路由,才能正確地加載應(yīng)用延旧。
- 公同依賴(包含各個業(yè)務(wù)開發(fā)中沒有抽離到初始化代碼中谋国,但是都會使用的依賴,比如lodash)需要一致:應(yīng)用需要能被正確執(zhí)行迁沫,那么就必須保證頁面切換中芦瘾,頁面中所有部分的代碼都是基于同一套基礎(chǔ)依賴的,不然就無法保證應(yīng)用能夠被正確執(zhí)行集畅,比如A近弟,B站需要互相無刷新頁面切換,A站使用React開發(fā)挺智,B站使用Angular開發(fā)祷愉,兩天路由加載的邏輯根本的不同,你無法使用React的路由去加載Angular的路由代碼,反之亦然二鳄。當(dāng)然在實(shí)踐執(zhí)行中赴涵,你即便使用的同一套框架,很多細(xì)節(jié)的代碼也必須保持一致才能保持整個大應(yīng)用能準(zhǔn)確無誤的被執(zhí)行订讼。
凡事必有代價髓窜,這個方案在我們實(shí)踐中也帶來了不少問題。
你別看上面兩項限制看起來簡單欺殿,但是因?yàn)槲覀兊幕舅悸肥腔谀硞€現(xiàn)有的前端框架寄纵,然后將不同站點(diǎn)通過Nginx等代理軟件,偽裝成同一個單頁應(yīng)用的一部分來提示用戶體驗(yàn)脖苏。不同業(yè)務(wù)必須要保證其均能夠運(yùn)行在同一套代碼上下文下程拭,這本身就是本末倒置,之前方案一的出發(fā)點(diǎn)就是為了解耦應(yīng)用棍潘,讓業(yè)務(wù)開發(fā)者不需要了解其他團(tuán)隊怎么開發(fā)恃鞋,只需要保證符合業(yè)務(wù)需求即可。但是此方案導(dǎo)致:
- 開發(fā)者必須使用同樣的開發(fā)框架亦歉,本身就和微服務(wù)的開發(fā)框架選項無關(guān)性背道而馳山宾。
- 因?yàn)閼?yīng)用運(yùn)行在同一套框架執(zhí)行上下文上,而開發(fā)者團(tuán)隊又是在獨(dú)立開發(fā)和部署的開發(fā)模式下鳍徽。必然導(dǎo)致線上線下環(huán)境的割裂资锰,很多聯(lián)調(diào)的問題只會在上線部署配置完成后,一些執(zhí)行上下文的細(xì)微區(qū)別的影響才有可能會被暴露出來阶祭,輕則業(yè)務(wù)切換B后應(yīng)用行為和單獨(dú)訪問B的時候不一致绷杜,重則加載B站會導(dǎo)致應(yīng)用崩潰。
這個方案下濒募,由于環(huán)境的割裂鞭盟,業(yè)務(wù)開發(fā)者又不可能去了解整個應(yīng)用的開發(fā)進(jìn)展,必然導(dǎo)致很多錯誤會被推出到上線瑰剃,而排錯又需要了解整個運(yùn)行原理和所有團(tuán)隊的開發(fā)進(jìn)展齿诉,導(dǎo)致排錯困難。錯誤又難以在上線前排查出來晌姚。
這個方案而言粤剧,我們又進(jìn)一步做了一些優(yōu)化方案,其中比較好的方案是將公共依賴盡可能的抽出成為CDN挥唠,并且我們保證CDN上的版本總是最新的版本抵恋,這樣很多依賴版本導(dǎo)致的問題可以結(jié)合一些開發(fā)部署策略盡可能避免,這樣基本能夠被應(yīng)用起來宝磨,但不得不說弧关,離真正的前端微服務(wù)而言盅安,還有很長的路。
Other Solution Idea
還有其他的一些解決思路世囊,可以參考下這篇文章Building Microfrontends别瞭,其中最有希望的是Web Component方案,不過這個方案基于瀏覽器標(biāo)準(zhǔn)的實(shí)現(xiàn)株憾,而且每個組件部分其實(shí)完整的應(yīng)用畜隶,可能會引入一定的代碼冗余,但是是能滿足我們在獨(dú)立部署下不刷新切換頁面愿景的号胚,而且解耦的粒度更下,是頁面上的組件級別浸遗,是相對有潛力的一個方案猫胁。