新輪子 Mooa:使用 mooa 微服務化 Angular 應用
Angular 基於 Component 的思想,可以讓其在一個頁面上同時執行多個 Angular 應用;可以在一個 DOM 節點下,存在多個 Angular 應用,即類似於下面的形式:
<app-home _nghost-c3=""ng-version="5.2.8">
<app-help _nghost-c0=""ng-version="5.2.2"style="display:block;"><div _ngcontent-c0=""></div></app-help>
<app-app1 _nghost-c0=""ng-version=
<app-app2 _nghost-c0=""ng-version="5.2.2"style="display:none;"><nav _ngcontent-c0=""class="navbar"></div></app-app2>
</app-home>
可這一樣一來,難免需要做以下的一些額外的工作:
建立子應用專案模板,以統一 Angular 版本
構建時,刪除子應用的依賴
修改第三方模組
而在這其中最麻煩的就是第三方模組衝突問題。思來想去,在三月中旬,我在 Mooa 中添加了一個 iframe 模式。
Mooa
Mooa 是一個為 Angular 服務的微前端框架,它是一個基於 single-spa,針對 IE 10 及 IFRAME 優化的微前端解決方案。
Mooa 框架與 Single-SPA 不一樣的是,Mooa 採用的是 Master-Slave 架構,即主-從式設計。
對於 Web 頁面來說,它可以同時存在兩個到多個的 Angular 應用:其中的一個 Angular 應用作為主工程存在,剩下的則是子應用模組。
主工程,負責載入其它應用,及使用者許可權管理等核心控制功能。
子應用,負責不同模組的具體業務程式碼。
在這種模式下,則由主工程來控制整個系統的行為,子應用則做出一些對應的響應。
iframe 微服務架構設計
主要過程如下:
主工程在執行的時候,會去伺服器獲取最新的應用配置。
主工程在獲取到配置後,將一一建立應用,併為應用繫結生命週期。
當主工程監測到路由變化的時候,將尋找是否有對應的路由匹配到應用。
當匹配對對應應用時,則建立或顯示相應應用的 iframe,並隱藏其它子應用的 iframe。
其載入形式與之前的 Component 模式並沒有太大的區別:
而為了控制不同的 iframe 需要做到這麼幾件事:
為不同的子應用分配 ID
在子應用中進行 hook,以通知主應用:子應用已載入
在子應用中建立對應的事件監聽,來響應主應用的 URL 變化事件
在主應用中監聽子程式的路由跳轉等需求
因為大部分的程式碼可以與之前的 Mooa 複用,於是我便在 Mooa 中實現了相應的功能。
微前端框架 Mooa 的特製 iframe 模式
iframe 可以建立一個全新的獨立的宿主環境,這意味著我們的 Angular 應用之間可以相互獨立執行,我們唯一要做的是:建立一個通訊機制。
它可以不修改子應用程式碼的情況下,可以直接使用。與此同時,它在一般的 iframe 模式進行了優化。使用普通的 iframe 模式,意味著:我們需要載入大量的重複元件,即使經過 Tree-Shaking 優化,它也將帶來大量的重複內容。如果子應用過多,那麼它在初始化應用的時候,體驗可能就沒有那麼友好。但是與此相比,在初始化應用的時候,載入所有的依賴在主程式上,也不是一種很友好的體驗。
於是,我就在想能不能建立一個更友好地 IFrame 模式,在裡面對應用及依賴進行處理。如下,就是最後生成的頁面的 iframe 程式碼:
<app-home _nghost-c2=""ng-version="5.2.8">
<iframeframeborder=""width="100%"height="100%"src="http://localhost:4200/assets/iframe.html"id="help_206547"style="display:block;"></iframe>
<iframeframeborder=""width="100%"height="100%"src="http://localhost:4200/assets/iframe.html"id="app_235458 style="display:none;"></iframe>
</app-home>
對,兩個 iframe 的 src 是一樣的,但是它表現出來的確實是兩個不同的 iframe 應用。那個 iframe.html 裡面其實是沒有內容的:
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>App1</title>
<basehref="/">
<metaname="viewport"content="width=device-width,initial-scale=1">
<linkrel="icon"type="image/x-icon"href="favicon.ico">
</head>
<body>
</body>
</html>
(PS:詳細的程式碼可以見 https://github.com/phodal/mooa)
只是為了建立 iframe 的需要而存在的,對於一個 Angular 應用來說,是不是一個 iframe 的區別並不大。但是,對於我們而言,區別就大了。我們可以使用自己的方式來控制這個 IFrame,以及我們所要載入的內容。如:
共同 Style Guide 中的 CSS 樣式。如,在使用 iframe 整合時,移除不需要的
去除不需要重複載入的 JavaScript。如,打包時不需要的 zone.min.js、polyfill.js 等等
注意
:對於一些共用 UI 元件而言,仍然需要重複載入。這也就是 iframe 模式下的問題。
微前端框架 Mooa iframe 通訊機制
為了在主工程與子工程通訊,我們需要做到這麼一些事件策略:
釋出主應用事件
由於,我們使用 Mooa 來控制 iframe 載入。這就意味著我們可以通過 document.getElementById
來獲取到 iframe,隨後通過 iframeEl.contentWindow
來發布事件,如下:
let iframeEl: any = document.getElementById(iframeId)
if(iframeEl && iframeEl.contentWindow){
iframeEl.contentWindow.mooa.option = window.mooa.option
iframeEl.contentWindow.dispatchEvent(
newCustomEvent(MOOA_EVENT.ROUTING_CHANGE,{ detail: eventArgs })
)
}
這樣,子應用就不需要修改程式碼,就可以直接接收對應的事件響應。
監聽子應用事件
由於,我們也希望能直接在主工程中處理子程式的事件,並且不修改原有的程式碼。因此,我們也使用同樣的方式來在子應用中監聽主應用的事件:
iframeEl.contentWindow.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE,function(event:CustomEvent){
if(event.detail){
navigateAppByName(event.detail)
}
})
示例
同樣的我們仍以 Mooa 框架作為示例,我們只需要在建立 mooa 例項時,配置使用 iframe 模式即可:
this.mooa =newMooa({
mode:'iframe',
debug:false,
parentElement:'app-home',
urlPrefix:'app',
switchMode:'coexist',
preload:true,
includeZone:true
});
...
that.mooa.registerApplicationByLink('help','/assets/help', mooaRouter.matchRoute('help'));
that.mooa.registerApplicationByLink('app1','/assets/app1', mooaRouter.matchRoute('app1'));
this.mooa.start();
...
this.router.events.subscribe((event: any)=>{
if(eventinstanceofNavigationEnd){
that.mooa.reRouter(event);
}
});
子程式則直接使用:https://github.com/phodal/mooa-boilerplate 就可以了。
GitHub 地址:https://github.com/phodal/mooa