微前端在網易七魚的實踐

1、前言

網易七魚是提供圍繞客戶服務與智能營銷的 SaaS 平臺。在七魚業務中,有在線系統、呼叫系統、機器人、工單系統、數據大屏等業務線,它們分佈在兩個業務端,管理端和客服端。這兩個端的功能框架相似,都是由外層框架(頂部導航、一級菜單)及中間的內容區組成。css

2、業務現狀

隨着業務體量的增大與功能的增多,主系統做爲一個巨石應用複雜度愈來愈高,全部的業務線耦合在一塊兒,在系統構建、業務分離、開發維護方面帶來了新的挑戰。html

爲解決以上問題,咱們最初採用了 「MPA + iframe」 的技術方案。先按業務維度從巨型單體應用中拆分出多個子應用,並用 React 技術棧對它們進行了重構,經過 iframe 的方式隔離新老技術棧。這些子應用基於 URL 解耦,每一個子應用能夠獨立開發、運行和部署。前端

採用「MPA + iframe」 的技術方案是一把雙刃劍,用它能夠較方便地解決現有的問題,但同時也帶來了一些新的問題。react

MPA 方案能夠容許子應用使用不一樣技術棧,父子應用之間自然隔離,可是瀏覽器頁面跳轉時不能保持單頁應用的流暢體驗,父子應用通訊困難。webpack

iframe 能夠方便地隔離新老技術棧,可是也帶來了一些問題:git

問題 舉例 較好的解決方案
父子框架 URL 不一樣步、瀏覽器前進後退按鈕異常 -- 定義父子框架路由映射,利用 postMessage 和 history API 解決
父子框架 UI 不一樣步 遮罩層只能遮蓋 iframe 所在的區域、iframe 內的彈框沒法相對外層頁面居中
子框架的全局上下文與父框架徹底隔離,致使父子框架通訊困難、同步數據冗餘 --
加載慢,體驗較差 --

項目最開始時採用的開發框架是 NEJ(Nice Easy Javascript),它的依賴管理系統、控件系統等特性爲早期的項目開發作出了很大的貢獻,如今它完成了本身的歷史使命,項目開始向 React 技術棧過渡。github

下圖展現了應用框架現狀:web

應用框架現狀

能夠看到,整個系統中使用了 NEJReact 兩套技術棧。npm

React 外層框架內部嵌入的是 React 應用,這些應用分別引用了各自的外層框架,並經過 React 業務組件庫複用。json

NEJ 外層框架內部的狀況則比較複雜,部分場景嵌入的是 NEJ 應用,還有部分場景是經過 iframe 嵌入的 React 應用,這些 React 應用中的部分頁面中也有經過 iframe 再次嵌入 NEJ 應用的場景。

由於 NEJ 老技術棧的組件支持匱乏,並且歷史遺留代碼較多,致使它們的開發和維護成本都很高。

目前前端工程正處於技術棧統一的過渡期,須要維護兩套外層框架,後續將逐漸由 NEJ 轉向 React。對於新增的應用,則直接採用 React 技術棧。

隨着新應用的增多,外層框架被引用的次數愈來愈多,每次更新都須要發佈多個應用,使用新技術棧外層框架的維護成本爲愈來愈高。

微前端是目前比較火的話題,它是微服務在前端領域的擴展。它將前端總體拆分爲多個更小、更易管理的片斷,能夠解決工程複雜度高、多技術棧共存、開發維護困難等問題。微前端的兩大特性微應用技術棧無關,每一個微應用能夠獨立開發、運行和部署,能夠很好的匹配現有的業務場景。

所以咱們將目光轉到了對現有應用進行微前端改造上。

3、微前端改造

改造的好處

將現有的應用進行微前端改造能夠帶來如下好處:

  • 積累實踐經驗,爲未來從巨石應用拆分及微前端改造作準備;
  • 去除接入二方應用時使用的 iframe,優化產品體驗;
  • 收斂外層框架,提高研發效率,下降維護成本;
  • 提供前端增量升級能力,後續能夠更好地複用歷史代碼、實施漸進式重構;

社區內的微前端解決方案有許多種,包括:

  • Single-spa:只解決了應用之間的加載方案,沒有考慮其餘的周邊問題;
  • qiankun:基於 single-spa,提供了更加開箱即用的 API,具有 JS 沙箱、樣式隔離、子應用並行等能力;
  • Icestark:約束了框架應用必須基於 React,不利於後續的技術棧優化;
  • Magix:適合作單頁應用的項目,不支持多個實例,不知足業務需求;
  • Luigi:是一個基於 iframe 的微前端框架,仍有前文提到的 iframe 帶來的產品體驗問題;
  • Ara Framework:是一個基於 Airbnb's Hypernova 的,由服務端渲染延伸出的微前端框架,接入時對原應用的侵入較多;
  • WidgetJS:是一個輕量級的微前端方案,文檔不夠友好;

綜合考慮業務場景、上手難度、文檔友好性、代碼入侵性、可維護性等方面,最終選擇的微前端解決方案是 qiankun。接下來就是基於 qiankun 的微前端改造了。

業務分析與改造效果

七魚的微前端改造,從技術層面涉及到 React、NEJ 兩類技術棧,從業務層面涉及到管理端、客服端。

由於最終目的是全部前端工程統一到 React 技術棧,而管理端部分應用的外層框架已經用 React 重構過,因此先從管理端下手。

首先分別重新、老技術棧應用中選取一個應用進行改造,積累相關經驗。應用選擇的標準是無複雜的業務邏輯、流量少,以下降改造風險。新技術棧應用選的是首頁應用,老技術棧應用選的是數據大屏應用。

來看一下七魚微前端改造後的主頁:

七魚微前端改造後主頁

這裏說明兩個概念,基座應用(也稱爲主應用、框架應用等)和子應用(也稱爲微應用):
**

  • 基座應用負責總體佈局、子應用的配置和調度,通常包含各個子應用公有的部分,好比外層框架;
  • 子應用負責自身業務邏輯的渲染;

能夠看到,上圖用紅框標出了主頁的兩個組成部分,外層框架(頂部導航、一級菜單)和中間內容區。

外層框架就是由基座應用控制的,經過監聽 URL 進行路由分發、子應用調度等。內容區由一個或多個子應用控制,上圖中的內容區就是由一個首頁子應用控制的。

大體的改造步驟

  1. 建立管理端基座工程 basic-admin;

    1. 基座應用只包含各個子應用共有的部分;
  2. 建立首頁子工程 micro-index、大屏子工程 micro-bigscreen,以及相應的應用和集羣;
  3. 在項目的入口文件裏,暴露相應的生命週期鉤子,供 qiankun 識別;
  4. 修改打包配置,使物料以 umd 的方式輸出,以 webpack 爲例:
const webpackConfig = {
    //...
    output: {
        //...
        library: `${packageName}-[name]`, // 此處的packageName爲子應用名,如micro-bigscreen
        libraryTarget: 'umd',
        jsonpFunction: `webpackJsonp_${packageName}`,
    }
};
  1. 新增微應用對應的內部路由,改造網關:

    1. 內部路由用於註冊子應用,正常狀況下用戶沒法直接訪問到;
    2. 改造後的網關須要將全部匹配到基座 URL 前綴的請求,都定向到基座應用;
  2. 兼容七魚 PC 客戶端(低版本 Chrome 瀏覽器內核):

    1. qiankun 加載資源時依賴的 fetch API 的兼容性問題;
    2. 由於 height 繼承等致使的樣式問題;
  3. 在基座應用中調用 qiankun 的 API,將子應用註冊到基座應用,如:
registerMicroApps(
  [
    {
      name: 'micro-index',
      entry: '//' + location.hostname + '/_MicroIndex',
      container: '#subapp-container',
      activeRule: '/madmin/home',
    },
    {
      name: 'micro-bigscreen',
      entry: '//' + location.hostname + '/_MicroBigscreen/index',
      container: '#subapp-container',
      activeRule: '/madmin/dashboard',
    }
  ]
);

4、微前端架構下的業務變化

服務網關的變化

微前端改造後,全部管理端相關子應用的 URL 前綴爲「/madmin/」,如主頁的 URL 爲「/madmin/home/」。服務網關須要將全部以「/madmin/」開頭的路由定向到管理端基座應用。

結合網關的微前端架構圖以下:

微前端架構圖

子應用的開發模式

子應用有獨立的倉庫,部署完以後,將應用的發佈產物註冊到基座應用裏,這些產物能夠是子應用的訪問地址,也能夠是資源配置對象(scripts + styles + html)。

須要注意的是,在子應用與基座應用開發聯調時,子應用讀取的是基座應用的同步數據,Mock 的同步數據須要在基座應用中配置。同理,子應用用到的接口代理也須要在基座應用中配置全。

基座應用的總體流程

基座應用啓動後會監聽 URL 變化,當用戶訪問系統時,根據當前訪問的 URL 和註冊的路由信息,可以匹配到當前須要加載的子應用信息,而後去加載子應用的資源並渲染子應用

當用戶點擊觸發跳轉時,若是路由變化觸發的是一個內部 URL 跳轉,會直接根據應用內部的路由邏輯渲染頁面。若是路由變化觸發的是跨應用的跳轉,則從新回到上面的路由匹配的流程中。

下圖是微前端改造後的應用框架:

微前端改造後的應用框架

按照上述的子應用改造過程,能夠逐步完成管理端的微前端改造。接下來就是對客服端的微前端改造了。

雖然客服端與管理端的框架結構相似,可是它們的 URL 是解耦的,並且它們一級菜單和頂部導航的業務功能差異較大,共用同一個基座應用會致使應用複雜度太高,最好是另外建立一個客服端專用的基座應用,兩個基座應用經過業務組件庫複用組件。

將來總體的應用框架以下:

將來總體的應用框架

有了微前端的助力,整個系統能夠更加平滑地進行技術棧升級,最終實現前端技術棧的統一,更高效地賦能業務發展。

5、遇到的問題及解決方案

一、子應用接入基座應用後,babel-polyfill 報錯

babel-polyfill 不支持引用屢次(基座應用和子應用分別引用了一次),直接去除 babel-polyfill 會致使沒法單獨運行子應用,能夠改用 idempodent-babel-polyfill

二、基座應用訪問子應用資源報 404 錯誤

資源路徑有問題,須要配置運行時的 public path。

if (window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} else {
    __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/";
}

三、報錯提示找不到子應用容器

將 sandbox 設置爲 strictStyleIsolation,會啓用嚴格的樣式隔離,原理是把子應用內容渲染到基座容器的 shadow dom 中,致使沒法直接獲取基座應用的 dom 元素。

取消 strictStyleIsolation,只設置 jsSandBox 爲 true 就不會有問題。

樣式隔離的最佳實踐是採用約定式隔離:用 CSS 命名空間、CSS Module、css-in-js 等工程化手段,避免寫全局樣式。

四、本地聯調時基座應用訪問子應用資源時報跨域錯誤

開發環境使用 browserSync 進行瀏覽器同步,qiankun 框架經過瀏覽器的 fetch API 獲取子應用的資源,會存在跨域問題,因此須要設置 cors 爲 true。

browserSync({
  //...
  cors: true
});

五、子應用引入 qiankun 生命週期後,沒法獨立運行

添加條件判斷,非 qiankun 環境下,走以前的運行環境。
修改 'entry.js' 的 render 條件:

if (!window.__POWERED_BY_QIANKUN__) {
  ReactDOM.render(
    <Root store={store} history={history} routes={routes}/>,    document.getElementById('react-content')
  );
}

六、本地聯調時子應用由於有熱加載致使報錯

使用 ScriptExtHtmlWebpackPlugin 插件修改 webpack 配置,爲每一個頁面的入口 js 加 entry 屬性。

tplPlugins.push(
  new ScriptExtHtmlWebpackPlugin({
    custom: {
      test: /(?<!vendors.*)entry\.js$/,
      attribute: 'entry'
    }
  }
));

七、本地聯調時子應用調用 Mock 接口或同步數據報錯

在子應用與基座應用開發聯調時,子應用讀取的是基座應用的全局配置。本地環境基座應用可能接入不少子應用,其餘子應用用到的接口代理要配全,不然調不到接口。同理,Mock 的同步數據也要在基座應用配置全。

八、低版本瀏覽器加載資源時 cookie 丟失

qiankun 框架經過瀏覽器的 fetch API 獲取子應用的資源。Chrome 內核71及以前的版本,即便網址與調用腳本同源,fetch API 也不會自動發送 cookie。

須要在基座應用中啓動應用時,對 fetch 進行顯式的參數配置:

qiankun.start({
  //...
  fetch: (url, init) => {
    return window.fetch(url, {
      ...init,
      credentials: 'same-origin'  // 在當前域名內自動發送 cookie
    });
  }
});

九、非 React 環境引入 qiankun 生命週期的方式

定義一個與子應用名稱一致的全局變量,生命週期鉤子函數必須返回 promise,若是不支持 promise 須要引入 promise-polyfill。入口文件能夠這樣寫:

(function(win) {
    // 此處的'micro-bigscreen'與註冊到基座應用的子應用名稱一致
    win['micro-bigscreen'] = {
        bootstrap: function() {
            // 必須返回promise,不然子應用沒法正常啓動
            return Promise.resolve();
        },
        mount: function() {
            return Promise.resolve();
        },
        unmount: function() {
            return Promise.resolve();
        }
    };
})(window);

十、PC 客戶端子應用變量訪問報錯:Uncaught TypeError: 'get' on proxy

PC 客戶端注入了 window.cefQuery 與 window.cefQueryCancel 變量,它們的屬性描述符中 writable 與 configurable 都爲 false,通過 JS 沙箱 Proxy 後直接訪問它們會報錯:Uncaught TypeError: 'get' on proxy。

由於只有子應用用到了沙箱,此報錯只會影響子應用,基座應用不受影響。

解決方法是:分別從 window.cefQuery 與 window.cefQueryCancel 複製出新的變量 window.cefQuery2 與 window.cefQueryCancel2,修改它們的屬性描述符 writable 與 configurable 爲 true。而後將微前端子應用中引用 window.cefQuery 與 window.cefQueryCancel 的地方分別修改成 window.cefQuery2 與 window.cefQueryCancel2。

基座應用中的相關代碼:

const polyfillPcPlatform = () => {
  if (window.cefQuery) {
    Object.defineProperty(window, 'cefQuery2', {
      value: window.cefQuery,
      writable: true,
      configurable: true
    });
  }
  if (window.cefQueryCancel) {
    Object.defineProperty(window, 'cefQueryCancel2', {
      value: window.cefQueryCancel,
      writable: true,
      configurable: true
    });
  }
};

//註冊子應用
registerMicroApps(
  [
    //...
  ],
  {
    beforeLoad: [
      app => {
        // 兼容PC客戶端
        polyfillPcPlatform();
      }
    ],
    //...
  }
);

6、總結

本次微前端實踐基於 qiankun 框架,建立了管理端基座應用,將管理端首頁和數據大屏應用進行了微前端改造,改造涉及 React 和 NEJ 兩套技術棧,達到了如下目的:

  1. 積累了微前端實踐經驗,爲未來從巨石應用拆分及微前端改造作準備;
  2. 使管理端不一樣技術棧的二方應用接入再也不須要使用 iframe,優化了產品體驗;
  3. 收斂了管理端外層框架,使新應用的接入再也不須要理會頂部導航和一級菜單;
  4. 提供了前端增量升級能力,後續能夠更好地複用歷史代碼、實施漸進式重構;

微前端不是一個框架,而是一套架構體系,基座應用的建立和子應用的改造是它的基礎設施,除了基礎設施外還有配置中心和觀察工具。配置中心包括參數配置、版本管理、發佈策略等。觀察工具備必定的運維職能,包括應用狀態的可見、可控性等。

有了上述能力後,能夠經過它們統一管控全部的微應用,爲 SaaS 產品提供自由組合的能力,使技術爲業務帶來更大的價值。

更多技術內容,歡迎關注【網易智企技術+】公衆號。

相關文章
相關標籤/搜索