為了解決龐大的一整塊後端服務帶來的變更與擴充方面的限制,出現了微服務架構。然而,越來越重的前端工程也面臨同樣的問題,自然地想到了将微服務思想應用(照搬)到前端,于是有了“微前端(micro-frontends)”的概念。即,一種由獨立傳遞的多個前端應用組成整體的架構風格。具體的,将前端應用分解成一些更小、更簡單的能夠獨立開發、測試、部署的小塊,而在使用者看來仍然是内聚的單個産品。
我們常見背景項目通常長這樣:

如果我們的項目需要開發某個新的功能,而這個功能另一個項目已經開發好,我們想直接複用時。
說明:我們需要的隻是别人項目的這個功能頁面的内容部分,不需要别人項目的頂部導航和菜單。
一個比較笨的辦法就是直接把别人項目這個頁面的代碼拷貝過來,但是萬一别人不是 vue 開發的,或者說 vue 版本、UI 庫等不同,以及别人的頁面加載之前操作(路由攔截,鑒權等)我們都需要拷貝過來,更重要的問題是,别人代碼有更新,我們如何做到同步更新。甚至當别的項目采用其它技術棧時,如何內建?顯然複制代碼是行不通的。
以前端元件的概念作類比,我們可以把每個被拆分出的子應用看作是一個應用級元件,每個應用級元件專門實作某個特定的業務功能(如商品管理、訂單管理等)。這裡實際上談到了微前端拆分的原則:即以業務功能為基本單元。經過拆分後,整個系統的結構也發生了變化:
如上圖所示,左側是傳統大型單頁應用的前端架構,所有子產品都在一個應用内,由應用本身負責路由管理,是應用分發路由的方式;而右側是基座模式下的系統架構,各個子應用互不相關,單獨運作在不同的服務上,由基座應用根據路由選擇加載哪個應用到頁面内,是路由分發應用的方式。這種方式使得各個子產品的耦合性大大降低,而微前端需要解決的主要問題就是如何拆分群組織這些子應用。
典型的基于vue-router的Vue應用與這種架構存在着很大的相似性:
代碼越來越多,打包越來越慢,部署更新麻煩,一些插件的更新和公共元件的修改需要考慮的更多,很容易牽一發而動全身。
項目太大,參與人員越多,代碼規範比較難管理,代碼沖突也頻繁。
産品功能齊全,但是客戶往往隻需要其中的部分功能。剝離不需要的代碼後,需要獨立制定版本,獨立維護,增加人力成本。
微前端的誕生也是為了解決以上問題:
複用(嵌入)别人的項目頁面,但是别人的項目運作在他自己的環境之上。
巨無霸應用拆分成一個個的小項目,這些小項目獨立開發部署,又可以自由組合進行售賣。
使用微前端的好處:
技術棧無關,各個子項目可以自由選擇架構,可以自己制定開發規範。
快速打包,獨立部署,互不影響,更新簡單。
可以很友善的複用已有的功能子產品,避免重複開發。
目前主流的微前端方案包括以下幾個:
iframe
基座模式,主要基于路由分發,qiankun和single-spa就是基于這種模式
組合式內建,即單獨建構元件,按需加載,類似npm包的形式
EMP,主要基于Webpack5 Module Federation
Web Components
iframe:是傳統的微前端解決方案,基于iframe标簽實作,技術難度低,隔離性和相容性很好,但是性能和使用體驗比較差,多用于內建第三方系統;
基座模式:主要基于路由分發,即由一個基座應用來監聽路由,并按照路由規則來加載不同的應用,以實作應用間解耦;
組合式內建:把元件單獨打包和釋出,然後在建構或運作時組合。
EMP:基于Webpack5 Module Federation,一種去中心化的微前端實作方案,它不僅能很好地隔離應用,還可以輕松實作應用間的資源共享和通信。
Web Components:是官方提出的元件化方案,它通過對元件進行更高程度的封裝,來實作微前端,但是目前相容性不夠好,尚未普及。
總的來說,iframe主要用于簡單并且性能要求不高的第三方系統;組合式內建目前主要用于前端元件化,而不是微前端;基座模式、EMP和Web Components是目前主流的微前端方案。
目前微前端最常用的有兩種解決方案:iframe 方案和 基座模式方案。
iframe 大家都很熟悉,使用簡單友善,提供天然的 js/css 隔離,也帶來了資料傳輸的不便,一些資料無法共享(主要是本地存儲、全局變量和公共插件),兩個項目不同源(跨域)情況下資料傳輸需要依賴 postMessage 。
iframe 有很多坑,但是大多都有解決的辦法:
1. 頁面加載問題
iframe 和首頁面共享連接配接池,而浏覽器對相同域的連接配接有限制,是以會影響頁面的并行加載,阻塞 onload 事件。每次點選都需要重新加載,雖然可以采用 display:none 來做緩存,但是頁面緩存過多會導緻電腦卡頓。(無法解決)
2. 布局問題
iframe 必須給一個指定的高度,否則會塌陷。
解決辦法:子項目實時計算高度并通過 postMessage 發送給首頁面,首頁面動态設定 iframe 高度。有些情況會出現多個滾動條,使用者體驗不佳。
3. 彈窗及遮罩層問題
彈窗隻能在 iframe 範圍内垂直水準居中,沒法在整個頁面垂直水準居中。
解決辦法1:通過與架構頁面消息同步解決,将彈窗消息發送給首頁面,首頁面來彈窗,對原項目改動大且影響原項目的使用。
解決辦法2:修改彈窗的樣式:隐藏遮罩層,修改彈窗的位置。
4. iframe 内的 div 無法全屏
彈窗的全屏,指的是在浏覽器可視區全屏。這個全屏指的是占滿使用者螢幕。
全屏方案,原生方法使用的是 Element.requestFullscreen(),插件:vue-fullscreen。當頁面在 iframe 裡面時,全屏會報錯,且 dom 結構錯亂。
解決方案:iframe 标簽設定 allow="fullscreen" 屬性即可
5. 浏覽器前進/後退問題
iframe 和首頁面共用一個浏覽曆史,iframe 會影響頁面的前進後退。大部分時候正常,iframe 多次重定向則會導緻浏覽器的前進後退功能無法正常使用。并且 iframe 頁面重新整理會重置(比如說從清單頁跳轉到詳情頁,然後重新整理,會傳回到清單頁),因為浏覽器的位址欄沒有變化,iframe 的 src 也沒有變化。
6. iframe 加載失敗的情況不好處理
非同源的 iframe 在火狐及 chorme 都不支援 onerror 事件。
解決辦法1:onload 事件裡面判斷頁面的标題,是否 404 或者 500
解決辦法2:使用 try catch 解決此問題,嘗試擷取 contentDocument 時将抛出異常。
基座模式方案以single-spa和qiankun為代表,這裡我選擇qiankun。
qiankun 是螞蟻金服開源的一款架構,它是基于 single-spa 的。他在 single-spa 的基礎上,實作了開箱即用,除一些必要的修改外,子項目隻需要做很少的改動,就能很容易的接入。
qiankun架構官網:https://qiankun.umijs.org/zh/。
微前端中子項目的入口檔案常見的有兩種方式:JS entry 和 HTML entry。純 single-spa 采用的是 JS entry,而 qiankun 既支援 JS entry,又支援 HTML entry。
JS entry 的要求比較苛刻:
(1)将 css 打包到 js 裡面
(2)去掉 chunk-vendors.js,
(3)去掉檔案名的 hash 值
(4)将 single-spa 模式的入口檔案( app.js )放置到 index.html 目錄,其他檔案不變,原因是要截取 app.js 的路徑作為 publicPath。
建議使用 HTML entry ,使用起來和 iframe 一樣簡單,但是使用者體驗比 iframe 強很多。qiankun 請求到子項目的 index.html 之後,會先用正則比對到其中的 js/css 相關标簽,然後替換掉,它需要自己加載 js 并運作,然後去掉 html/head/body 等标簽,剩下的内容原樣插入到子項目的容器中 。
以“大資料分析”項目為例,将客戶特有的需求,如“電子路單”、“資料填報”單獨提取為可獨立運作的子項目。
大資料分析項目改造為主應用基座,代碼倉庫位址:http://192.168.1.102/zouqiongjun/big-data-web.git。
客戶自定義需求單獨作為子應用項目,代碼倉庫位址:http://192.168.1.102/zouqiongjun/zibo-custom-web.git。
sass-base-web:主倉庫,主要存放一些批量操作的腳本,用于聚合管理倉庫和一鍵編譯、一鍵部署。
倉庫代碼結構如下圖所示:
big-data-web:大資料分析主應用
zibo-custom-web:客戶自定義需求,微應用倉庫
子應用可以獨立運作,但是目前子應用是直接嵌套在主應用的main内容區域,是以暫時并沒有單獨提供左側菜單導航,後續如有需要可以擴充和補充此功能。
将普通的項目改造成 qiankun 主應用基座,需要進行三步操作:
(1) 建立微應用容器 - 用于承載微應用,渲染顯示微應用;
(2) 注冊微應用 - 設定微應用激活條件,微應用位址等等;
(3) 啟動 qiankun;
注意:由于big-data-web主應用的路由采用的是hash模式,是以子應用的路由也應該采用hash模式。
為了使用keepAlive緩存,這裡我們采用手動加載微應用的方式。
當微應用資訊注冊完之後,一旦浏覽器的 url 發生變化,便會自動觸發 qiankun 的比對邏輯,所有 activeRule 規則比對上的微應用就會被插入到指定的 container 中,同時依次調用微應用暴露出的生命周期鈎子。
在views目錄下,建立AppVueHash.vue,作為子應用的容器,代碼如下:
這個id屬性要唯一,最終子應用的内容将會挂載在這裡。
ContainerOther.vue代碼改造:
js代碼:
這裡的container屬性值,必須和AppVueHash.vue元件中的id值保持一緻。
根據url位址判斷是否是子應用,如果是子應用,則手動加載,否則隐藏子應用容器,隻加載主應用的router-view。
在ContainerOther第一次加載或路由變化時手動加載微應用:
監聽tab頁簽變化,關閉頁簽時,需要解除安裝子應用。
在 src 目錄新增檔案 public-path.js:
修改 index.html 中項目初始化的容器,不要使用 #app ,避免與其他的項目沖突,建議小駝峰寫法
修改入口檔案 main.js:
主要改動是:引入修改 publicPath 的檔案和 export 三個生命周期。
注意:
webpack 的 publicPath 值隻能在入口檔案修改,之是以單獨寫到一個檔案并在入口檔案最開始引入,是因為這樣做可以讓下面所有的代碼都能使用這個。
路由檔案需要 export 路由資料,而不是執行個體化的路由對象,路由的鈎子函數也需要移到入口檔案。
在 mount 生命周期,可以拿到父項目傳遞過來的資料,router 用于跳轉到主項目/其他子項目的路由,store 是父項目的執行個體化的 Vuex(也可以傳遞其他資料過來)。
修改打包配置 vue.config.js:
這裡主要就兩個配置,一個是允許跨域,另一個是打包成 umd 格式。為什麼要打包成 umd 格式呢?是為了讓 qiankun 拿到其 export 的生命周期函數。
注意: 這個 name 預設從 package.json 擷取,可以自定義,隻要和父項目注冊時的 name 保持一緻即可。
vue.config.js中
這裡我子應用項目編譯後會将打封包件打包到sass-base-web項目中的zibo-custom-web下。
路由動态加載
需要給子項目所有的路由都添加一個字首,子項目的路由跳轉如果之前使用的是 path 也需要修改,用 name 跳轉則不用。
avue-router.js:
qiankun 通過 initGlobalState: 定義全局狀态,并傳回通信方法,建議在主應用使用,微應用通過 props 擷取通信方法;
onGlobalStateChange: 在目前應用監聽全局狀态,有變更觸發 callback;
setGlobalState: 按一級屬性設定全局狀态,微應用中隻能修改已存在的一級屬性; 換句話說隻能修改主用于預先定義的屬性,後面添加的屬性無效。
官方例子:釋出-訂閱的設計模式:
主應用:
子應用:
如果主應用和子應用都是vue技術棧,可以在子應用中把資料傳遞給子應用,例如store。
props: { data: { store, router } },
實際開發中項目存儲在公司倉庫中,以 gitLab 為例, 當子應用一多,全部放在一個倉庫下面, 這時候就顯得很臃腫了,也很龐大,大大的增加了維護成本,和開發效率;
我們可以通過 sh 腳本, 初始隻需要克隆主倉庫代碼, 然後通過 sh 腳本去一鍵拉取所有子應用代碼。
這裡我将單獨建立一個用來編譯和打包的主倉庫項目sass-base-web,倉庫位址:http://192.168.1.102/zouqiongjun/sass-base-web.git。
項目根目錄下,建立 script/clone-all.sh 檔案,内容如下:
當我們啟動主項目的時候,如果想要使用所有子應用的功能,子應用,需要一個一個啟動,這樣無論是開發還是編譯都十分不便。
考慮到國内使用npm裝包可能會由于網絡的原因安裝失敗,可以讓npm使用國内淘寶鏡像。
執行指令:npm config set registry https://registry.npm.taobao.org。
在這個主倉庫項目裡面,我們隻需要安裝一個npm-run-all插件。
運作:yarn add npm-run-all -D或者npm i -D。
該項目下隻有一個package.json檔案,用于配置編譯和打包指令,代碼如下:
要執行yarn clone:all,需要用到bash,跳轉到sass-base-web目錄,右鍵滑鼠打開Git bash視窗,如下圖所示:
這樣當我們把各個代碼倉庫的代碼都拉取到sub-service這個目錄下面來,如下圖所示:
代碼拉取完成後, 緊接着就是下載下傳各個項目的依賴及運作。
運作npm run serve-all則可以自動執行package.json中配置的指令,這個指令最終會執行以下三個執行指令:
這樣就不需要我們自己一個一個單獨的去運作各個項目了。
總體運作步驟: 第一步 clone 主應用, 然後依次執行 yarn clone:all --> yarn install-all --> yarn start-all 即可運作整個項目。
build-all:可以編譯整個項目。
sub-service目錄,将其添加到.gitignore當中,因為在sass-base-web項目當中,我們隻需要配置和編譯及打包用,并不需要真正的将所有子應用的代碼都送出到sass-base-web項目中,各子應用都有自己私有的倉庫。
(1)所有的資源(圖檔/音視訊等)都應該放到 src 目錄,不要放在 public 或者static。資源放 src 目錄,會經過 webpack 處理,能統一注入 publicPath。否則在主項目中會404。
(2)避免 css 污染。元件内樣式的 css-scoped 是必須的。
(3)給 body 、 document 等綁定的事件,請在 unmount 周期清除
(4)謹慎使用 position:fixed
在父項目中,這個定位未必準确,應盡量避免使用,确有相對于浏覽器視窗定位需求,可以用 position: sticky,但是會有相容性問題(IE不支援)。
常見問題見官網:https://qiankun.umijs.org/zh/faq
主應用和微應用都是獨立開發和部署,即它們都屬于不同的倉庫和服務。
場景:主應用和微應用部署到同一個伺服器(同一個IP和端口)
如果伺服器數量有限,或不能跨域等原因需要把主應用和微應用部署到一起。通常的做法是主應用部署在一級目錄,微應用部署在二/三級目錄。
若微應用想部署在非根目錄,在微應用打包之前需要做兩件事:
必須配置 webpack 建構時的 publicPath 為目錄名稱,更多資訊請看 webpack 官方說明 和 vue-cli3 的官方說明。
history 路由的微應用需要設定 base ,值為目錄名稱,用于獨立通路時使用。
部署之後注意三點:
activeRule 不能和微應用的真實通路路徑一樣,否則在主應用頁面重新整理會直接變成微應用頁面。
微應用的真實通路路徑就是微應用的 entry,entry 可以為相對路徑。
微應用的 entry 路徑最後面的 / 不可省略,否則 publicPath 會設定錯誤,例如子項的通路路徑是 http://localhost:8080/app1,那麼 entry 就是 http://localhost:8080/app1/。
通過配置 nginx 端口到目錄的轉發。須要對外開放子利用對應的端口,将編譯好的利用檔案放到對應的配置目錄。
跳轉到sass-base-web目錄,執行npm run build-all,會自動執行所有build:開頭的指令:
zibo-custom-web目錄結構如下圖所示:
這裡是子應用和主應用部署在同一台伺服器上,且IP和端口相同,nginx不需要額外設定。
如果子應用和主應用部署在同一台伺服器上, 但是端口不同,需要修改vue.config.js中的outputDir,這個是打包編譯後代碼存放的路徑,這個不做配置,預設會将代碼編譯打包到目前根目錄下,并生成一個dist目錄用于存放編譯後的代碼,如下圖所示:
修改nginx.conf配置:
此處 nginx 主要作用是用于端口目錄轉發,并配置主應用通路子應用的跨域問題。
使用 docker 配置部署 nginx:
将編譯後的主應用以及子應用放到對應的資料卷挂載目錄即可,如主應用 /app/micro/portal。
同理,也需要将配置好的 nginx.conf 檔案放到指定的資料卷挂載目錄,使用 docker-compose up -d 啟動即可。
nginx 端口目錄轉發配置:
部署到生産,需要修改big-data-web/public/util/config.js中的VUE_APP_ZIBO_CUSTOM_URL配置項:
這裡config.js是為了配置檔案外置,不需要編譯。
盡管qiankun架構支援各個子應用使用不同的技術架構,但是都需要子應用做相應的改造,而且在其它技術棧中總是會時不時的出現各種難以預料的錯誤,一旦出現問題,都需要我們去改造代碼。是以如果都是vue技術棧,建議使用qiankun做微前端。
如果是第三方公司的項目接入進來,由于他們的代碼不受我們控制,需要酌情考慮是否用iframe。
參考文獻:qiankun 微前端方案實踐及總結
部落格位址:
http://www.cnblogs.com/jiekzou/
部落格版權:
本文以學習、研究和分享為主,歡迎轉載,但必須在文章頁面明顯位置給出原文連接配接。
如果文中有不妥或者錯誤的地方還望高手的你指出,以免誤人子弟。如果覺得本文對你有所幫助不如【推薦】一下!如果你有更好的建議,不如留言一起讨論,共同進步!
再次感謝您耐心的讀完本篇文章。
其它:
.net-QQ群4:612347965
java-QQ群:805741535
H5-QQ群:773766020
我的拙作《ASP.NET MVC企業級實戰》《H5+移動應用實戰開發》
《Vue.js 2.x實踐指南》
《JavaScript實用教程 》
《Node+MongoDB+React 項目實戰開發》
已經出版,希望大家多多支援!