如何解構單體前端應用——微前端應用的微服務式拆分

刷新頁面?路由拆分?No,動態加載組件。

本文分爲如下四部分:javascript

  • 前端微服務化思想介紹
  • 微前端的設計理念
  • 實戰微前端架構設計
  • 基於 Mooa 進行前端微服務化

前端微服化

對於前端微服化來講,有這麼一些方案:css

  • Web Component 顯然能夠一個很優秀的基礎架構。然而,咱們並不可能去大量地複寫已有的應用。
  • iFrame。你是說真的嗎?
  • 另一個微前端框架 Single-SPA,顯然是一個更好的方式。然而,它並不是 Production Ready。
  • 經過路由來切分應用,而這個跳轉會影響用戶體驗。
  • 等等。

所以,當咱們考慮前端微服務化的時候,咱們但願:html

  • 獨立部署
  • 獨立開發
  • 技術無關
  • 不影響用戶體驗

獨立開發

在過去的幾星期裏,我花費了大量的時間在學習 Single-SPA 的代碼。可是,我發現它在開發和部署上真的太麻煩了,徹底達不到獨立部署地標準。按 Single-SPA 的設計,我須要在入口文件中聲名個人應用,而後才能去構建:前端

declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), pathPrefix('/inferno'));

同時,在個人應用裏,我還須要去指定個人生命週期。這就意味着,當我開發了一個新的應用時,必須更新兩份代碼:主工程和應用。這時咱們還很可能在同一個源碼裏工做。 java

當出現多個團隊的時候,在同一份源碼裏工做,顯然變得至關的不可靠——好比說,對方團隊使用的是 Tab,而咱們使用的是 2 個空格,隔壁的老王用的是 4 個空格。git

獨立部署

一個單體的前端應用最大的問題是,構建出來的 js、css 文件至關的巨大。而微前端則意味着,這個文件被獨立地拆分紅多個文件,它們即可以獨立去部署應用。github

咱們真的須要技術無關嗎?

等等,咱們是否真的須要技術無關?若是咱們不須要技術無關的話,微前端問題就很容易解決了。json

事實上,對於大部分的公司和團隊來講,技術無關只是一個無關痛癢的話術。當一家公司的幾個創始人使用了 Java,那麼極有可能在將來的選型上繼續使用 Java。除非,一些額外的服務來使用 Python 來實現人工智能。所以,在大部分的狀況下,仍然是技術棧惟一。bootstrap

對於前端項目來講,更是如此:一個部門裏基本上只會選用一個框架。前端框架

因而,咱們選擇了 Angular。

不影響用戶體驗

使用路由跳轉來進行前端微服務化,是一種很簡單、高效的切分方式。然而,路由跳轉地過程當中,會有一個白屏的過程。在這個過程當中,跳轉前的應用和將要跳轉的應用,都失去了對頁面的控制權。若是這個應用出了問題,那麼用戶就會一臉懵逼。

理想的狀況下,它應該能夠被控制。

微前端的設計理念

設計理念一:中心化路由

互聯網本質是去中心化的嗎?不,DNS 決定了它不是。TAB,決定了它不是。

微服務從本質上來講,它應該是去中心化的。可是,它又不能是徹底的去中心化。對於一個微服務來講,它須要一個服務註冊中心

服務提供方要註冊通告服務地址,服務的調用方要能發現目標服務。

對於一個前端應用來講,這個東西就是路由。

Menu

從頁面上來講,只有咱們在網頁上添加一個菜單連接,用戶才能知道某個頁面是可使用的。

404

而從代碼上來講,那就是咱們須要有一個地方來管理咱們的應用:**發現存在哪些應用,哪一個應用使用哪一個路由。

管理好咱們的路由,實際上就是管理好咱們的應用

設計理念二:標識化應用

在設計一個微前端框架的時候,爲每一個項目取一個名字的問題糾結了我好久——怎麼去規範化這個東西。直到,我再一次想到了康威定律:

系統設計(產品結構等同組織形式,每一個設計系統的組織,其產生的設計等同於組織之間的溝通結構。

康威定律

換句人話說,就是同一個組織下,不可能有兩個項目的名稱是同樣的。

因此,這個問題很簡單就解決了。

設計理念三:生命週期

Single-SPA 設計了一個基本的生命週期(雖然它沒有統一管理),它包含了五種狀態:

  • load,決定加載哪一個應用,並綁定生命週期
  • bootstrap,獲取靜態資源
  • mount,安裝應用,如建立 DOM 節點
  • unload,刪除應用的生命週期
  • unmount,卸載應用,如刪除 DOM 節點

因而,我在設計上基本上沿用了這個生命週期。顯然,諸如 load 之類對於個人設計是多餘的。

設計理念四:獨立部署與配置自動化

從某種意義上來講,整個每系統是圍繞着應用配置進行的。若是應用的配置能自動化,那麼整個系統就自動化。

當咱們只開發一個新的組件,那麼咱們只須要更新咱們的組件,並更新配置便可。而這個配置自己也應該是能自動生成的。

實戰微前端架構設計

基於以上的前提,系統的工做流程以下所示:

系統工做流

總體的工程流程以下所示:

  1. 主工程在運行的時候,會去服務器獲取最新的應用配置。
  2. 主工程在獲取到配置後,將一一建立應用,併爲應用綁定生命週期。
  3. 當主工程監測到路由變化的時候,將尋找是否有對應的路由匹配到應用。
  4. 當匹配對對應應用時,則加載相應的應用。

故而,其對應的架構以下圖所示:

Architecture

獨立部署與配置自動化

咱們作的部署策略以下:咱們的應用使用的配置文件叫 apps.json,由主工程去獲取這個配置。每次部署的時候,咱們只須要將 apps.json 指向最新的配置文件便可。配置的文件類以下所示:

  1. 96a7907e5488b6bb.json
  2. 6ff3bfaaa2cd39ea.json
  3. dcd074685c97ab9b.json

一個應用的配置以下所示:

{
  "name": "help",
  "selector": "help-root",
  "baseScriptUrl": "/assets/help",
  "styles": [
    "styles.bundle.css"
  ],
  "prefix": "help",
  "scripts": [
    "inline.bundle.js",
    "polyfills.bundle.js",
    "main.bundle.js"
  ]
}

這裏的 selector 對應於應用所須要的 DOM 節點,prefix 則是用於 URL 路由上。這些都是自動從 index.html 文件和 package.json 中獲取生成的。

應用間路由——事件

因爲如今的應用變成了兩部分:主工程和應用部分。就會出現一個問題:只有一個工程能捕獲路由變化。當由主工程去改變應用的二級路由時,就沒法有效地傳達到子應用。在這時,只能經過事件的方式去通知子應用,子應用也須要監測是不是當前應用的路由。

if (event.detail.app.name === appName) {
  let urlPrefix = 'app'
  if (urlPrefix) {
    urlPrefix = `/${window.mooa.option.urlPrefix}/`
  }
  router.navigate([event.detail.url.replace(urlPrefix + appName, '')])
}

類似的,當咱們須要從應用 A 跳轉到應用 B 時,咱們也須要這樣的一個機制:

window.addEventListener('mooa.routing.navigate', function(event: CustomEvent) {
  const opts = event.detail
  if (opts) {
    navigateAppByName(opts)
  }
})

剩下的諸如 Loading 動畫也是相似的。

使用 Mooa 進行

So,咱們就有了前端微服務框架 Mooa。它基於 single-spa && single-spa-angular-cli,而且符合以上的設計思想。

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

對於主工程而言,只須要如下的幾行代碼就能夠完成上面的功能:

http.get<any[]>('/assets/apps.json')
  .subscribe(data => {
    data.map((config) => {
      that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
    });
    this.mooa.start();
  });

this.router.events.subscribe((event: any) => {
  if (event instanceof NavigationEnd) {
    that.mooa.reRouter(event);
  }
});

並添加一個對應的子應用路由:

{
  path: 'app/:appName/:route',
  component: HomeComponent
}

則如上所述的四個步驟。

對於子工程而言,則只須要一個對應的 Hook 操做:

mooaPlatform.mount('help').then((opts) => {
  platformBrowserDynamic().bootstrapModule(AppModule).then((module) => {
    opts['attachUnmount'](module);
  });
});

並設置好對應的 base_href:

providers: [
  {provide: APP_BASE_HREF, useValue: mooaPlatform.appBase()},
]

嗯,就是這麼簡單。DEMO 視頻以下:

Demo 地址見:http://mooa.phodal.com/

GitHub 示例:https://github.com/phodal/mooa

相關文章
相關標籤/搜索