個推前端微服務化:突破傳統SPA瓶頸
本文作者:個推高階前端開發工程師 沈創
目前的前端領域,單頁面應用(SPA)大行其道。而隨著時間的推移以及應用功能的豐富,這些應用變得越來越龐大也越來越難以維護。於是“微前端”這一概念應運而生。
“微前端”出自2016 年的 ThoughtWorks 技術雷達,指將專案拆分成一個個可獨立執行、獨立開發、獨立部署的前端微應用,這些微應用可以並行開發、共享元件。
而微前端的實現方式也分很多種:伺服器路由重定向、組合多個獨立應用、iFrame、通過Web Components構建等。
微前端的相關概念也在個推前端中的部分專案(基於Vue框架)中得到應用
為什麼要進行前端微服務化
之所以強調“部分專案”,是因為任何一種技術或者概念都有其適用場景,微前端也不例外。針對中小型的專案,使用微前端反而會將事情複雜化,因為微前端對專案的開發並不友好。
以個推的業務場景為例:
在A專案線中有10-20個模組,每一個模組中有5-15個不等的頁面。而A專案線中所有的產品都是基於這些模組來自由組合的,也就是說:如果按照普通的SPA開發路線,我們可能需要很多分支或者repo來維護這些產品,因為每個產品所需的模組版本會有細微區別。
演變的過程
為了不出現分支混亂、專案龐大、程式碼衝突、打包麻煩等一系列的問題,藉著後端微服務拆分的機會,我們開始對A專案線前端開發和部署方式進行了調整。
最初,我們並沒有使用前端微服務的開發和部署方式,而是先把專案中的各個模組拆分成了許多獨立的repo,避免團隊內的工程師在開發的過程中出現需要pull程式碼並解決衝突的情況(一個模組一個迭代一般由1-2人完成)。
因此,我們的問題是:模組拆分後,如何解決開發、打包部署,以及專案中的公共依賴和元件複用的問題。
拆分後的模組專案目錄結構大致如下:
專案中的main.js入口和公共元件被抽離成了一個單獨的專案,這裡稱為main專案。
由於各個子模組專案中僅有當前模組的頁面程式碼和路由、選單配置,所以dev子模組無法被直接開發。於是我們開發了一個名為lego的CLI工具。開發模組時,開發人員只需要在模組根目錄執行“lego dev”命令即可啟動一個當前模組的開發服務,開發好的模組都會被髮布到我們自己的npm源進行版本的管理。
如果僅僅是對模組進行拆分,那麼開發人員單獨對模組進行開發時,需要給模組配置對應的執行環境,並且模組與模組之間的相互呼叫也很麻煩。而“lego”CLI解決了模組執行環境的問題,執行環境由CLI自動載入,模組開發人員只需要關注模組自身的業務邏輯即可。
此外,模組還提供了一個config.js檔案,可以從npm源配置其他依賴模組,幫助開發人員在開發時更便捷地呼叫不同模組。使用“lego dev”命令還支援“@self/”路徑引入,“@self/”路徑指向當前模組的src/資料夾,而“@/”指向main專案的src/資料夾,從而避免了模組開發時import路徑的問題。
通過模組的拆分改造,解決了專案龐大、分支混亂的問題,程式碼衝突的情況也顯著減少。但是對於單個產品的打包部署,我們仍然需要從各個模組獲取原始碼,並通過main專案打包成一個獨立的產品。即使只修改了某一個模組的一行程式碼,整個系統也需要重新打包,打包後的整個產品也需要進行迴歸測試。
針對這一問題,我們思考是否可以直接把模組打包成應用以供呼叫。
模組打包以及獨立部署
我們的理想情況是:各個模組可以獨立開發和部署,然後由產品自身決定載入的模組。
效果如下:
因此我們需要在模組打包之後,入口(index.js)可以按照需要被注入到main專案中,並且被main專案載入(路由)。
一方面,使用webpack進行打包的專案,程式碼是基於CommonJS規範的。由於umd規範兼容於CommonJS規範,這使得開發人員可以直接在專案中使用基於umd規範打包後的模組。
另一方面,vue-router和vuex庫,都支援動態載入addRouter/registerModule的API。
我們採用過兩種方案:
第一種:main專案在Vue例項初始化時,將vue-router和vuex的例項暴露到全域性(window),將子模組的路由字首儲存在專案中的路由表。當頁面跳轉到匹配的子模組的路由時,main專案載入子模組umd.js檔案並動態註冊router和vuex module,進而渲染頁面。
簡單DEMO如下圖所示:
第二種:子模組umd.js檔案先載入,向全域性(window)暴露該子模組的路由和vuex資訊。Vue例項從window獲取路由資訊和vuex module、選單資訊等,形成一個獨立的產品。
簡單DEMO如下圖所示:
當然,兩種方案都存在一定的缺點:
第一種方案:首先,子模組js檔案是在頁面跳轉之後再進行載入,因此,在404跳轉和路由許可權校驗的實現上會遇到一些問題;其次,在子模組檔案載入完成之前以及子模組渲染之前都存在較長的頁面白屏時間。
第二種方案:無論子模組使用者是否會訪問到umd入口檔案,該檔案都需要事先載入。這就要求入口檔案需要足夠小,意味著子模組無法使用min-chunk-size-plugin外掛來對chunk進行合併,需要開發人員採用手寫webpackChunkName或者使用其他工具進行合併。
基於VUE-CLI3的實踐
Vue-cli3.x對子模組的打包提供了比較好的支援,使用"vue-cli-service build - target=lib"即可將子模組程式碼打包成umd規範格式。
但是,需要注意以下幾個問題:
-
“--target=lib”的初衷是給釋出到npmjs的元件使用,所以打包出的檔案是不帶hash值的(即使在vue.config.js中配置了chunkName)。我們採取的辦法是在執行lego腳手架的打包命令前,修改vue-cli-service原始碼。
-
使用“--target=lib”打包子模組時,如果沒有配置css-in-js,打包出的css檔案中的background-image路徑有問題。基於此,我們給出兩個解決辦法:配置css-in-js,或者修改node_modules中vue-cli-service原始碼再打包。
以上便是個推前端微服務化的開發及部署的實踐情況。
在實踐中我們發現,微服務化的接入,很好地解決了專案中遇到的維護難、產品編譯部署麻煩等問題。在模組化拆分時,我們開發的CLI工具也很好地解決了模組單獨開發執行的問題。
當然,我們的微服務化方案也存在侷限。它比較適合模組之間聯絡比較緊密的大型專案,且沒有微前端概念中強調的技術無關性以及團隊程式碼隔離性。
在不久的將來,除了微服務化方案的繼續升級,我們還會接入新的框架,迎接新的挑戰。