天天看點

生産可用:是時候來一個微前端架構了!

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

生産可用:是時候來一個微前端架構了!

阿裡妹導讀:随着前端越來越複雜,微前端的概念也越來越熱,那麼什麼是微前端?如何應用微前端來改進現有的前端架構?有沒有哪些成功的案例和實踐經驗?

本文将分享微前端的場景域在螞蟻落地時遇到的問題,通過實施一個标準的微前端架構,提出面臨的技術決策以及需要處理的技術細節,真正意義上幫助你建構一個生産可用的微前端架構系統。

微前端的場景域

在選擇一個微前端方案之前,常常需要思考這樣一個問題,我們為什麼需要微前端。通常對微前端的訴求有兩個方面,一是工程上的價值,二是産品上的價值。

對于工程上的價值,可以從一個三年陳的項目來看,如下所示, commit 的記錄顯示,第一次送出是 2016 年 8 月。

生産可用:是時候來一個微前端架構了!

依賴樹 dependencies:

生産可用:是時候來一個微前端架構了!

打包體積:

生産可用:是時候來一個微前端架構了!

雖然這個三年陳的項目看上去版本比較低,但仍然是相對主流的全家桶方案。這樣一個樂觀的項目,在真實的場景中經過三年的時間,也不實用了。因為開發的時間比較長,并且人員流動也比較大,會導緻一些祖傳的代碼出現,其次,在技術上不能及時的更新,導緻開發體驗變得很差,例如打包的時間就超過三分鐘。也有可能在不經意間依賴一些不相容的架構,導緻項目無法更新。種種原因,最後很有可能變成一個遺産項目。

對于産品體驗上的問題,例如下圖所示,要完成一個跳多個控制台任務,在過程中發現每個控制台視覺不統一、流程出現斷點以及重複加載或認證的問題導緻整個産品的體驗較差。

微前端的定義

Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks.

—— Micro Frontends。

以上是 Micro Frontends 網站對微前端的定義。意思是所謂微前端就是一種多個團隊之間可以使用不同技術建構一個現代化 web 的技術手段以及方法政策。其中的關鍵字是多團隊、采用不同的技術棧以及現代化的 web。微前端的思路繼承自微服務的思想。

生産可用:是時候來一個微前端架構了!

微前端的架構圖

如圖,其中上層為統一共享的拼接層,主要做一些基礎資訊的加載,和對來自不同團隊不同技術棧的用戶端在運作時動态組成一個完整的 SPA 應用,以及生命周期的排程和事件的管理。總之,微前端是将微服務概念做了一個很好的延伸和實作。

在具體實踐中,衡量一個微前端方案是否是可利用的,需要滿足以下幾個條件:

  • 技術棧無關性,不僅指子應用之間使用多個不同的架構,也指在使用同一個架構時,有可能在一個長的時間跨度下,由于架構的不相容的更新,導緻應用被鎖死的情況。
  • 開發、釋出及部署獨立,要求子應用和主應用做到工程上的解耦和獨立。
  • 應用隔離的能力,是指需要考慮如何不幹擾到原來子應用的開發模式和部署模式的情況下,做好運作時的樣式隔離、JS 隔離以及異常隔離等。

以上幾點是基于工程價值方面考慮的。此外,也需要動态組合的能力,是基于産品價值方面考慮的。

落地的關鍵問題

微前端架構中的技術選擇

生産可用:是時候來一個微前端架構了!

按架構類型區分,正常 web 應用的架構類型分為兩種,一種是 MPA,另一種是 SPA。如上圖所示為 2017 年各雲産品控制台架構調研,除了 google cloud 之外,大部分的雲廠商都使用 MPA 架構。MPA 的優點在于部署簡單,具備獨立開發和獨立部署的特性。但是,它的缺點是完成一個任務要跳到多個控制台,并且每個控制台又是重複重新整理的。而 SPA 能極大保證多個任務之間串聯的流暢性,但問題是通常一個 SPA 是一個技術棧的應用,很難共存多個技術棧方案的選型。SPA 和 MPA 都是微前端方案的基礎選型,但是也都存在各自的問題。

生産可用:是時候來一個微前端架構了!

單執行個體,一個運作時隻有一個 APP Actived

生産可用:是時候來一個微前端架構了!

多執行個體,一個運作同時有多個 APP Actived

按運作時特性區分,微前端包含兩個類别,一類是單執行個體,另一類是多執行個體。單執行個體場景如上圖中左側,通常是一個頁面級别的組合,例如一個運作時隻有一個 App 被激活。多執行個體場景如上圖右側,像一個元件或者是容器級别的應用,運作時可以做到多個應用被同時激活。這兩種模式都有自己适應的場景和優勢。微前端架構的核心訴求是實作能支援自由組合的微前端架構,将其他的 SPA 應用以及其他元件級别的應用自由的組合到平台中。那麼,如何選擇 SPA 和 MPA 以及單執行個體和多執行個體是一個問題,我們是否能探索出一種方案,将 SPA 和 MPA 工程上的特點結合起來,同時兼顧多執行個體和單執行個體運作時的場景來實作。

技術細節上的決策

為了實作上述的方案,在技術細節上的決策需要注意以下問題:

  • 如何做到子應用之間的技術無關。
  • 如何設計路由和應用導入。
  • 如何做到應用隔離。
  • 基礎應用之間資源的處理以及跨應用間通信的選擇。

對于如何做到子應用之間的技術無關問題,我們是通過協定來解決的。如下代碼所示的方式,就可以完成子應用的導入。如果子應用接入時做了一些架構上的耦合或者依賴一個具體實作庫的機制,就一定會存在與實作庫版本耦合的可能,不利于整個微前端生态的統一和融合。

export async function bootstrap() {
  console.log('react app bootstraped') ;
}

export async function mount(props) {
  console.log(props) ;
  ReactDOM.render(<App/>, document.getElementById('react15Root'));
}

export async function unmount() {
  ReactDOM.unmountComponentAtNode(document.getElementById('react15Root') ) ;
}
           

如下所示是一個與具體架構實作相耦合的例子(反例):

//主應用
import React from 'react' ;
import ReactDOM from 'react-dom';
import MicroFrontend from 'micro-frontend';

ReactDOM.render(<MicroFrontend  base="/app1" entry="//localhost/a.js">);

//子應用
window.microFrontends = {
  app:{...} ,
  reduxStore:{...} ,
  globals:(...) ,
}
           

對于路由的問題,如下圖所示。這樣一條通路鍊路後,重新整理目前 URL 通常情況下會發生什麼?

生産可用:是時候來一個微前端架構了!

正常通路一個站點,經過一番操作之後,進入到站點的清單頁,路由會變大很複雜,但如果是一個微前端使用者,重新整理一下頁面會出現 404 的情況。解決思路是将 404 路由 fallback 到一個異步注冊的子應用路由機制上。

對于應用導入方式的選擇,比較常見的方案是 Config Entry。通過在主應用中注冊子應用依賴哪些 JS。這種方案一目了然,但是最大的問題是 ConfigEntry 的方式很難描述出一個子應用真實的應用資料資訊。真實的子應用會有一些 title 資訊,依賴容器 ID 節點資訊,渲染時會依賴節點做渲染,如果隻配 JS 和 CSS,那麼很多資訊是會丢失的,有可能會導緻間接上的依賴。

<html>
  <head>
  <title>sub app</title>
    <link rel="stylesheet" href="//localhost/app.css">
</head>
<body>
  <main id="root"></main>
   <script src="//localhost/base. js">
</body>
</html>

<script>
   import React from 'react'
   import ReactDOM from 'react-dom'

   ReactDOM.render(<App/>, document.getElementById('root') )
</script>
           

另外一種方案是 HTML Entry,直接配 html,因為 html 本身就是一個完整的應用的 manifest,包含依賴的資訊。HTML Entry 的優點是接入應用的資訊可以得到完整的保留,接入應用位址隻需配一次,子應用的原始開發模式得到完整保留,因為子應用接入隻需要告知主應用 html 在哪,包括在不接入主應用時獨立的打開。它的缺點是将解析的消耗留給了運作時。而 Config Entry 相較于 HTML Entry 減少了運作時的解析消耗。Config Entry 的缺點是主應用需配置完整的子應用資訊,包含初始 DOM 資訊、js/css 資源位址等。

registerMicroApps([
    {
        name: 'react app'
        //   index.html 本身就是一個完整的應用的 manifest
       entry: '//localhost: 8080/index.html',
       render,
       activeRule: '/react'
     }
]) ;
           

對于樣式隔離問題,例如 BEM,每個子應用在寫樣式之前要加一些字首,做一些隔離,但是這個做法并不推薦。相對而言,CSS Module 更簡單高效,也更智能化,是比較推薦的方式,但是也存在着問題。而 Web Components 看上去很不錯,但在實踐過程中也會發生一些問題。

例如在 Web Components 渲染的流程中出現了問題,如下圖所示。

生産可用:是時候來一個微前端架構了!

在 antd 中提供了全局的 API,可以提前設定好所有的彈框的 container,但是也不是每個元件庫都能像 antd 一樣完成度那麼高。

生産可用:是時候來一個微前端架構了!

螞蟻所采用的解決方案是做動态的加載和解除安裝樣式表,如下圖所示,這種方案是很有效的。

生産可用:是時候來一個微前端架構了!

對于 JS 隔離,螞蟻提出了 JS Sandbox 機制,如上圖所示,其中 bootstrap、mount及 unmount 生命周期是子應用需要暴露出來的,因為子應用的整個生命周期都是被主應用所管理的,是以可以在主應用中給子應用插入各種攔截的機制,也可以捕獲到子應用在加載期間做了哪些全局上的修改。在 unmount 時,可以将全局上的副作用全部手動移除掉,同時也可以實作在重新進來時,将上次忘記解除安裝的副作用重建一遍,因為需要保證下次進來時能完整回複到與上次一緻的上下文。

生産可用:是時候來一個微前端架構了!

對于資源加載問題,在微前端方案中存在一個典型的問題,如果子應用比較多,就會存在之間重複依賴的場景。解決方案是在主應用中主動的依賴基礎架構,然後子應用保守的将基礎的依賴處理掉,但是,這個機制裡存在一個問題,如果子應用中既有 react 15 又有 react 16,這時主應用該如何做?螞蟻的方案是在主應用中維護一個語義化版本的映射表,在運作時分析目前的子應用,最後可以決定真實運作時真正的消費到哪一個基礎架構的版本,可以實作真正運作時的依賴系統,也能解決子應用多版本共存時依賴去從的問題,能確定最大程度的依賴複用。

基于 props 以單向資料流的方式傳遞給子應用:

export function mount(props) {
    ReactDOM.render(
        <App {...props}/>,
         container
    )
}
           

基于浏覽器原生事件做通信:

//主應用
window.dispathEvent(
    new CustomEvent('master:collapse-menu'),
    {detail: {collapsed:true} }
)

//子應用
window.addEventLister(
    'master:collapse-menu',
    event => console.log(event.detail.collapsed)
)
           

對于應用之間資料共享及通信的問題,螞蟻提出了兩個原則,第一個原則是基于 props 以單向資料流的方式傳遞給子應用。第二個原則是基于浏覽器原生事件做跨業務之間的通信。

在真實的生産實踐中,螞蟻總結出了幾點經驗及建議:兄弟節點間通信以主應用作為消息總線,不建議自己封裝的 Pub/Sub 機制,也不推薦直接基于某一狀态管理庫做資料通信。

螞蟻在實踐中做的性能優化,包括異步樣式導緻閃爍問題的解決以及預加載問題的解決。

異步樣式導緻的閃爍問題:

生産可用:是時候來一個微前端架構了!

預加載:

export function prefetch(entry: Entry, fetch?: Fetch) {
    const requestIdleCallback = window.requestIdleCallback || noop;

    requestIdleCallback(async () => {
        const { getExternalScripts, getExternalStyleSheets }
            = await importEntry(entry, { fetch } );
        requestIdleCallback(getExternalStyleSheets) ;
        requestIdleCallback(getExternalScripts) ;
    }) ;
}
           

如圖所示為微前端方案涉及到的技術點,本文分享了圖中三分之二的内容。

生産可用:是時候來一個微前端架構了!

在螞蟻金服做了大量關于微前端方案之後,總結了衡量一個微前端方案是否友好的兩個标準,第一個标準是技術無關,也是微前端最核心的特性,不論是子應用還是主應用都應該做到架構不感覺。第二個标準是接入友好,子應用接入應該像接入一個 iframe 一樣輕松自然。

螞蟻的微前端落地的實踐成果

螞蟻内部基于微前端基礎架構提出了一體化上雲解決方案,稱為 OneX,是一個基礎的平台,它可以将各種流程和工具串聯,其價值展現在品牌、産品和技術方面。品牌價值指的是統一的界面架構、UI、互動形成了螞蟻金服科技品牌心智。

生産可用:是時候來一個微前端架構了!

OneNav + OneConsole + TechUI + OneAPI + Bigfish

下圖所示為螞蟻的一個真實應用的例子,除了中間接入的産品是自己控制之外,其他内容都是由平台提供,這樣,如論是一個三年陳項目還是新做的項目,在基本的視覺上可以做到統一。

生産可用:是時候來一個微前端架構了!

産品價值指的是産品具有自由組合能力。之前的産品是多個産品、多個站點的控制台,而現在隻需要一個控制台,将多個産品自由的組合,這樣,可以在商業上有更多的相應空間以及更多自由的搭配。基于這樣的系統也可以做一些全局性的事情,例如埋點、使用者的轉化跟蹤業務。

生産可用:是時候來一個微前端架構了!

技術價值指的是研發上的提效。經過微前端的改造後,螞蟻可以将大型的系統解耦成可以獨立開發的并行的小型的系統,這些小型系統可以交給别的團隊或者使用可視化的系統去實作,最後在運作時隻需要将他們內建起來。

在技術價值方面也可以實作傳遞上的提效,隻需要在某一個環境的任意一個環境中做平台上的接入,應用就可以做到在多餘的環境中不改代碼,直接運作。

下圖為阿裡雲剛上市的一個産品例子,其中包括 15 個來自不同團隊的應用進行維護,它的特點是并不是單獨為阿裡雲而設計的,之前在螞蟻也有運作,隻不過在阿裡雲中做了動态的組合。OneTour 微應用元件主要解決的是在多個産品控制台之間自由切換導緻流程割裂的問題。

生産可用:是時候來一個微前端架構了!

螞蟻微前端的落地成果包括:有 70+ 線上應用接入(阿裡雲 + 螞蟻雲 + 專有雲),最複雜一個控制台同時內建 15 個應用,并且有 4+ 不同技術棧,以及開發到釋出上線全鍊路的自動化支援,一雲入駐多雲運作。

基于以上技術上的成果,螞蟻沉澱了自己的微前端方案并開源。

基于以上技術上的成果,螞蟻沉澱了自己的微前端方案并開源。qiankun 是架構無關的微前端核心,umi-plugin-qiankun 是基于 umi 應用的 qiankun 插件,友善 umi 應用通過修改配置的方式變身成為一個微前端系統。基于上述實踐的檢驗和内部落地結果來看,在大規模中背景應用場景下,微前端架構是一個值得嘗試的方案。

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/zhibo

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-02

本文作者:有知

本文來自:“

阿裡技術公衆号

”,了解相關資訊可以關注“

阿裡技術

繼續閱讀