微前端(singleSpa + React )試玩

前言

咱們團隊正在作一個XX系統,技術棧是React,目前該系統日漸龐大,開發及維護成本加大,且每次必須把整個項目一塊兒打包,費時費力。經考慮後決定將其拆分紅多個項目,由它們組合成一個完整系統,微前端架構是很是好的選擇。html

微前端有如下幾個優勢:前端

  1. 單項目維護:好比將商品模塊單拉出來造成一個項目,它能夠由一個小組單獨維護,實現良好解耦
  2. 複雜度下降:不須要在整個集成式的龐大系統內開發,避免巨大的代碼量,開發時編譯速度快,提升開發效率
  3. 容錯性:單獨項目發生錯誤不會影響整個系統
  4. 技術棧靈活:vue、react、angular 等包括其餘前端技術棧均可以使用,會 vue 的不須要再學 react

對咱們來講最大的好處是單項目維護vue

展現

UI示例圖

咱們將整個微前端分爲兩個部分:react

  1. 主項目(Main):紅色框部分,做爲整個項目的父級,負責展現菜單模塊、頭部模塊
  2. 子項目(Sub-apps):藍色框部分,子項目的做用是具體的業務展現

動圖展現

注意看地址欄變化,其中包含 /app1/xxx/app2/xxx,乍一看這是一個項目中兩個頁面的切換,其實是來自兩個獨立的項目,app1 和 app2 來自不一樣的 git 倉庫。webpack

微前端架構圖

整個流程大概爲:用戶訪問 index.html, 此時運行模塊加載器Js,加載器會根據整個系統的配置文件(project.config) 去註冊各個項目,系統會先加載主項目(Main),而後會根據路由前綴動態加載對應的子項目git

咱們這個架構也參考了網上不少好的文章,其中核心文章可參考 alili.tech/archive/110…github

關於 project.config

大概以下web

[
 {
    isBase: false,
    name: 'app1',
    version: '1.0.0',
    //經過該路由前綴匹配加載當前入口文件
    hashPrefix: '/app1',
    //入口文件
    entry: 'http://www.xxxx.com/app1/dist/singleSpaEntry.js',
    //頂級Store
    store: 'http://www.xxxx.com/main/dist/store.js'
  }
  ......
]
複製代碼

這裏 project.config 用於生產環境,咱們把打包後的文件上傳到 OSS(或CDN),而後將當前打包的項目配置同步到服務端,服務端會將全部項目的配置整合到 project.config, 用戶在訪問 index.html 時會獲取 project.config,而後 single-spa 會根據這些配置進行註冊並根據路由加載對應項目。 bootstrap

技術細節

single-spa

咱們找了些實現微前端的倉庫,對比後決定使用single-spamarkdown

咱們技術棧是 react,在子項目入口中須要使用 single-spa-react 來構建,關鍵代碼以下:

import singleSpaReact from 'single-spa-react';

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  domElementGetter
});

export function bootstrap(props) {
  return reactLifecycles.bootstrap(props);
}

export function mount(props) {
  return reactLifecycles.mount(props);
}

export function unmount(props) {
  return reactLifecycles.unmount(props);
}
複製代碼

若是你使用 vue,可使用 single-spa-vue

而後在系統入口文件中,把全部的項目註冊進來:

import * as singleSpa from 'single-spa';

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('app1-entry.js'),
    () => location.hash.startsWith(`#/app1`),
    props
  );
複製代碼

具體可參考 single-spa 官網 single-spa.js.org 這裏有不少例子

Webpack 與 SystemJs

咱們使用 lerna 統一管理全部項目的依賴包,全部依賴包的版本統一,這樣很是方便維護。

使用 webpack 的 dll 功能,將全部項目的公用依賴包抽離,好比 react、react-dom、react-router、mobx等

爲了方便項目動態加載,咱們也參考網上大佬的想法,使用了systemjs,咱們用的是 0.20.19 版本。配合 systemjs ,在 Webpack 中須要改一下 libraryTarget:

output: {
    publicPath: 'http://www.xxxxx.com/',
    filename: '[name].js',
    chunkFilename: '[name].[chunkhash:8].js',
    path: path.resolve(__dirname, 'release'),
    libraryTarget: 'amd', //注意 這裏使用 amd 的規範
    library: 'app1'
  },
複製代碼

咱們沒有使用 umd 規範,使用的是 amd 規範,也沒有使用 systemjs 裏的 Import Maps 功能,而是直接經過 project.config 來動態加載模塊入口。

app之間通訊

關於這個也看了一些大佬的方案,大概就是全部的項目裏有個 store,在註冊入口時將全部 store 放進隊列,須要更新 store 裏的狀態時,調用 dispatch 將全部 store 同步。

個人作法和傳統單頁應用同樣,一個系統應該只有一個頂級 Store,因爲頂級 Store 裏存的通常是整個系統的公用狀態 好比菜單、用戶信息等,我把它放在 Main項目裏,但打包時這個Store是單獨抽離的:

entry: {
    singleSpaEntry: './src/singleSpaEntry.js',
    store: './src/store' //單獨一個入口
  },
複製代碼

在註冊時,將這個 Store 傳入每一個項目中:

//頂級Store
const mainStore = await SystemJS.import(storeURL);

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('http://www.x.com/app1/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);
singleSpa.registerApplication(
    'app2',
    () => SystemJS.import('http://www.x.com/app2/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);
複製代碼

這樣就能夠達到只管理這一個 Store 就能夠,很是方便。 注意:我使用的是 Mobx 做爲狀態管理

前端部署

咱們部署的方式很是簡單,我本身寫了一個 webpack 插件用於把打包後的 dist 傳到 OSS 而後將項目配置發送給服務端,服務端(NodeJs)根據傳入的項目配置組織成 project.config,而後用戶在訪問 index.html 時會獲取 project.config,此時 single-spa 根據配置註冊全部項目,而後根據路由來拉取對應的項目入口文件js文件。

把子項目的掛載 DOM 放在 Main 項目裏

咱們的需求是 Main 做爲整個項目的 Layout,其中子項目的掛載 Dom 也在 Main項目裏,這就必須等到 Main 項目徹底渲染完成後,才能掛載子項目。我參考了網上有些微前端的實現,把 domElementGetter 方法借鑑了過來:

function domElementGetter() {
  let el = document.getElementById('sub-module-wrap');
  if (!el) {
    el = document.createElement('div');
    el.id = 'sub-module-wrap';
  }
  let timer = null;
  timer = setInterval(() => {
    if (document.querySelector('#content-wrap')) {
      document.querySelector('#content-wrap').appendChild(el);
      clearInterval(timer);
    }
  }, 100);

  return el;
}
複製代碼

demo

demo地址:github.com/Vibing/micr… 該demo只提供一個微前端的參考,實際開發和部署須要看各位公司狀況來定

結束語

這是咱們第一次玩微前端,可能有不少地方不完美,還望各位大佬多多包涵

相關文章
相關標籤/搜索