微前端落地,懸着的心終於放下了

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!css

前言

產品提出系統要模塊化,經過不一樣的模塊組合構建咱們的應用。這不是最近很火的微前端嗎,和組內的小夥伴通過一番溝通,帶着壓力開始搞起來了,通過三個月的迭代開發,在業務開發的同時,把兩個模塊遷移成了子應用。系統上線了,懸着的心終於落下了。html

現有的系統情況分析

現有的系統是一個獨立的項目,採用umi框架開發,經過文件夾區分各大業務模塊。公共組經過npm包的方式去管理。因爲系統迭代時間比較久,架構上遺留的幾個問題須要解決前端

  • 子應用採用ant4.x (antd用的3.x版本,升級antd4.x難度比較大,主要是Form變更比較大)
  • hooks替換redux部分state (redux stroe中的state差很少有一百多個,一些本不該該放在Redux裏的狀態也放到了裏面)
  • 統一封裝requst(拆分紅獨立http和socket請求庫,支持hooks)

第一個落地版本的架構

image.png

引入微前端要解決的問題

  • 封裝腳手架
  • 組件庫升級,支持antd4.x
  • 主子應用通訊
  • 主應用共享一些實例供子應用使用
  • Redux Store得部分數據須要共享,怎麼作限制
  • 子應用支持國際化,國際化內容動態加載
  • 子應用跳轉父應用
  • 樣式隔離

改造正式開始

微前端框架用qiankun, 因爲咱們用的框架是umi, 對應提供了插件 @umijs/plugin-qiankun。webpack

主應用

  1. 在umi配置文件開啓qiankun配置
qiankun: {
    master: {},
  },
複製代碼
  1. 加載子應用配置
function microApp(entryPrefix) {
  return [
    {
      name: 'app1', //惟一 id
      entry: entryPrefix ? `${entryPrefix}/app1` : '//localhost:3000',
    },
    {
      name: 'app2', //惟一 id
      entry: entryPrefix ? `${entryPrefix}/app2` : '//localhost:3001',
    },
    ... //其它子應用
  ];
}
export default microApp;
複製代碼
  1. 配置激活子應用路由
const router = [
  {
    path: '/main/app/rouer/app1', // (/main/app/rouer/)主應用部分路由
    microApp: 'app1',
    microAppProps: {
      autoSetLoading: true,
      className: 'appClassName',
      wrapperClassName: 'wrapperClass',

    },
  },
  {
    path: '/main/app/rouer/app2',
    microApp: 'app2',
    microAppProps: {
      autoSetLoading: true,
      className: 'appClassName',
      wrapperClassName: 'wrapperClass',

    },
  }
  ... //其它子應用
  ];

export default router;
複製代碼
  1. 在入口文件app.js導出qiankun對象
import microApp from './microApp';
import router from './router';

export const qiankun = new Promise((resolve) => {
  const entryPrefix = process.env.NODE_ENV === 'production' ? window.location.origin : null;
  const res = microApp(entryPrefix);
  resolve(res);
}).then(apps => {
  return {
    apps,
    routes: router,
  };
});
複製代碼

子應用

  1. 在umi中開啓qiankun配置
qiankun: {
    slave: {},
  },
複製代碼
  1. 在入口文件app.js導出qiankun的生命週期
export const qiankun = {
    // 應用加載以前
    async bootstrap(props) {
     init(props);
    },
    // 應用 render 以前觸發
    async mount(props) {
    },
    // 應用卸載時觸發
    async unmount(props) {
    },
};

複製代碼

基於qiankun和umi提供的qiankun插件仍是比較方便的運行起來。下面咱們一塊兒看下上面的問題怎麼解決。nginx

解決實際遇到的一些問題

組件庫升級,支持antd4.x

咱們採用施漸進式重構,咱們須要一種增量升級的能力,先讓新舊代碼和諧共存,再逐步轉化舊代碼,直到整個重構完成。目前咱們把基於antd3.x版本的組件庫升級到4.x。兩個版本同時更新一段時間,兩個版本能夠共存,遷出來的子應用使用antd 4.x,保證新的特性咱們能使用web

主子應用通訊

umi qiankun plugin提供了主子應用通訊的方式npm

在主應用入口導出useQiankunStateForSlave方法

export function useQiankunStateForSlave() {
  const [masterState, setMasterState] = useState({});

  return {
    SlaveSDK,
    getStore,
    // masterState,
    setMasterState,
  };
}
複製代碼
子應用中會自動生成一個全局 model,能夠在任意組件中獲取主應用透傳的 props 的值。
import { useModel } from 'umi';

function MyPage() {
  const masterProps = useModel('@@qiankunStateFromMaster');
  return <div>{JSON.stringify(masterProps)}</div>;
}
複製代碼

該方式主子應用通訊沒問題,嵌套多層子應用使用不是很方便,後期咱們會對它進行改造redux

主應用共享一些實例供子應用使用

在微前端架構當中,不推薦應用共享實例,由於這樣子應該很難作到獨立開發,獨立部署,應用會有耦合。但因爲咱們是對系統作拆分,一些公用的功能,好比彈框查看日誌,數據預覽等功能會先用父應用的功能,須要共享。這裏咱們封裝了SDK供子應用使用。後期若是要改造這塊成獨立開發,獨立部署。咱們實現對應的接口,動態注入,就能夠實現。bootstrap

  • 定義log要共享的方法
class Log {
  show = (params) => 
      // 具體業務邏輯
  }

  showOther = (params) => {
      // 具體業務邏輯
  }
}

export default new Log();

複製代碼
  • 在主應用中建立SlaveSDK實例
import intl from 'utils/intl';
import { history } from 'umi';
import { getParams } from 'utils/util';
import Log from './log';

class SlaveSDK {
  constructor() {
    this.log = Log;
    this.intl = intl;
    this.router = {
      history, // 主應用的history對象
      getParams, // 獲取主應用的參數
    };
    ... 其它實例共享
  }
}

export default new SlaveSDK();

複製代碼
  • 經過數據通訊傳遞給子應用
import SlaveSDK from './SlaveSDK';
export function useQiankunStateForSlave() {
  return {
    SlaveSDK,
  };
}
複製代碼
  • 子應用在生命週期裏獲取sdk並初始化保存
export const qiankun = {
    // 應用加載以前
    async bootstrap(props) {
         props?.SlaveSDK && setSDK(props.SlaveSDK);
    },
};

複製代碼

Redux Store部分數據須要共享,怎麼作限制

子應用業務有可能須要Redux Store的部分數據,但咱們不能把整個Store都暴露給子應用,在應用使用的時候作些限制,這裏咱們用到了代理對象去實現後端

const allowGet = { auth: '', user: '' }; // 須要共享的key
    const state = getReduxStore();
    const proxy = new Proxy(state, {
      get(target, property) {
        if (property in allowGet) {
          return target[property];
        }
        return undefined;
      },
    });
    return proxy;
  };
複製代碼

子應用支持國際化,國際化內容動態加載

咱們經過共享實例,經過SDK的方式後去獲取國際化實例intl,在子應用初始化的時候去加載國際化。

/**
 * 國際化初始化
 */
const intlInit = async () => {
  // load語言 文件
  const result = await import('./locales');
  const intl = await import('./utils/intl');
  try {
    moment.locale(intl?.default?.getIntlLang?.() || 'zh_CN');
    intl?.default?.load(result.default);
  } catch (error) {
    console.error('intl error', error);
  }
};

複製代碼

子應用跳轉父應用

在子應用中跳轉父應用的路由,也是經過把父應用的router的history對象傳遞給子應用

import { history } from 'umi';
class SlaveSDK {
  constructor() {
    this.router = {
      history, // 主應用的history對象
      getParams, // 獲取主應用的參數
    };

  }
}
複製代碼

樣式隔離

樣式隔裏子應用咱們用的是cssModule,編譯的時候會自動生成惟一的key。主要問題是antd,由於咱們既加載了antd3,又加載了antd4,致使樣式會有衝突。我在antd4的編譯的時候改前綴。配置以下

import { ConfigProvider } from 'antd';

// 彈框的前綴配置
 ConfigProvider.config({
    prefixCls: 'my-ant',
 });

// 組件的配置
<ConfigProvider prefixCls="my-ant">
</ConfigProvider>
複製代碼

部署

系統採用nginx部署,是在同一個Server下經過不一樣的path來處理。配置以下

server {
  listen       8080;
  server_name  localhost;


  location / {
    root   html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }


  location /app1 {
    root   html;
    index  index.html index.htm;
    try_files $uri $uri/ /app1/index.html;
  }


  location /app2 {
    root   html;
    index  index.html index.htm;
    try_files $uri $uri/ /app2/index.html;
  }
}
複製代碼

思考

目前初版微前端已經上線,下個版本有可能會有嵌套多層子應用。如下問題須要解決

  • 資源隔離
  • 嵌套多層應用共享狀態
  • webpack5 Module Federation和qiankun結合作資源隔離
  • 隨着子應用的增多,腳手架加強

結束語

這個版本,腳手架完成了基礎的功能,能根據模塊去生成咱們的子應用。後期加強以後,能生成主應用和子應用,會分享這塊。以上是微前端落地這塊總結的一些問題,若有問題,歡迎指正。

參考

相關文章
相關標籤/搜索