天天看點

如何在應用架構中設計微前端方案

微前端在 2016 年 ThoughtWorks 的一個技術雷達上面提出後,不斷有團隊嘗試将單體的前端 web 應用按不同次元進行拆分或者組合,再聚合到一個整體的應用架構下面。無論從系統體驗優化還是技術架構更新的角度,都對微前端的方案提出了各種高要求。本文将圍繞 icestark 對于不同場景的思考和設計,來嘗試給出解決方案。

NO.1

場景分析

如何在應用架構中設計微前端方案

在聚焦希望引入微前端技術架構的場景上,不難發現,以下的兩類場景的訴求會相對強烈:1. 工作台的場景,基于産品體驗的緯度 2. 大型單體應用,這種場景更側重于想從技術次元進行優化,能系統可持續的疊代發展

工作台場景

在工作台場景,從産品側的訴求來看,希望跨系統的操作能夠更加簡易,能夠帶來系統操作和體驗的一緻性;而從技術架構的角度,各個獨立的系統缺乏統一的管控手段,許多能力都在重複建設。

大型單體應用

面向大型單體應用的場景,也就是我們常見的巨石應用,随着業務需求的疊代,系統的複雜度直線上升。從直覺的感受來講,這個應用建構的速度越來越慢,生成的 bundle 大小越來越大。日常的調試開發體驗也收到一定程度的影響。從另一方面來看,伴随着業務功能體量的提升,也導緻了開發和協作成本的上升,常見的問題就是局部技術架構更新會變得非常困難,業務的擴充和其他業務能力的接入都會對單體 SPA 架構帶來要求。

NO.2

技術選型

如何在應用架構中設計微前端方案

對于應用架構的設計上,除了微前端的技術架構外,還有幾種場景的技術選型。

SPA/MPA

巨石應用也就是 SPA/MPA 的技術架構。這裡想單獨提一下,針對産品體驗的場景,一個簡單的 SPA 應用、肯定是能夠給到一個整體的系統體驗,并且能夠管控系統的技術複雜度。但如果系統越來越臃腫,就會遇到上面到的技術緯度的問題。SPA/MPA 是前端技術架構中最為常見的,對于一個獨立的系統它能在整體體驗和技術複雜度上做到很好的管控。但是為了防止這個項目後續發展成一個上古項目,無論是在功能疊代還是公用子產品的管理上都會提出很高的要求。不然随着系統的臃腫,系統健壯性和可持續疊代的複雜度問題都會随之而來。

iframe

iframe 在微前端方案流行前,它其實是一個比較好的解決方案。不管是一些二方或是三方的接入,它都能夠很好地滿足需求。但它存在一個緻命的問題,就是使用者體驗。舉個常見的問題,iframe 如果不去做一些特殊處理,嵌入的頁面雙滾動條、路由無法同步、頁面内部存在彈出遮罩互動等問題都是體驗緯度上的硬傷。

架構元件

架構元件,簡單來講就是跨系統複用的公共元件。通常将一些通用的頭部或吊頂中常用的邏輯封裝成一個元件,然後以 npm 的形式進行維護,通過這種方式能夠非常友善的将複用邏輯 / UI 提供出去。但本質上沒有解決去解決技術架構持續更新和多系統使用者體驗優化的問題。

微前端

微前端技術架構雖然會映入一些技術的複雜度,但基于上述兩種核心場景能夠從體驗和效率維護去尋找一個平衡點,讓前端的協作模式發生改變,功能子產品拆分後獨立開發,獨立部署,最終再內建到一個系統當中。

NO.3

微前端架構

如何在應用架構中設計微前端方案

在應用架構中接入微前端,核心需要處理架構應用和微應用之前的關系。icestark 将微前端方案中需要處理的技術細節進行屏蔽。架構應用去不用關心路由處理邏輯或者接入微應用的處理,隻需要完成微應用的配置及其應用上的一些業務邏輯,比如鑒權、應用埋點等業務邏輯即可。接下來也将針對 icestark 内部技術架構的設計進行分享。

核心概念

如何在應用架構中設計微前端方案

icestark 裡面引入的核心概念,主要兩個點:架構應用和微應用。

  • 架構應用就負責整體的 Layout 跟微應用配置與注冊渲染。架構應用通常會有的一個通用的頭部 Header,側邊欄SiderBar,除了 Layout 之外,還需要配置微應用的資訊,配置中會包含微應用的核心資訊,比如資源 url 和基準路由。
  • 微應用它其實就是按業務次元拆分開來的一些應用,通常來講它可能就是一個 SPA 應用,并且會包含至少一到多個頁面或路由。

微應用注冊

架構應用通過 ​

​icestark​

​​ 提供的 ​

​AppRouter​

​ 元件可以快速地完成微應用的挂載

<AppRouter>
  <AppRoute
    path={['/', '/message', '/about']}
    title="通用頁面"
    url={['//unpkg.com/icestark-child-common/build/js/index.js', '//unpkg.com/icestark-child-common/build/css/index.css']}
   />
  <AppRoute
    path={['/', '/message', '/about']}
    title="通用頁面"
    entry="https://ice.alicdn.com/icestark/child-common-angular/index.html"
  />
</AppRouter>      

核心配置資訊中 path 代表基準路由,聲明了通路路由位址是對應微應用将會被加載;url資訊代表了應用的 bundle 資源;除此之外,通過 entry 的方式可以把 html 整體引入,不再需要關心頁面中加載的 js 資源和 css 樣式。

工作流程

如何在應用架構中設計微前端方案

引入微前端架構後的工作流程可以從兩個方面發生變化

  • 微應用的開發模式。微應用開發有獨立的倉庫,獨立的開發、測試、布署流程。開發測試部署完之後,将應用的釋出産物統一注冊到架構應用裡面,這些産物可能是 JS bundle 或 html 資源。
  • 架構應用的整體流程,架構應用會維護微應用的注冊資訊。使用者在通路系統的時候,根據它之前注冊的路由資訊,它能夠精确地比對到目前需要加載的應用資訊,根據相應的資訊去加載應用的資源并最終渲染應用。使用者點選觸發跳轉的時候,如果路由變化觸發的是一個内部應用跳轉,那應用将會直接根據應用内部的路由邏輯渲染頁面。如果涉及到一些跨應用的跳轉,則又重新回到了上面路由的查找流程當中。

路由規則

微應用能按照 SPA 體驗根據路由的變化進行加載,取決為 icestark 内部路由管理的設計。

如何在應用架構中設計微前端方案

icestark 裡面的路由規則非常簡單,接觸過 react-router 的開發者不難發現兩者在配置上其實是有很多相似的地方,比如 path、exact 的配置規則。當通路架構應用頁面時,icestark 内部會去做一個路由的分發。如上圖中注冊的三個微應用配置:

  • 通路 /seller 路由時,比對到了第一個注冊資訊
  • 通路 /data 或者 /message 時,比對到第二個注冊資訊
  • 通路 /seller/a 的時候,比對到的是第三個路由
第一個注冊配置中設定了 excat 屬性。隻有在精準比對 ​

​/seller​

​ 路由的時候才會比對到第一份注冊資訊

兜底路由

如果在微應用架構裡面去設定了 path 為 ​

​/​

​ 的一個微應用,那它将整個系統的一個兜底路由,所有不比對已注冊的路由配置都會由兜底路由進行渲染。兜底路由一般情況都會用來渲染通用頁面,比如跟架構應用有比較強的耦合頁面,比如登陸頁面, 404 頁面或者說登出的頁面。是以實踐上面我們也将兜底路由作為架構應用自身路由的渲染。

路由劫持

為了能夠讓 icestark 響應頁面路由的變化,并對相應的微應用進行加載,icestark 對兩類路由事件進行了劫持:

  • history API 中的 popstate 和 hashChange
  • window 上的路由事件 pushState 和 replaceState(通常在浏覽器上進行前進後退操作的時候會觸發)。
如何在應用架構中設計微前端方案

一旦應用間發生跳轉,通過上述事件的劫持能夠拿到對應的路由資訊,再根據路由的比對規則來決定哪個微應用進行挂載。一個微應用可能會有多個路由設定,如果在沒有發生應用間跳轉的情況下,由于比對到的是目前的微應用,是以不會再次加載資源,内部路由跳轉邏輯則根據微應用自身路由配置決定渲染。

路由劫持發生的時機在整個微前端配置初始化階段,即 ​

​AppRouter​

​​ 的挂載,一旦 ​

​AppRouter​

​ 解除安裝對應的劫持也将會移除

應用通信

從微前端的設計原則上來說,并不希望微應用太多地去依賴架構應用或者其它微應用提供的能力。這樣在微應用獨立開發的時候需要額外建立一個架構應用環境,不利于技術架構的結偶和維護。但基于一些輕量的應用場景,比如通過通信機制讓架構應用和微應用的多語言設定保持一緻,一旦多語言設定發生切換,微應用能夠監聽到這個變化。icestark 提供了一個應用通信機制,在實際開發過程中推薦輕量的去使用,不要耦合過多的業務邏輯

如何在應用架構中設計微前端方案

​@ice/stark-data​

​ 中提供了應用通信的能力,核心的實作是一個 EventBus 的機制,架構應用跟微應用之間的通訊,以 window 這樣一個全局變量作為橋梁。這樣不管是微應用添加的事件或資料,還是架構應用添加的事件或資料都可以通路到。

微前端隔離

icestark 在設計隔離的方案時候,有兩個基礎原則:

  • 首先認為研發體驗是高于隔離的。假如說我引入一個完美的隔離方案,但它需要讓我去做很多額外的處理邏輯,那這個方案肯定是不被接受的。無論是改造成本還是開發體驗效果都會受到非常大的影響。
  • 其次是二方的場景要高于三方的場景。因為大多數微前端的應用場景,都是二方場景,一個統一的獨立的系統,很少碰到要去接一個完全不受控的三方産品。而二方場景的接入其邏輯和安全性都是可控的。
如何在應用架構中設計微前端方案

基于上述的兩個因素,在實踐過程中并不一定說是要實作了一個完美的隔離方案之後,然後才在微前端技術架構中去使用。如果目前方案能夠滿足基礎的業務的訴求,那就讓這個方案在業務中使用。

通常微前端中的隔離場涉及兩個方面:一個就是 CSS 的隔離,另一個就是 JS 的隔離。

樣式隔離

樣式隔離上面,推薦的方案是基于一些約定的隔離,用低成本的隔離方式,讓樣式之間不會互相影響。

如何在應用架構中設計微前端方案

樣式隔離主要分兩類:

  • 開發者自己業務代碼中的樣式隔離,業務代碼的隔離推薦通過 CSSModule 的方式,能夠自動生成 hash 字尾的樣式名,基于每個不同的應用建構出來的樣式,在天然上就能夠做到隔離。
  • 基礎元件樣式隔離,大多數社群的一些基礎元件,在設計上都考慮到樣式字首的替換。基礎元件能夠支援 CSS prefix 的方式,可以為所有樣式添加一個字首,在實踐過程中将架構應用的字首和微應用字首進行區分,來完成樣式的隔離。如果有不支援 CSS prefix 的樣式,我們也能夠借助社群 PostCSS 的能力給元件樣式加上 namespace,架構應用跟微應用通過不同的 namespace 進行樣式隔離。

shadow DOM 的方案

為什麼我們沒有直接去使用 shadow DOM,本質的原因還是 shadow DOM 的方案還不夠開箱即用。目前 shadow DOM 對于業務上的改造還是有一定成本和問題:

  • 比如如果使用的基礎依賴的元件庫,并沒有設計讓Dialog 等彈出層在指定的 dom 節點中插入結構的話,彈出層都是會逃離你目前的 shadow DOM。逃離之後,它就是一個無樣式的彈框。這種無樣式的彈框對于業務上來說是不可以接受的,是以彈框邏輯需要去做一些相容,更甚至需要對底層元件去做改造。
  • 在 React 場景下,shadow DOM 的使用會涉及到事件機制的問題,因為React 的事件機制是代理到 document 的,但基于 shadow DOM 處理的話,它可能會阻斷事件到它的 host 層,也就是你渲染 shadow DOM 的那一層。雖然說社群也有對應的包去做一些相容處理,但它對業務上來說還是會有一些實作成本。
  • 除此之外還包括其它的問題。比如 CSS @font-face,或者說一些字型屬性,svg 都會有一個不相容的場景。

雖然 shadow DOM 問題還是比較多,但是接下來 icestark 也會這方面繼續探索、逐漸完善,争取提供出一個開箱即用的方案,達到啟用 shadow DOM 能夠沒有太大改造的成本。

腳本隔離

如何在應用架構中設計微前端方案

多個應用的 bundle 多次執行的時候很容易對全局變量造成污染,特别是代碼中出現對于 window 全局變量的依賴。icestark 中通過 proxy 的沙箱機制實作了腳本的隔離。Prxoy 沙箱的基本原理是通過 with + new Function 的形式阻斷代碼中對于 window 的直接通路,并通過 Proxy 的方式攔截對于 window 變量的通路和寫入,沙箱的隔離使代碼不能直接通路到 window 對象,通過ES6 的新特性 Proxy 可以定制 get/set 的邏輯,這樣就能對 window 上的一些全局變量變化進行快照記錄,以便微應用切換的時候進行恢複。另外像一些應用初始化時,會在 window 上面設定 setTimeout、setInterval,如果在解除安裝階段沒有很好的處理,将會影響到下一個挂載微應用的執行。是以在沙箱中針對這類方法進行了特殊處理,在沙箱挂載前對相應的方法進行劫持,在解除安裝的時候,再對它進行恢複。

三方隔離

如何在應用架構中設計微前端方案

對于不授信的三方最簡單最安全的隔離方式是 iframe。在 icestark 中可以簡單定義好基準路由 path ,再通過自定義渲染的方法 render 将 iframe 相關的内容渲染出來。

微子產品能力

微子產品的能力其實是對微前端方案的一個補充,通常一個微子產品并不會耦合路由,在一個頁面中可以随意組合和挂載。它的應用場景主要有以下兩種模式:

  • 子產品共存問題:常見為微前端的技術體系下面去實作一個多 tab 方案。設想一下,最低成本的一個解決方案會是什麼樣的,是不是讓一個子產品能夠在不同的位置正常渲染就行了?
  • 子產品動态組合:一個頁面裡面會有資訊子產品,表單子產品,以及清單子產品。在一些對外輸出複用的場景中,如果直接接入整個頁面,其通用性并不是特别強,但如果各個子產品能夠進行自由組合,就可以按需組合出不同需求的頁面

微子產品架構

如何在應用架構中設計微前端方案

icestark 對于微子產品的應用場景上會有一個明确的定義,微子產品其實是不會再去耦合路由的。之前提到的微應用的内部基本上是一個 SPA 它至少有一個路由或者是一個頁面,但是微子產品的使用上我們希望盡量簡單,因為一旦多個子產品都大量耦合路由的話,這會使路由處理變得複雜。在子產品的标準上面,微子產品是以 UMD 的方式直接打包,通過這種标準模式打包,即便是以 npm 包的形式也可以正常使用。在微子產品内部除了預設導出子產品方法外,還需要定義挂載(mount)和解除安裝(unmount)的生命周期。微子產品的應用場景其實是對微應用的一個補充,它更适用于更加細粒度的功能拆分和動态搭建的場景。

微子產品挂載

根據子產品資源在執行的位置渲染子產品:

import { MicroModule } from '@ice/stark-module';

const App = () => {
  const moduleInfo = {
    name: 'moduleName',
    url: 'https://localhost/module.js',
  }
  return <MicroModule moduleInfo={moduleInfo} />;
}      

通過元件方式的挂載,将微子產品渲染至指定位置,而上層的切換展示邏輯均由業務進行控制。

NO.4

總結

微前端的技術架構,它給大型單體應用場景和工作台場景帶來技術架構優化的方案,通過引入微前端的技術架構去解決目前系統遇到的問題和瓶頸,也能給上古代碼找到一個重新煥發生機的機會。同時結合微子產品的方案,讓基于标準化子產品的上層方案有更大的想象空間,未來面向的場景和方案也将更加豐富。

關注「前端加加」, 第一時間擷取優質文章.      

繼續閱讀