博客 / 詳情

返回

手把手教你定製一套適合團隊的微前端體系

編者按:大家在使用目前市面上的微前端解決方案時,可能會有些顧慮。比如遇到框架自身的問題和坑點,影響了業務進度怎麼辦?現在有這麼一款框架 Satum,可以像 express/koa 框架一樣提供中間件機制,其只負責核心的功能(規則計算和微應用加載/卸載)。可以基於該框架定製一套適合團隊自身業務的微前端體系,另外該框架還有更多特性,如面向多實例集成、適配多終端等。

寫在前面

隨着前端工程複雜度逐漸增加,業務複雜的前端項目需要拆分成多個項目來維護。還有就是業務存在了 3-5 年,一些技術棧沒及時升級,已經無法在其基礎上做開發,需要搞個新項目一起集成。又或者是想通過新業務,對新技術/框架最新大版本試用。遇到上述場景(當然不限這些場景,還有更多場景待挖掘),大家也許會把目光轉向微前端方案。

市面上也有一些微前端框架的實現,但黑盒化太嚴重。遇到問題/坑點,只能等框架的作者修復了才能正常工作。但業務開發時真正遇到了問題,如果寄希望於框架作者/社區,會不會感到涼涼?

本篇我們基於 Satum 定製一套微前端體系出來。 Satum 是一款面向多實例集成、功能可插拔的微前端框架,旨在解決多實例模式的微前端場景(當然單實例也包含其中)。即多個微應用被同時激活時,該如何協調加/卸載數據依賴組件共享渲染順序的問題。且支持中間件&插件機制,可以很方便自定義沙箱路由協調緩存等。通過不同的中間件&插件組合,可以定製適合自己團隊的微前端架構體系。

Satum 的優勢(相較於社區常用的方案 qiankunmicro-app):

✅:完全支持, ❌:不支持, ⭕️:弱支持
Satum qiankun micro-app
使用簡單,無需適配代碼即可集成 無需適配 強依賴 需要
擴展性,控制流程中的輸入或輸出 ⭕️
定製化,支持定製適合業務的微前端體系
多應用同時激活,微應用調度和路由協調 支持 極弱 規則限制
掛載點基於路由規則,同個應用可渲染到頁面不同處
多終端支持,相同 URL 在不同端激活不同的微應用
共享機制,支持共享三方包和組件
區塊機制,可以加載任意 UI 組裝頁面
支持 Vite,沙箱環境中無縫支持

微前端體系

微前端體系包含微前端框架、配置中心、微應用開發配套工具及配套服務、物料市場、低代碼平台等。
體系示例圖如下:
image.png

帶來的好處

微前端體系帶來的好處不言而喻,簡單説來,使用該體系可以將一個大型項目拆分成多個子項目,每個子項目各自獨立,互不影響,又互相關聯。
核心價值是什麼呢?

  • 技術棧無關
    主框架不限制接入應用的技術棧,微應用具備完全自主權
  • 獨立開發、獨立部署
    微應用倉庫獨立,前後端可獨立開發,部署完成後主框架自動完成同步更新
  • 增量升級
    在面對各種複雜場景時,通常很難對一個已經存在的系統做全量的技術棧升級或重構,而微前端是一種非常好的實施漸進式重構的手段和策略
  • 獨立運行時
    每個微應用之間狀態隔離,運行時狀態不共享

    所需的工程板塊

    從上述體系架構圖可以很清楚地看到,我們需要以下工程板塊:

    微前端框架

    建議利用 @satumjs/core 的定製化能力,打造適合團隊的微前端框架。不太建議使用市面上黑盒化的微前端框架。這樣做,可以利用其中間件/插件機制,定製個性化的微應用處理邏輯。

    配置中心

    所謂配置中心,其實是一個可視化的 json 編輯。如果有個線上的配置中心,可以靈活控制業務板塊的熱更新。有些業務需求,不需要修改和發佈代碼就能達到目的。

    微應用開發配套工具及配套服務

    配套工具和服務太重要了,因為我們開發的微應用不是完整的業務,有些功能模塊甚至是從其他微應用共享而來。雖然微應用可以獨立運行和部署,但開發過程中,還是需要及時看到集成後的效果。

    物料市場

    這塊主要是基礎組件和業務組件,可以是基於 ant-design 或其他 design 搞的組件家族。

    低代碼平台

    微前端能夠集成低代碼平台生成的頁面,我覺得也很必要。這樣的好處是揚長避短,低代碼平台一般不太建議搭建業務複雜的頁面,所以單獨存在的低代碼平台意義有限;而純代碼打造的平台工期較長,需要的前端能力也較高;如果能夠做到簡單頁面低代碼實現,複雜邏輯純代碼實現,然後集成在一起,是最理想的方案。

    使用後達到的目標

    1、微應用合理分拆,獨立運行和部署,降低整體複雜度;
    2、技術庫無關,可以根據業務需要嘗試各種組件庫和技術棧;
    3、增量升級,新老系統可以集成在一起,新系統負責新業務功能;
    4、多終端適配,Satum 支持同一個 URL 不同終端喚起不同的微應用;
    5、單一職責,讓諸如 React/Vue 等只做視圖方面的事情,業務邏輯在其他微應用上承載;
    6、可自行定製沙箱、代碼處理、路由協調等功能;

    如何定製

    初始化項目

    新建文件夾,添加必要的依賴

    新建文件夾 mf2e-test,通過命令 echo {} > package.jsonyarn init初始化 package.json。
    我們使用 rollup 做構建工具,需要先安裝構建相關的依賴。

    yarn add rollup @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-dts rollup-plugin-terser @babel/cli @babel/core @babel/preset-env @babel/preset-typescript typescript rimraf --dev

    之後再創建構建相關的配置文件 .babelrcrollup.config.jstsconfig.json

    {
    "presets": [
      "@babel/typescript",
      "@babel/preset-env"
    ]
    }
    import commonjs from '@rollup/plugin-commonjs';
    import { nodeResolve } from '@rollup/plugin-node-resolve';
    import { babel } from '@rollup/plugin-babel';
    import { terser } from 'rollup-plugin-terser';
    import dts from "rollup-plugin-dts";
    import * as pkg from './package.json';
    
    const extensions = ['.js', '.ts'];
    const plugins = [
    commonjs(),
    nodeResolve({ extensions }),
    babel({ extensions, include: ['./src/**/*'] }),
    terser(),
    ];
    
    const umdConfig = {
    input: 'src/index.ts',
    // 此處 name 是會在 window 上附加,需注意請自定義
    output: { dir: 'lib', name: 'mf2eTest', format: 'umd' },
    plugins,
    };
    
    const esConfig = {
    input: 'src/index.ts',
    external: Object.keys(pkg.dependencies),
    output: { file: 'lib/index.es.js', format: 'es' },
    plugins,
    };
    
    const dtsConfig = {
    input: './src/index.ts',
    output: { file: './lib/index.d.ts', format: 'es' },
    plugins: [dts()]
    }
    
    export default [umdConfig, esConfig, dtsConfig];
    {
    "compilerOptions": {
      "module": "es2015",
      "target": "es5",
      "lib": ["esnext", "dom"],
      "baseUrl": "./src",
      "esModuleInterop": true,
      "noImplicitAny": true,
      "strictNullChecks": true,
      "noImplicitReturns": true,
      "noFallthroughCasesInSwitch": true,
      "noUnusedLocals": false,
      "noUnusedParameters": true,
      "suppressImplicitAnyIndexErrors": true,
      "allowSyntheticDefaultImports": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "removeComments": true,
      "sourceMap": true,
      "declaration": true,
      "outDir": "./lib",
      "allowJs": true,
      "moduleResolution": "Node"
    }
    }

    創建源碼文件夾 src,添加入口文件 index.ts

    創建文件夾 src,作為源碼文件夾,並在其中添加入口文件 index.ts。做完這些事情,我們的文件夾看起來應該是這樣:

    mf2e-test
    |- src
      |- index.ts
    |- .babelrc
    |- package.json
    |- rollup.config.js
    |- tsconfig.json

    這時候我們可以安裝框架邏輯需要的依賴了,執行下面命令:

    yarn add @satumjs/core @satumjs/simple-midwares @satumjs/midware-single-spa

    我們可以測試下 es6 的源碼是否能夠正常構建出 es5 的代碼,當然在此之前 package.json裏要添加一些 scripts

    "scripts": {
    "build": "rimraf lib && rollup -c",
    "build:w": "npm run build -- -w",
    "pub": "npm run build && npm publish --access public"
    },

    之後請在 index.ts裏添加如下代碼,然後執行命令 yarn build可以看到能正常生成代碼。

    import { register, start } from '@satumjs/core';
    export { register, start };

    那麼到此,恭喜你,初始化工作告一段落,我們提交一下代碼。

    添加必要的中間件

    @satumjs/simple-midwares是一些中間件的集合,包含了常用的中間件如緩存、沙箱、css 隔離、獲取掛載 dom、路由協調等。我們可以先使用其中的沙箱、獲取掛載 dom 這兩個中間件。然後我們還要使用 @satumjs/midware-single-spa中間件協助管理微應用的加載和卸載(你可以不使用該中間件,內置有實現簡單的 single-spa 功能,不過強烈建議使用)。index.ts 修改代碼如下:

    import { register, start, use } from '@satumjs/core';
    import {
    simpleSandboxMidware,
    mountNodeMidware,
    } from '@satumjs/simple-midwares';
    import singleSpaMidware from '@satumjs/midware-single-spa';
    
    use(simpleSandboxMidware);
    use(mountNodeMidware);
    use(singleSpaMidware);
    
    export { register, start };

    做完這一步,其實我們可以執行一下 yarn link,搞個本地示例工程出來直接看該框架的實際運行情況了。本地示例工程請參考 https://stackblitz.com/edit/github-gacap7,換一下依賴 @satumjs/coremf2e-test即可。

    添加微應用本地調試工具

    所謂調試工具,其本質是獲取到微應用的配置後,如果調試 whiteList 裏包含該微應用,則使用 whiteList 裏的 entry 地址,而這個地址可以指向本地啓動的微應用。所以我們繼續在 index.ts裏添加邏輯,我以 whiteList 存儲在 localStorage 裏為例。

    use((system, microApps, next) => {
    system.set(MidwareName.proxyEntry, (entry: string | string[], appName: string) => {
      const proxyMap = {};
      const proxySetting = localStorage.getItem('proxyEntries') || '';
      const proxyData = proxySetting.replace(/\s/g, '').split(',');
      
      proxyData.forEach((item) => {
        const [itemAppName, proxyUrl] = item.split('|');
        if (itemAppName && proxyUrl) proxyMap[itemAppName] = proxyUrl;
      });
    
      const currentApp = microApps.find((item) => item.name === appName);
      return currentApp && proxyMap[appName] ? proxyMap[appName] : entry;
    });
    next();
    });

    這樣你往瀏覽器的 localStorage 裏添加形如 micro-name|micro-entry-url規則,當框架加載微應用時就會使用本地啓動的。多個可以以 ,隔開。

    添加跨域服務,以方便遠程非跨域微應用集成

    好多時候,我們把微應用部署到開發、測試、預發等環境,可能是直接部署到了 cdn 上。這些環境並不支持跨域,進而影響集成。我們可以自己搞個跨域服務出來,做一下中轉,然後方便微應用集成。框架層面上也要做下支持,其實本質是遇到微應用的 entry,為其加一層跨域中轉請求。

    use((system, _, next) => {
    system.set(MidwareName.urlOption, {
      corsRule: `https://cors-server-test.com/?target=${corsRuleLabel}`,
    });
    next();
    });

    代碼編寫到這個地方,恭喜你,微前端框架部分就完成了。不過作為框架,需要讓用户可以傳一些參數進來,控制一些中間件的行為。整體代碼如下:

    import {
    register,
    start as coreStart,
    use,
    MidwareName,
    corsRuleLabel,
    KeyObject,
    } from '@satumjs/core';
    import {
    simpleSandboxMidware,
    mountNodeMidware,
    simpleCacheMidware,
    } from '@satumjs/simple-midwares';
    import singleSpaMidware from '@satumjs/midware-single-spa';
    
    use((system, microApps, next) => {
    system.set(
      MidwareName.proxyEntry,
      (entry: string | string[], appName: string) => {
        const proxyMap = {};
        const proxySetting = localStorage.getItem('proxyEntries') || '';
        const proxyData = proxySetting.replace(/\s/g, '').split(',');
    
        proxyData.forEach((item) => {
          const [itemAppName, proxyUrl] = item.split('|');
          if (itemAppName && proxyUrl) proxyMap[itemAppName] = proxyUrl;
        });
    
        const currentApp = microApps.find((item) => item.name === appName);
        return currentApp && proxyMap[appName] ? proxyMap[appName] : entry;
      }
    );
    next();
    });
    
    function start(options: KeyObject<any>) {
    const { enableCache, corsServerUrl, ...opts } = options || {};
    
    if (enableCache) use(simpleCacheMidware, opts);
    
    use((system, _, next) => {
      system.set(MidwareName.urlOption, {
        corsRule: `${corsServerUrl}?target=${corsRuleLabel}`,
      });
      next();
    });
    
    use(simpleSandboxMidware, opts);
    use(mountNodeMidware, opts);
    use(singleSpaMidware, opts);
    
    coreStart(opts);
    }
    
    export { register, start };

微前端框架示例倉庫請訪問 https://github.com/satumjs/mf2e-test

創建配置中心

最簡單的是,發一個 json 文件到 cdn。當然可以使用 nodejs 搭建一個可視化編排 json 的配置中心出來,這樣可以做到不用修改代碼,即可上下線微應用。

物料市場

可以使用業界開源的 x-design 來做基礎組件庫,根據業務定製一些業務組件出來。這些組件可以在微應用裏通過共享,分發到其他子應用。

低代碼平台

需要自行實現該平台,然後基於 satumjs 強悍的集成能力,把分散的頁面集成到一個網站下。

後續

本文通過代碼的方式一步步定製出一套微前端框架(示例倉庫 https://github.com/satumjs/mf2e-test),然後又把該框架周邊最常用的工具的實現展現了出來。還有一些工程板塊,可以自行搭建,也可以使用公司或開源的。
如果大家對 Satum 感興趣,歡迎訪問其官網 https://satumjs.github.io/website 瞭解更多。如果對中間件和插件感興趣,官網上也有一些資料。建議大家加微信羣或釘釘羣,方便後續交流。感謝大家耐心閲讀這篇文章~

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.