1. 程式人生 > >新輪子 Mooa:使用 mooa 微服務化 Angular 應用

新輪子 Mooa:使用 mooa 微服務化 Angular 應用

Angular 基於 Component 的思想,可以讓其在一個頁面上同時執行多個 Angular 應用;可以在一個 DOM 節點下,存在多個 Angular 應用,即類似於下面的形式:

  1. <app-home _nghost-c3=""ng-version="5.2.8">

  2. <app-help _nghost-c0=""ng-version="5.2.2"style="display:block;"><div _ngcontent-c0=""></div></app-help>

  3. <app-app1 _nghost-c0=""ng-version=

    "5.2.3"style="display:none;"><nav _ngcontent-c0=""class="navbar"></div></app-app1>

  4. <app-app2 _nghost-c0=""ng-version="5.2.2"style="display:none;"><nav _ngcontent-c0=""class="navbar"></div></app-app2>

  5. </app-home>

可這一樣一來,難免需要做以下的一些額外的工作:

  • 建立子應用專案模板,以統一 Angular 版本

  • 構建時,刪除子應用的依賴

  • 修改第三方模組

而在這其中最麻煩的就是第三方模組衝突問題。思來想去,在三月中旬,我在 Mooa 中添加了一個 iframe 模式。

Mooa

Mooa 是一個為 Angular 服務的微前端框架,它是一個基於 single-spa,針對 IE 10 及 IFRAME 優化的微前端解決方案。

Mooa 框架與 Single-SPA 不一樣的是,Mooa 採用的是 Master-Slave 架構,即主-從式設計。

對於 Web 頁面來說,它可以同時存在兩個到多個的 Angular 應用:其中的一個 Angular 應用作為主工程存在,剩下的則是子應用模組。

  • 主工程,負責載入其它應用,及使用者許可權管理等核心控制功能。

  • 子應用,負責不同模組的具體業務程式碼。

在這種模式下,則由主工程來控制整個系統的行為,子應用則做出一些對應的響應。

iframe 微服務架構設計

640?wx_fmt=png

Mooa 架構

主要過程如下:

  • 主工程在執行的時候,會去伺服器獲取最新的應用配置。

  • 主工程在獲取到配置後,將一一建立應用,併為應用繫結生命週期。

  • 當主工程監測到路由變化的時候,將尋找是否有對應的路由匹配到應用。

  • 當匹配對對應應用時,則建立或顯示相應應用的 iframe,並隱藏其它子應用的 iframe。

其載入形式與之前的 Component 模式並沒有太大的區別:

640?wx_fmt=jpeg
Mooa Component 載入

而為了控制不同的 iframe 需要做到這麼幾件事:

  1. 為不同的子應用分配 ID

  2. 在子應用中進行 hook,以通知主應用:子應用已載入

  3. 在子應用中建立對應的事件監聽,來響應主應用的 URL 變化事件

  4. 在主應用中監聽子程式的路由跳轉等需求

因為大部分的程式碼可以與之前的 Mooa 複用,於是我便在 Mooa 中實現了相應的功能。

微前端框架 Mooa 的特製 iframe 模式

iframe 可以建立一個全新的獨立的宿主環境,這意味著我們的 Angular 應用之間可以相互獨立執行,我們唯一要做的是:建立一個通訊機制

它可以不修改子應用程式碼的情況下,可以直接使用。與此同時,它在一般的 iframe 模式進行了優化。使用普通的 iframe 模式,意味著:我們需要載入大量的重複元件,即使經過 Tree-Shaking 優化,它也將帶來大量的重複內容。如果子應用過多,那麼它在初始化應用的時候,體驗可能就沒有那麼友好。但是與此相比,在初始化應用的時候,載入所有的依賴在主程式上,也不是一種很友好的體驗。

於是,我就在想能不能建立一個更友好地 IFrame 模式,在裡面對應用及依賴進行處理。如下,就是最後生成的頁面的 iframe 程式碼:

  1. <app-home _nghost-c2=""ng-version="5.2.8">

  2. <iframeframeborder=""width="100%"height="100%"src="http://localhost:4200/assets/iframe.html"id="help_206547"style="display:block;"></iframe>

  3. <iframeframeborder=""width="100%"height="100%"src="http://localhost:4200/assets/iframe.html"id="app_235458 style="display:none;"></iframe>

  4. </app-home>

對,兩個 iframe 的 src 是一樣的,但是它表現出來的確實是兩個不同的 iframe 應用。那個 iframe.html 裡面其實是沒有內容的:

  1. <!doctype html>

  2. <htmllang="en">

  3. <head>

  4. <metacharset="utf-8">

  5. <title>App1</title>

  6. <basehref="/">

  7. <metaname="viewport"content="width=device-width,initial-scale=1">

  8. <linkrel="icon"type="image/x-icon"href="favicon.ico">

  9. </head>

  10. <body>

  11. </body>

  12. </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 來發布事件,如下:

  1. let iframeEl: any = document.getElementById(iframeId)

  2. if(iframeEl && iframeEl.contentWindow){

  3.  iframeEl.contentWindow.mooa.option = window.mooa.option

  4.  iframeEl.contentWindow.dispatchEvent(

  5. newCustomEvent(MOOA_EVENT.ROUTING_CHANGE,{ detail: eventArgs })

  6. )

  7. }

這樣,子應用就不需要修改程式碼,就可以直接接收對應的事件響應。

監聽子應用事件

由於,我們也希望能直接在主工程中處理子程式的事件,並且不修改原有的程式碼。因此,我們也使用同樣的方式來在子應用中監聽主應用的事件:

  1. iframeEl.contentWindow.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE,function(event:CustomEvent){

  2. if(event.detail){

  3.  navigateAppByName(event.detail)

  4. }

  5. })

示例

同樣的我們仍以 Mooa 框架作為示例,我們只需要在建立 mooa 例項時,配置使用 iframe 模式即可:

  1. this.mooa =newMooa({

  2.  mode:'iframe',

  3.  debug:false,

  4.  parentElement:'app-home',

  5.  urlPrefix:'app',

  6.  switchMode:'coexist',

  7.  preload:true,

  8.  includeZone:true

  9. });

  10. ...

  11. that.mooa.registerApplicationByLink('help','/assets/help', mooaRouter.matchRoute('help'));

  12. that.mooa.registerApplicationByLink('app1','/assets/app1', mooaRouter.matchRoute('app1'));

  13. this.mooa.start();

  14. ...

  15. this.router.events.subscribe((event: any)=>{

  16. if(eventinstanceofNavigationEnd){

  17.  that.mooa.reRouter(event);

  18. }

  19. });

子程式則直接使用:https://github.com/phodal/mooa-boilerplate 就可以了。

640?wx_fmt=png

GitHub 地址:https://github.com/phodal/mooa