一些微前端架构的实践总结

一些微前端架构的实践总结前言 在平时接触的项目中需要用到微前端架构解决巨石应用的问题 在经过一段时间的调研和实践中总结出一些微前端架构的经验

欢迎大家来到IT世界,在知识的湖畔探索吧!

前言:在平时接触的项目中需要用到微前端架构解决巨石应用的问题。在经过一段时间的调研和实践中总结出一些微前端架构的经验。

一些微前端架构的实践总结



欢迎大家来到IT世界,在知识的湖畔探索吧!

技术选择

  • 主应用:react(基于umi和antd-pro)
  • 子应用:react(基于umi搭建)、vue(基于vue-cli搭建) ——(举两种常用的技术栈,其他技术栈以后再补充)

基础配置

主应用qiankun配置(umi项目)

使用@umijs/plugin-qiankun配置

该方法适合子应用都是基于umi架构的react项目,如果子应用是vue项目(项目复杂度比较高),用该插件配置的主应用会出现切换vue子应用时,因为js沙箱失效导致404问题。

  • 安装插件
  • yarn add qiankun yarn add @umijs/plugin-qiankun -D

    欢迎大家来到IT世界,在知识的湖畔探索吧!

  • 注册子应用
  • 欢迎大家来到IT世界,在知识的湖畔探索吧!// config/config.ts export default defineConfig({ .... // 其他配置代码 qiankun: { master: { apps: [ { name: 'sub-app-1', entry: '//localhost:8001' }, { name: 'sub-vue-app', entry: '//localhost:9528' } ] } } })
  • 装载子应用
  • // config/routes.ts export default [ .... // 其他路由配置 { name: 'sub-app-1', icon: 'smile', path: '/sub-app-1', microApp: 'sub-app-1', microAppProps: { className: 'sub-app-1', }, }, { name: 'sub-vue-app', icon: 'smile', path: '/sub-vue-app', microApp: 'sub-vue-app', microAppProps: { className: 'sub-vue-app', }, }, ]

    不使用@umijs/plugin-qiankun配置,直接用qiankun提供的配置方法

    该方法兼容不同技术栈的子应用

  • 安装插件
  • 欢迎大家来到IT世界,在知识的湖畔探索吧!yarn add qiankun
  • 注册子应用
  • // app.tsx import { registerMicroApps, setDefaultMountApp, start } from 'qiankun'; const microAppsOptions = [ { name: 'sub-app-1', entry: '//localhost:8001', container: '#subapp-container', activeRule: '/sub-app-1', className: 'sub-app-1', }, { name: 'sub-vue-app', entry: '//localhost:9528', container: '#subapp-container', activeRule: '/sub-vue-app', className: 'sub-vue-app', }, ]; registerMicroApps(microAppsOptions); setDefaultMountApp('/sub-app-1'); .... // 添加子应用渲染容器 export const layout: RunTimeLayoutConfig = ({ initialState }) => { .... childrenRender: (children) => { let activeClassName = ''; microAppsOptions.forEach((item) => { if (history.location.pathname.startsWith(item.activeRule)) { activeClassName = item.className; } }); return ( <> {children}{' '} {history.location.pathname !== loginPath && ( <div id="subapp-container" className={activeClassName} /> )} </> ); }, }
  • 装载子应用
  • 欢迎大家来到IT世界,在知识的湖畔探索吧!// config/routes.ts export default [ .... // 其他路由配置 { name: 'sub-app-1', icon: 'smile', path: '/sub-app-1', microApp: 'sub-app-1', }, { name: 'sub-vue-app', icon: 'smile', path: '/sub-vue-app', microApp: 'sub-vue-app', }, ] 

    子应用qiankun配置

    vue项目

    • 入口文件main.js
    // 声明一个变量,可以用于卸载
    let instance: any = null;
    let router: any = null;
    // 挂载到自己的html中,基座会拿到这个挂载后的html插入进去
    function render(props = {}) {
      const container:any = (props as any).container;
    
      router = new VueRouter({
        base: (window as any).__POWERED_BY_QIANKUN__ ? '/sub-vue-app/' : '/',
        mode: 'history',
        routes,
      });
    
      instance = new Vue({
        router,
        store,
        render: h => h(App)
      }).$mount(container ? container.querySelector('#app') : '#app')
    }
    
    // // webpack打包公共文件路径
    if ((window as any).__POWERED_BY_QIANKUN__) {
       __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
    // 独立运行
    if (!(window as any).__POWERED_BY_QIANKUN__) {
      render()
    }
    
    // 子组件的协议,必须暴露三个函数
    export async function bootstrap(props: any) {
      console.log('bootstrap函数:', props)
    }
    export async function mount(props: any) {
      console.log('mount函数:', props)
      render(props)
    }
    export async function unmount(props: any) {
      console.log('unmount函数:', props)
      instance.$destroy()
      instance = null
    }
    • 配置文件vue.config.js
    欢迎大家来到IT世界,在知识的湖畔探索吧!const port = process.env.port || process.env.npm_config_port || 9528 // dev port const { name } = require('./package.json'); module.exports = { devServer: { port, headers: { 'Access-Control-Allow-Origin': '*' } }, configureWebpack: { output: { library: `${name}`, libraryTarget: 'umd', // 把微应用打包成 umd 库格式 jsonpFunction: `webpackJsonp_${name}`, } }, }

    react项目(umi项目)

    • 安装 qiankun 插件 @umijs/plugin-qiankun
    yarn add @umijs/plugin-qiankun -D
    • 插件注册(.umirc.ts)
    欢迎大家来到IT世界,在知识的湖畔探索吧!export default defineConfig({ ..... // 其他配置代码 qiankun: { slave: {} } });
    • 配置运行时生命周期钩子(可选)

    插件会自动为你创建好 qiankun 子应用需要的生命周期钩子,但是如果你想在生命周期期间加一些自定义逻辑,可以在子应用的 src/app.ts 里导出 qiankun 对象,并实现每一个生命周期钩子,其中钩子函数的入参 props 由主应用自动注入。

    export const qiankun = { // 应用加载之前 async bootstrap(props) { console.log('app1 bootstrap', props); }, // 应用 render 之前触发 async mount(props) { console.log('app1 mount', props); }, // 应用卸载之后触发 async unmount(props) { console.log('app1 unmount', props); }, };

    react项目(ant-design-pro项目)

    • 安装 qiankun 插件 @umijs/plugin-qiankun
    欢迎大家来到IT世界,在知识的湖畔探索吧!yarn add @umijs/plugin-qiankun -D
    • 当使用ant-design-pro搭建项目时,默认运行会加上以项目名称为命名的根路径(ROUTER_BASE),接入主应用及部署时需要确认子应用单独运行时的根路径名称(ROUTER_BASE)和在主应用里的根路径名称(MICRO_ROUTE_BASE)
    // src/consts.ts export const MICRO_ROUTER_BASE = '/sub-app-1/'; export const ROUTER_BASE = '/app-1/';
    • 插件注册(config/config.ts)
    欢迎大家来到IT世界,在知识的湖畔探索吧!.... import { MICRO_ROUTER_BASE, ROUTER_BASE } from '../src/consts'; const { REACT_APP_ENV, UMI_ENV } = process.env; const isProd = UMI_ENV === 'prod'; const logoPath = isProd ? ROUTER_BASE : '/'; defaultSettings.logo = logoPath + 'logo.svg'; export default defineConfig({ ... manifest: { basePath: ROUTER_BASE, }, qiankun: { slave: {}, }, define: { MICRO_ROUTER_BASE, ROUTER_BASE, }, ... });
    • 全局配置ROUTER_BASE(src/pages/document.ejs)
    ... <body> <script> if (window.__POWERED_BY_QIANKUN__) { window.routerBase = "<%= context.config.define.MICRO_ROUTER_BASE %>" } else { window.routerBase = "<%= context.config.define.ROUTER_BASE %>" } </script> .... </body>

    主子应用实现通信配置

    主应用umi项目使用@umijs/plugin-qiankun配置

    向umi子应用通信(使用@umijs/plugin-qiankun提供的通信方式)

    • 在主应用app.ts中导出通信方法
    欢迎大家来到IT世界,在知识的湖畔探索吧!export function useQiankunStateForSlave(): object { const { initialState, setInitialState } = useModel('@@initialState'); return { initialState, setInitialState, }; }
    • 在子应用组件中使用qiankunStateFromMaster获取主应用数据
    import { useModel } from 'umi'; import styles from './index.less'; function IndexPage() { // 获取主应用的登录信息 const masterProps = useModel('@@qiankunStateFromMaster'); const { initialState } = masterProps; return ( <> <h1 className={styles.title}>Page index</h1> <div>从主应用获取用户名:{initialState?.currentUser?.name}</div> <div>从主应用获取的色值:{initialState?.currentTheme?.theme?.primaryColor}</div> </> ); } export default IndexPage;

    向vue子应用通信(使用qiankun提供的通信方式)

    • 在主应用src目录下新建action.ts文件
    欢迎大家来到IT世界,在知识的湖畔探索吧!import { initGlobalState, MicroAppStateActions } from 'qiankun'; // 初始化 state const actions: MicroAppStateActions = initGlobalState({ color: '#1890ff' }); actions.onGlobalStateChange((state, prev) => { console.log(state, prev); }); actions.offGlobalStateChange(); export default actions;
    • 在子应用入口文件main.js配置

    主应用传递的数据initialState、监听数据方法onGlobalStateChange、设置数据方法setGlobalState都会以参数的形式由qiankun生命周期mount函数传递给全局的render函数,配合vuex修改全局状态。

    /* eslint-disable */
    import './public-path.js';
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import App from './App.vue';
    import routes from './router';
    import store from './store';
    
    Vue.config.productionTip = false;
    
    // 声明一个变量,可以用于卸载
    let instance: any = null;
    let router: any = null;
    // 挂载到自己的html中,基座会拿到这个挂载后的html插入进去
    function render(props = {}) {
    
      const container:any = (props as any).container;
      const currentUser = (props as any)?.initialState?.currentUser;
      const currentTheme = (props as any)?.initialState?.currentTheme;
    
      store.dispatch('user/getInfo', {name: currentUser?.name || ''});
    
      store.dispatch('theme/getColor', currentTheme?.theme?.primaryColor || '');
      
      (props as any).onGlobalStateChange((state:any, prev:any) => {
        // state: 变更后的状态; prev 变更前的状态
        console.log(state, prev);
        store.dispatch('theme/getColor', state?.color || '')
      });
    
      router = new VueRouter({
        base: (window as any).__POWERED_BY_QIANKUN__ ? '/sub-vue-app/' : '/',
        mode: 'history',
        routes,
      });
    
      instance = new Vue({
        router,
        store,
        render: h => h(App)
      }).$mount(container ? container.querySelector('#app') : '#app')
    }
    
    // // webpack打包公共文件路径
    // if ((window as any).__POWERED_BY_QIANKUN__) {
    //   __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    // }
    
    // 独立运行
    if (!(window as any).__POWERED_BY_QIANKUN__) {
      render()
    }
    
    // 子组件的协议,必须暴露三个函数
    export async function bootstrap(props: any) {
      console.log('bootstrap函数:', props)
    }
    export async function mount(props: any) {
      console.log('mount函数:', props)
      render(props)
    }
    export async function unmount(props: any) {
      console.log('unmount函数:', props)
      instance.$destroy()
      instance = null
    }

    主应用umi项目不使用@umijs/plugin-qiankun配置,直接用qiankun官方文档提供的方式通信

    向umi子应用通信

    • 在主应用src目录下新建action.ts文件
    欢迎大家来到IT世界,在知识的湖畔探索吧!import { initGlobalState, MicroAppStateActions } from 'qiankun'; import storage from 'store'; import { MAIN_APP_THEME } from '@/consts'; // 初始化 state const actions: MicroAppStateActions = initGlobalState({ color: '#1890ff' }); store.set(MAIN_APP_THEME, '#1890ff') actions.onGlobalStateChange((state, prev) => { console.log(state, prev); }); actions.offGlobalStateChange(); export default actions;
    • 在子应用组件中使用qiankunStateFromMaster获取变化后的主应用变化的数据,用localStorage获取主应用用localStorage存储的初始值
    import { useModel, useEffect } from 'umi'; import storage from 'store'; import { MAIN_APP_THEME } from '@/consts'; import styles from './index.less'; function IndexPage() { const { initialState, setInitialState } = useModel('@@initialState'); // 获取主应用的登录信息 const masterProps = useModel('@@qiankunStateFromMaster'); useEffect(() => { if (masterProps) { const storageColor = storage.get(MAIN_APP_THEME); setInitialState((s) => ({ ...s, settings: { ...((initialState && initialState.settings) || {}), currentTheme: {primaryColor: storageColor}, }, })); const { onGlobalStateChange } = masterProps; if (onGlobalStateChange) { onGlobalStateChange((state: any) => { // state: 变更后的状态; prev 变更前的状态 const currentTheme = {primaryColor: state.color}; setInitialState((s) => ({ ...s, settings: { ...((initialState && initialState.settings) || {}), currentTheme, }, })); } } } }, []) return ( <div> <h1 className={styles.title}>Page index</h1> <div>从主应用获取的色值:{initialState?.currentTheme?.primaryColor}</div> </div> ); } export default IndexPage;

    向vue子应用通信(使用qiankun提供的通信方式)

    • 在主应用src目录下新建action.ts文件
    欢迎大家来到IT世界,在知识的湖畔探索吧!import { initGlobalState, MicroAppStateActions } from 'qiankun'; import storage from 'store'; import { MAIN_APP_THEME } from '@/consts'; // 初始化 state const actions: MicroAppStateActions = initGlobalState({ color: '#1890ff' }); store.set(MAIN_APP_THEME, '#1890ff') actions.onGlobalStateChange((state, prev) => { console.log(state, prev); }); actions.offGlobalStateChange(); export default actions;
    • 主应用传递的监听数据方法onGlobalStateChange、设置数据方法setGlobalState 都会已参数的形式由qiankun生命周期mount函数传递给全局的render函数,配合vuex修改全局状态, 用localStorage获取主应用用localStorage存储的初始值
    /* eslint-disable */
    import './public-path.js';
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import App from './App.vue';
    import routes from './router';
    import store from './store';
    import { MAIN_APP_THEME } from './consts';
    
    Vue.config.productionTip = false;
    
    // 声明一个变量,可以用于卸载
    let instance: any = null;
    let router: any = null;
    // 挂载到自己的html中,基座会拿到这个挂载后的html插入进去
    function render(props = {}) {
    
      const container:any = (props as any).container;
      // const currentUser = (props as any)?.initialState?.currentUser;
      const currentColor = store.get(MAIN_APP_THEME);
    
      // store.dispatch('user/getInfo', {name: currentUser?.name || ''});
    
      store.dispatch('theme/getColor', currentColor || '');
      
      (props as any).onGlobalStateChange((state:any, prev:any) => {
        // state: 变更后的状态; prev 变更前的状态
        console.log(state, prev);
        store.dispatch('theme/getColor', state?.color || '')
      });
    
      router = new VueRouter({
        base: (window as any).__POWERED_BY_QIANKUN__ ? '/sub-vue-app/' : '/',
        mode: 'history',
        routes,
      });
    
      instance = new Vue({
        router,
        store,
        render: h => h(App)
      }).$mount(container ? container.querySelector('#app') : '#app')
    }
    
    // // webpack打包公共文件路径
    // if ((window as any).__POWERED_BY_QIANKUN__) {
    //   __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    // }
    
    // 独立运行
    if (!(window as any).__POWERED_BY_QIANKUN__) {
      render()
    }
    
    // 子组件的协议,必须暴露三个函数
    export async function bootstrap(props: any) {
      console.log('bootstrap函数:', props)
    }
    export async function mount(props: any) {
      console.log('mount函数:', props)
      render(props)
    }
    export async function unmount(props: any) {
      console.log('unmount函数:', props)
      instance.$destroy()
      instance = null
    }

    本文为原创内容,若转载请注明出处,转发感激不尽。

    免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/146043.html

    (0)
    上一篇 47分钟前
    下一篇 32分钟前

    相关推荐

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注

    联系我们YX

    mu99908888

    在线咨询: 微信交谈

    邮件:itzsgw@126.com

    工作时间:时刻准备着!

    关注微信