美文网首页
微前端调研文档

微前端调研文档

作者: cs0710 | 来源:发表于2021-01-07 14:11 被阅读0次

    学习背景

    随着管理后台项目功能模块逐渐增多,直接带来的影响有以下几点:

    • 各功能模块共享同一个package.json配置,若包版本进行升级,需所有模块全量回归测试,造成时间成本的数量级增加
    • 主应用项目的编译、构建和打包时间都会随着项目体积的增加而增加,降低开发效率
    • 希望可以和主应用最大化解耦和沙箱隔离,并且做到独立开发、独立部署,将业务和主应用解耦,便于开发维护
    • 脱离技术栈的限制,三大框架可以组合应用,但还是推荐使用统一技术栈接入
    • 不受npm包版本限制,可以按需安装,且只和子应用有关

    iframe集成应用(废弃)

    • 整个应用刷新
    • ⼦应⽤会丢失状态
    • 单页应用体验差
    • url无记录,刷新页面,不够记忆
    • 显示区不易控制,尤其是modal,存在一定局限性
    • 主子通信需要约定好事件名,不够灵活

    微前端是什么

    微前端是一种类似于微服务的架构,它的核心思想就是将一个大的单体应用基于业务能力拆分为一组小的服务,每个服务都是独立的进程且能独立部署,无需统一的技术栈进行集中化管理,只需要进行轻量级的通信就可以完成业务诉求。当然拆分还需要一套成熟的通信机制串联起所有的应用,既能保证应用自治又能保证应用联系,以更好的协同度共同提升开发效率。

    Eg: 一个多插座插盘,连接着n个家用电器,每个可独立运行。

    微前端模式

    • 基座模式 通过一个主应用,来管理其它应用。设计难度小,方便实践,但是通用度低。
    • 自组织模式 应用之间是平等的,不存在相互管理的模式。设计难度大,不方便实施,但是通用度高。

    微前端为项目带来的好处

    • 技术栈无关---和技术栈无用,可以使用不同的技术栈组合应用,但推荐同一种技术栈,减少学习成本
    • 单一职责---每个前端应用可以只关注于自己所需要完成的功能
    • 应用自治---和主应用解耦,独立npm包管理,独立构建,独立部署
    • 加快构建---单个应用体积减小,加快构建速度
    • 调试友好---可独立运行,方便调试
    • 用户体验---统一用户体验,尤其在页面刷新的时候

    什么情况下适用微前端

    • 公司管理后台业务菜单越来越多,且功能越来越重
    • 业务模块功能各自独立,且各自团队维护
    • 项目生命周期长,代码量庞大
    • 主应用每次编译、打包随体积增大越来越慢
    • 某个包升级后,需全量回归各模块

    微前端的技术框架有哪些

    • Single-spa
    • Qiankun
    • Mooa
    • Ice

    技术栈选型

    • Single-spa实现了路由劫持(重写hashChange/popState)和应⽤加载,但是没有样式隔离和js沙箱隔离。loadApp函数需要⼿动创建script标签、动态加载⼦应⽤已知的打包⽂件,配置繁琐,适合打包文件比较少,且文件名固定的项目。如果dist中有很多chunk文件且已hash命名,就不适用

    • Mooa:仅适用于Angular技术栈

    • Ice:依赖于Ice cli生成的脚手架,对项目路由配置的侵入性较大,修改大量修改

    • Qiankun:

      • 除了具备路由劫持和应⽤加载,还实现了样式隔离和js沙箱隔离

      • 支持预加载和手动加载模式、支持单实例模式,还支持多实例模式同时激活,一起开启

      • 大厂已有成熟的产品接入,社区活跃,参考度高

      • 主应用整体接入成本小,与Umi版本无关,开箱即用Api

      • 与技术栈无关

    • 主应用不使用umi/qiankun

      • 我们后台是约定式路由方案,这就需要依赖umi 3版本umi包导出的MicroApp组件,目前我们的项目时umi2.x,需要改造
      • umi/qiankun注册子应用是在config.js文件(webpack文件),对于向子应用业务数据的传递,需要借助useQiankunStateForSlave函数
      • 如果需要强制使用话
        • 升级umi版本到3.x
        • 降低umi/qiankun到1.x版本,但是MicroApp组件无用使用(依赖于umi3),只能适用于自定义路由的项目,和我们现在的约定时路由是相悖的。而且主子需要两边保持路由 base 上的一致,否则可能会出现 base 配置不一致导致 url 无法被微应用识别,从而无法正常加载微应用的问题,增加主子应用的复杂度。
    • 主应用使用qiankun

      • 和umi版本无关,省级umi也不需做任何改动
      • 文档比较完善,参考度高

    app

    app状态

    // 实现子应用的注册、挂载、切换、卸载功能
    
    /**
     * 子应用状态
     */
    // 子应用注册以后的初始状态
    const NOT_LOADED = 'NOT_LOADED'
    // 表示正在加载子应用源代码
    const LOADING_SOURCE_CODE = 'LOADING_SOURCE_CODE'
    // 执行完 app.loadApp,即子应用加载完以后的状态
    const NOT_BOOTSTRAPPED = 'NOT_BOOTSTRAPPED'
    // 正在初始化
    const BOOTSTRAPPING = 'BOOTSTRAPPING'
    // 执行 app.bootstrap 之后的状态,表是初始化完成,处于未挂载的状态
    const NOT_MOUNTED = 'NOT_MOUNTED'
    // 正在挂载
    const MOUNTING = 'MOUNTING'
    // 挂载完成,app.mount 执行完毕
    const MOUNTED = 'MOUNTED'
    const UPDATING = 'UPDATING'
    // 正在卸载
    const UNMOUNTING = 'UNMOUNTING'
    // 以下三种状态这里没有涉及
    const UNLOADING = 'UNLOADING'
    const LOAD_ERROR = 'LOAD_ERROR'
    // 在某个生命出错跳过,不影响其他的app
    const SKIP_BECAUSE_BROKEN = 'SKIP_BECAUSE_BROKEN'
    

    开发中涉及到的生命周期

    // 注册应用后,涉及到的生命周期
    • bootstrap(必选)-------> 第一次渲染到 dom 之前,只调用一次
    • mount(必选)----------> 只要 activeWhen 返回 true && 没在 dom 上时
    • unmount(必选)--------> 只要 activeWhen 返回 false && 在 dom 上时
    

    single-spa生命周期简介

    image-20201104192916644

    single-spa篇

    single-spa说明

    single-spa参考文档

    single-spa实现了路由劫持和应⽤加载,但是没有样式隔离和js沙箱隔离。loadApp函数需要⼿动创建script标签、动态加载⼦应⽤的打包⽂件。靠的是协议接⼊(⼦应⽤必须导出bootstrap,mount,unmount⽅法)。

    single-spa微前端实践

    一、create-react-app+vue脚手架配置

    base app: create-react-app

    子应用1:create-react-app

    子应用2: vue

    具体配置(主:single-spa 子:single-spa)
    1. 主应用安装配置

      yarn add single-spa 或 npm i single-spa -S
      
    2. 注册子应用并开启

      // base-app/src/index.js
      
      // 引入qiankun
      import { registerApplication, start } from 'single-spa';
      ...
      ...
      // 声明异步加载js方法
      function loadScript(url) {
        return new Promise((resolve, reject) => {
          const script = document.createElement('script');
          script.src = url;
          script.onload = resolve;
          script.onerror = reject;
          document.head.appendChild(script);
        })
      }
      
      
      // 注册react子应用
      registerApplication(
        'singleSpaReactApp',
        async () => {
          // 在匹配到路由时,会进入该加载函数中
          console.log('匹配到路由开始加载了...')
      
          // 缺陷:
            // 不够灵活,不能动态加载script标签
            // 样式不隔离,没有js沙箱机制
          await loadScript('//localhost:7100/static/js/bundle.js'); // 公共模块js代码
          await loadScript('//localhost:7100/static/js/0.chunk.js'); // 公共模块js代码
          await loadScript('//localhost:7100/static/js/main.chunk.js'); // 公共模块js代码
          console.log('window.reactApp', window.reactApp) // (window.子应用的library的名字)
          return window.reactApp;
        },
        location => location.pathname.startsWith('/react'),
        {
          aa: 1,
        },
      )
      
      // 注册vue子应用
      registerApplication(
        'singleSpaVueApp', // appName
        async () => {
          // 在匹配到路由时,会进入该加载函数中
          console.log('匹配到路由开始加载了...')
      
          // 缺陷:
            // 不够灵活,不能动态加载script标签
            // 样式不隔离,没有js沙箱机制
          await loadScript('//localhost:7200/js/chunk-vendors.js'); // 公共模块js代码
          await loadScript('//localhost:7200/js/app.js');
          console.log('window.vueApp', window.vueApp) // (window.子应用的library的名字)
          return window.vueApp; // 上面包含bootstrap mount unmount三个方法
        }, // 加载函数
        location => location.pathname.startsWith('/vue'), // 激活函数
        { // 自定义属性
          bb: 2,
        },
      )
      
      // 开启应用,子应用开始进行加载、初始化、挂载和卸载
      start();
      
      
    3. 子应用配置

      1. react子应用安装包

        yarn add single-spa-react 或 npm i single-spa-react -S
        
      2. react子应用配置

        // react-app/src/index.js
        
        
        // 引入single-spa-react
        import singleSpaReact from 'single-spa-react';
        
        // 开始对子应用配置
        const reactLifecycles = singleSpaReact({
          React,
          ReactDOM,
          rootComponent: App,
          domElementGetter: () => document.querySelector('#reactBox'),
          errorBoundary(err, info, props) {
            console.log('err, info, props', err, info, props);
            return (
              <div>This renders when a catastrophic error occurs</div>
            );
          },
        })
        
        // 处理公共路径和实例
        if (!window.singleSpaNavigate) {
          // 不是作为子应用时,可以独立启动
          ReactDOM.render(
            <React.StrictMode>
              <App />
            </React.StrictMode>,
            document.getElementById('root')
          );
        }
        
        export const bootstrap = props => {
          console.log('react bootstrap', props);
          return reactLifecycles.bootstrap(() => {});
        }
        export const mount = props => {
          console.log('react mount', props);
          return reactLifecycles.mount(() => {});
        }
        export const unmount = props => {
          console.log('react unmount', props);
          return reactLifecycles.unmount(() => {});
        }
        
      3. react子应用配置.env

        // react-app/.env
        
        
        PORT=7100
        BROSWER=none
        WDS_SOCKET_PORT=7100
        
      4. react子应用webpack配置

        // react-app/config-overrides.js
        
        
        module.exports = {
          webpack: (config) => {
            // 微应用的包名,这里与主应用中注册的微应用名称一致
            config.output.library = 'reactApp';
            // 将你的 library 暴露为所有的模块定义下都可运行的方式
            config.output.libraryTarget = 'umd';
            // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
            config.output.jsonpFunction = 'webpackJsonp_reactApp';
            // 设置输入公共路径
            config.output.publicPath = '//localhost:7100/';
            return config;
          },
        
          devServer: function (configFunction) {
            return function (proxy, allowedHost) {
              const config = configFunction(proxy, allowedHost);
              // 关闭主机检查,使微应用可以被 fetch
              config.disableHostCheck = true;
              // 配置跨域请求头,解决开发环境的跨域问题
              config.headers = {
                "Access-Control-Allow-Origin": "*",
              };
              // 配置 history 模式
              config.historyApiFallback = true;
        
              return config;
            };
          },
        };
        

      1. vue子应用安装包
      yarn add single-spa-vue 或 npm i single-spa-vue -S
      
      1. vue子应用配置
      // 引入single-spa-vue
      import singleSpaVue from 'single-spa-vue';
      
      const appOptions = {
        // 挂载到父级应用的id为vueApp的标签上
        el: '#vueBox',
        router,
        render: h => h(App)
      };
      
      
      const vueLifecycles = singleSpaVue({
        Vue,
        appOptions,
      });
      
      // 处理公共路径和实例
      if (window.singleSpaNavigate) {
        // 作为子应用,如果父应用引用我,动态设置公共路径
        __webpack_public_path__ = '//localhost:7200/'; // 后面必须加‘/’,相对当前应用的路径
      } else {
        // 不是作为子应用时,可以独立启动
        delete appOptions.el;
        new Vue(appOptions).$mount('#app');
      }
      
      
      // 子应用暴露三个生命周期函数,接入协议,父应用会调用这些方法
      export const bootstrap = props => {
        console.log('vue bootstrap', props);
        return vueLifecycles.bootstrap(() => {});
      }
      export const mount = props => {
        console.log('vue mount', props);
        return vueLifecycles.mount(() => {});
      }
      export const unmount = props => {
        console.log('vue unmount', props);
        return vueLifecycles.unmount(() => {});
      }
      
      1. vue子应用webpack配置
      // 将子应用打包为lib,供父应用使用
      module.exports = {
        configureWebpack: {
          output: {
            library: 'vueApp',
            // umd格式会把bootstrap/mount/unmount挂载到window.vueApp上
            libraryTarget: 'umd',
          },
          devServer: {
            port: 7200,
          }
        }
      }
      

    qiankun篇

    qiankun说明

    qiankun是基于single-spa,提供了开箱即⽤的API(Single-Spa + SandBox+import-html-entry),做到了技术栈⽆关,接⼊简单。

    ⼦应⽤可以独⽴构建,运⾏时动态加载,主⼦应⽤完全解耦,靠的是协议接⼊(⼦应⽤

    必须导出bootstrap,mount,unmount⽅法)。

    qiankun体系图

    image-20201102151103243

    qiankun微前端实践

    qiankun参考文档

    一、umi+create-react-app+vue 脚手架配置

    base app:umi

    子应用1: create-react-app

    子应用2: vue

    具体配置(主:qiankun 子:qiankun)
    1. 主应用安装qiankun

      yarn add qiankun 或 npm i qiankun -S
      
    2. 注册子应用并开启

      // base_app/src/layouts/BasicLayout.jsx
      useEffect(() => {
          // 注册微应用,必须确保唯一
          registerMicroApps([{
            // 子应用名称
            name: 'reactApp',
            // 子应用入口
            entry: '//localhost:7100',
            // 子应用挂在容器id/HTMLElement容器
            container: '#reactBox',
            // 子应用激活规则/函数 location => location.startsWith('/react')
            activeRule: '/react',
          }, {
            name: 'vueApp',
            entry: '//localhost:7200',
            container: '#vueBox',
            activeRule: '/vue',
            props: {myApp: 'vue'}
          }], {
            beforeMount: app => {
              console.log(1212, app);
            }
          });
        
           // 调用start,开始进行加载、初始化、挂在、卸载等操作
          start({
            // 是否预加载 默认是true
            prefetch: true,
            // 是否开启沙箱模式,默认为true
            sandbox: true,
            ...,
          });
        }, []);
      
    3. 添加子应用容器

      // base_app/src/layouts/BasicLayout.jsx
      
      // 声明并注册子应用入口和容器
      <Link to="/react" style={{ marginRight: 15 }}>react app</Link>
      <Link to="/vue">vue app</Link>
      <div id="reactBox"></div>
      <div id="vueBox"></div>
      
    4. 子应用配置(create-react-app)

      1. 安装启动脚本

        yarn add react-app-rewired -D
        
      2. 入口文件配置

        // micro_react/src/index.js
        
        // 有些时候我们希望直接启动微应用从而更方便的开发调试,你可以使用这个全局变量来区分当前是否运行在 qiankun 的主应用的上下文中:
        if (!window.__POWERED_BY_QIANKUN__) {
          render();
        } else {
          // 解决微应用加载的资源会 404
          // eslint-disable-next-line no-undef
          __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
        }
        /**
         * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
         * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
         */
        function render() {
          ReactDOM.render(
            <App />,
            document.getElementById('root-cra'),
          );
        }
        
        export async function bootstrap(props) {
          console.log('react app bootstraped', props);
        }
        /**
         * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
         */
        export async function mount(props) {
          console.log('mount', props);
          render();
        }
        /**
         * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
         */
        export async function unmount(props) {
          console.log('unmount', props)
          ReactDOM.unmountComponentAtNode(document.getElementById('root-cra'));
        }
        
      3. .env文件配置

        // micro_react/.env
        
        PORT=7100
        BROWSER=none
        
      4. 构建文件配置

        // micro_react/config-overrides.js
        
        module.exports = {
          webpack: (config) => {
            // 微应用的包名,这里与主应用中注册的微应用名称一致
            config.output.library = `reactApp`;
            // 将你的 library 暴露为所有的模块定义下都可运行的方式
            config.output.libraryTarget = "umd";
            // 按需加载相关,设置为 webpackJsonp_reactApp 即可
            config.output.jsonpFunction = `webpackJsonp_reactApp`;
        
            return config;
          },
        
          devServer: function (configFunction) {
            return function (proxy, allowedHost) {
              const config = configFunction(proxy, allowedHost);
              // 关闭主机检查,使微应用可以被 fetch
              config.disableHostCheck = true;
              // 配置跨域请求头,解决开发环境的跨域问题
              config.headers = {
                "Access-Control-Allow-Origin": "*",
              };
              // 配置 history 模式
              config.historyApiFallback = true;
              return config;
            };
          },
        };
        
    5. 子应用配置(vue)

      1. 入口文件配置

        // micro_vue/src/main.js
        
        let app = null;
        // 有些时候我们希望直接启动微应用从而更方便的开发调试,你可以使用这个全局变量来区分当前是否运行在 qiankun 的主应用的上下文中:
        if (!window.__POWERED_BY_QIANKUN__) {
          render();
        } else {
          // 解决微应用加载的资源会 404
          __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
        }
        function render() {
          app = new Vue({
            router,
            store,
            render: h => h(App)
          }).$mount('#app')
        }
        
        
        export async function bootstrap(props) {
          console.log('react app bootstraped', props);
        }
        /**
         * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
         */
        export async function mount(props) {
          console.log('mount', props);
          render();
        }
        /**
         * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
         */
        export async function unmount(props) {
          console.log('unmount', props)
          app.$destroy();
          app = null;
        }
        
      2. 构建文件配置

        module.exports = {
          publicPath: 'http://localhost:7200/',
          configureWebpack: {
            output: {
              library: 'vueApp',
              libraryTarget: 'umd',
            },
          },
          devServer: {
            port: 7200,
            headers: {
              // 解决跨域
              'Access-Control-Allow-Origin': '*'
            }
          }
        }
        

    二、umi脚手架配置

    具体配置(主:qiankun 子:umiqiankun)
    1. 主应用安装qiankun

      yarn add qiankun 或 npm i qiankun -S
      
    2. 主应用配置

      useEffect(() => {
          if (currentUser.userid) {
            // 注册微应用
            registerMicroApps([{
              name: 'app1',
              entry: '//localhost:7100',
              container: '#sub1box',
              activeRule: '/microApp1',
              props: {
                userInfo: currentUser
              },
            }, {
              name: 'app2',
              entry: '//localhost:7200',
              container: '#sub2box',
              activeRule: '/microApp2',
              props: {
                userInfo: currentUser,
              },
            }], {
              beforeMount: app => {
                console.log('app3', app);
              },
            });
      
            // 开启加载子应用
            start({
              // 是否预加载
              prefetch: true,
              // 是否开启沙箱模式
              sandbox: true,
            });
          }
          // TODO: 登录过期处理
        }, [currentUser])
      
    3. 声明子应用容器

      <Authorized authority={authorized.authority} noMatch={noMatch}>
        {children}
        <div id="sub1box"></div>
        <div id="sub2box"></div>
      </Authorized>
      
    4. 安装子应用依赖包

      yarn add @umijs/plugin-qiankun -D 或 npm i @umijs/plugin-qiankun -D
      
    5. 配置子应用插件

      // sub_app_1/config/config.js
      
      export default {
        qiankun: {
          slave: {}
        }
      }
      
    6. 添加子应用生命周期

      // src/app.js
      
      import ReactDOM from 'react-dom';
      export const qiankun = {
        // 应用加载之前
        async bootstrap(props) {
          console.log('app1 bootstrap1', props);
        },
        // 应用 render 之前触发
        async mount(props) {
          console.log('app1 mount1', props);
        },
        // 应用卸载之后触发
        async unmount(props) {
          console.log('app1 unmount1', props);
          ReactDOM.unmountComponentAtNode(document.querySelector('#root-slave'));
        },
      };
      
    7. 配置env环境变量

      // sub_app_1/.env
      
      SKIP_PREFLIGHT_CHECK=true
      BROWSER=none
      PORT=7100
      WDS_SOCKET_PORT=7100
      
    8. 修改html模板id

      // sub_app_1/src/pages/document.ejs
      
      <div id="<%= context.config.mountElementId %>"></div>
      
    9. 获取父应用传递的props

      1. 使用hooks接收

        import { useModel} from 'umi';
        
        const masterProps = useModel('@@qiankunStateFromMaster');
        console.log('父应用传递的props', masterProps);
        
        
      2. 使用高阶组件接收

        import { connectMaster } from 'umi';
        function MyPage(props) {
          return <div>{ JSON.strigify(props) }</div>;
        }
        export default connectMaster(MyPage);
        

    qinakun开发注意事项

    1. 在基本框架中使用qiankun时。注意WDS_SOCKET_PORT与PORT一致,避免热更新问题
    2. 在umi框架中,在document.ejs中使用context.config.mountElementId进行设置
    3. 使用@umijs/plugin-qiankun插件,在config.js必须使用qiankun对象配置中必须使用slave或者master作为key
    4. 子应用必须在入口处暴露出三个<span style="color: red">bootstrap/mount/unmount</span>三个必要的方法,且均为异步函数,供qiankun调用

    single-spa vs qiankun

    Single-spa Qiankun
    关联关系 / 基于single-spa 弥补了预加载、沙箱机制<br />提供了开箱即用的api,更容易接入
    应用粒度 模块 应用 single-spa是通过加载函数来加载的公用的vendor文件和chuck文件<br />qiankun是加载的项目应用的服务域名
    社区热度 7.6K 7.4k 基本持平
    预加载 在浏览器空闲时间预加载未打开的微应用,加速微应用的打开方式,提高性能
    单应用模式 qiankun除了支持单应用模式,还支持多应用模式,一起开启
    <span style="color: yellow">css隔离/js沙箱机制</span> qiankun css隔离方案:<br /> 1.Dynamic stylesheet 动态样式表,当应⽤切换时移除<br /> 2.css-modules 打包时⽣成hash选择器<br />qiankun js隔离方案:<br /> 1.利用proxy进行对象代理,在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改,避免主从应用的js全局变量的污染
    入口(加载函数)接入成本 Single-spa使用js entry 方式,添加新应⽤样式single-spa的加载函数需要手动加载子项目的入口js和模块js以及css文件,且文件数量和文件名(hash)不确定<br />qiankun既支持js entry,又支持 html entry(主流)方式,主应用通过import-html-entry接入子应用的域名,加载路径明确
    umi插件 qiankun:适合umi项目的插件@umijs/plugin-qiankun
    整体接入成本 single-spa的加载函数需要手动加载子项目的入口js和模块js文件、支持同步加载js、js文件数量也不确定,并且需要配置webpack 打包方式为umd<br />qiankun(针对umi项目)主应用接入子应用服务域名、在子应用引入@umijs/plugin-qiankun插件,webpack简单配置即可
    主从通信 支持 支持 都支持props传递到子应用<br />single-spa:<br />props、custom event事件、post message、event-bus(子父通信)<br />接收props在bootstrap/mount/unmount的props获取<br /><br />qiankun:<br />props、custom event事件、event-bus(子父通信)<br />接收属性可以利用@umijs/plugin-qiankun提供的hooks(useModel)或者高阶组件(connectMaster)接收<br />或者使用qiankun提供的全局状态设置api:initGloabalState,在子应用使用props.onGlobalStateChange接收属性

    主应用changeLog

    添加主应用--qiankun配置

    // micro_app_base/src/utils/qiankunConfig.js
    
    /**
    * qiankun配置子应用
    */
    import { registerMicroApps, start, addGlobalUncaughtErrorHandler, initGlobalState } from 'qiankun';
    
    window.qiankunActions = initGlobalState();
    
    export function qiankunConfig({
     beforeLoadCb,
     // afterMountCb,
    }) {
     // 声明注册子应用
     const apps = [
       {
         // 子应用名称
         name: 'microBaidu',
         // 子应用入口
         entry: '//localhost:7001',
         // 子应用挂在容器id/HTMLElement容器
         container: '#microBaidu',
         // 子应用激活规则/函数 location => location.startsWith('/react')
         activeRule: '/livemanage', // 匹配路由前缀
       },
       {
         // 子应用名称
         name: 'microToutiao',
         // 子应用入口
         entry: '//localhost:7002',
         // 子应用挂在容器id/HTMLElement容器
         container: '#microToutiao',
         // 子应用激活规则/函数 location => location.startsWith('/react')
         activeRule: '/yzl',
       },
       {
         // 子应用名称
         name: 'microTencent',
         // 子应用入口
         entry: '//localhost:7003',
         // 子应用挂在容器id/HTMLElement容器
         container: '#microTencent',
         // 子应用激活规则/函数 location => location.startsWith('/react')
         activeRule: '/ads',
       },
     ];
     // 注册微应用,必须确保唯一
     registerMicroApps(apps, {
       // 子应用加载前
       beforeLoad(app) {
         beforeLoadCb && beforeLoadCb();
         console.log('register before load', app.name);
         return Promise.resolve();
       },
       beforeMount(app) {
         console.log('register before mount', app.name);
         return Promise.resolve();
       },
       // 子应用挂载后
       afterMount(app) {
         // 挂载后的回调函数,可以传递属性
         // afterMountCb && afterMountCb();
         console.log('register after mount', app.name);
         return Promise.resolve();
       },
     });
    
     // 添加全局未捕获异常处理器
     addGlobalUncaughtErrorHandler(event => {
       console.log('event', event);
       const { message = '' } = event;
       if (message.includes('died in status LOADING_SOURCE_CODE')) {
         console.log('event message', message);
       }
     });
    
     // 启动qiankun配置
     start({
       // 解决子项目在首次挂载进来时,全局监听事件是可以被监听的。但二次唤醒时,事件监听就丢失了
       sandbox: false,
     });
    }
    
    
    
    // micro_app_base/src/layouts/index.js
    
    // 设置子应用 容器
    class Index extends React.Component {
    render() {
      ...
      // 添加判断
      if (/ads/.test(location.pathname)) {
        // mico app 广告模块
        renderChidren = <div id="microTencent" />;
      }
      if (/livemanage/.test(location.pathname)) {
        // micro app 直播模块
        renderChidren = <div id="microBaidu" />;
      }
      if (/yzl/.test(location.pathname)) {
        // micro app 支付模块
        renderChidren = <div id="microToutiao" />;
      }
      ...
    }
    }
    
     
    //。micro_app_base/src/layouts/SecurityLayout.tsx
     
    // qiankun
    import { qiankunConfig } from '@/utils/qiankunConfig';
    import request from '@/utils/request';
    
    ...获取power信息之后
    // 开启qiankun配置
    qiankunConfig({
     beforeLoadCb() {
       // user信息和request请求 与子应用 公用
       window.global_qiankun_props = {
         commonRequest: request,
         userRes: res,
       }
     },
    });
    

    移除目录

    ads(广告平台)

    livemanage(直播管理)

    yzl(有证链)

    子应用changeLog

    // 修改子应用name和注册子应用的name同名,否则切换应用时报错
    "name": "microTencent",
    

    添加umi/qiankun配置

    // config/config.ts
    
    // 添加umi/qiankun插件配置
    plugins: [
    ...,
    ['@umijs/plugin-qiankun/slave', {}], 
    ]
    
    export default {
    // 添加umi/qiankun插件配置,添加base,否则会以package.json中的name作为base url
    base: '/',
    ...,
    }
    
    // 子应用/config/plugin.config.ts 
    
    // FIXME:注释掉 添加umi/qiankun插件配置
     // config.plugin('webpack-theme-color-replacer').use(ThemeColorReplacer, [
     //   {
     //     fileName: 'css/theme-colors-[contenthash:8].css',
     //     matchColors: getAntdSerials('#1890ff'), // 主色系列
     //     // 改变样式选择器,解决样式覆盖问题
     //     changeSelector(selector: string): string {
     //       switch (selector) {
     //         case '.ant-calendar-today .ant-calendar-date':
     //           return ':not(.ant-calendar-selected-date)' + selector;
     //         case '.ant-btn:focus,.ant-btn:hover':
     //           return '.ant-btn:focus:not(.ant-btn-primary),.ant-btn:hover:not(.ant-btn-primary)';
     //         case '.ant-btn.active,.ant-btn:active':
     //           return '.ant-btn.active:not(.ant-btn-primary),.ant-btn:active:not(.ant-btn-primary)';
     //         default:
     //           return selector;
     //       }
     //     },
    
     //     // isJsUgly: true,
     //   },
     // ]);
    
    // 子应用/src/utils/index.js
    
    // 添加以下代码
    // 是否运行在qianku的环境
    export const isQiankunEnv = window.__POWERED_BY_QIANKUN__;
    
    // 子应用/src/layouts/BasicLayout.tsx
    
    
    // 添加
    import { isQiankunEnv } from '@/utils';
    
    
    
    
    const footerRender: BasicLayoutProps['footerRender'] = (_, defaultDom) => {
    // 添加判断
    if (isQiankunEnv) { return null };
    return (
     <>
      ...
     </>
    );
    };
    class BasicLayout extends React.PureComponent {
    
    render() {
    
     return (
       <ProLayout
        ...
         itemRender={(route, params, routes, paths) => {
           const first = routes.indexOf(route) === 0;
           return first ? (
             // <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
             // 处理首页跳转子应用的问题,使用href跳转
             <Button style={{ padding: 0 }} type="link" href={`${location.origin}${paths.join('/')}`}>{route.breadcrumbName}</Button>
           ) : (
             <span>{route.breadcrumbName}</span>
           );
         }}
        
         // 处理左侧菜单和宽度
         menuRender={isQiankunEnv ? false : undefined}
         siderWidth={isQiankunEnv ? 0 : 256}
    
    
        
       >
         <React.Fragment>
           <div className={styles.tabsContainer}>
             {
               // 处理头部
               isQiankunEnv ? children : (
                 <React.Fragment>
                   <div
                     className="ant-layout-header"
                     style={{
                       position: 'absolute',
                       padding: 0,
                       width: '100%',
                       height: '60px',
                       lineHeight: '60px',
                       borderLeft: '1px solid #e8e8e8',
                     }}
                   >
                     <div className="ant-pro-global-header">
                       <span
                         className="ant-pro-global-header-trigger"
                         style={{ position: 'absolute', height: 40, padding: '0 10px', zIndex: 3 }}
                         onClick={() => {
                           if (dispatch) {
                             dispatch({
                               type: 'global/changeLayoutCollapsed',
                               payload: !collapsed,
                             });
                           }
                         }}
                       >
                         <Icon type={collapsed ? 'menu-unfold' : 'menu-fold'} />
                       </span>
       
                       <RightContent
                         viewNotice={this.viewNotice}
                         // count={count}
                         onViewMore={this.onViewMore}
                         showNotice={showNotice}
                         clearCache={this.clearCache}
                         onNoticeVisibleChange={this.handleNoticeVisibleChange}
                         {...this.props}
                       />
                     </div>
                   </div>
                   <div style={{ height: 40, margin: '0 180px 2px 40px' }} />
                   {children}
                 </React.Fragment>
               )
             }
           </div>
         </React.Fragment>
       </ProLayout>
     );
    }
    }
    
    
    
    
    // 子应用/src/layouts/index.js
    
    // 因为删除了useTemplate目录所以对应的引用注释掉
    // import Container from '@/pages/template/container';
    ...
    // if (/useTemplate/.test(location.pathname)) {
     //   renderChidren = <Container />;
     // }
    
    // micro_app_tencent/src/utils/request.js
    
    // 单独启动或者运行在qiankun中 request方法 判断
    export default isQiankunEnv ? window.global_qiankun_props?.commonRequest : request;
    
    // micro_app_tencent/src/layouts/SecurityLayout.tsx
    
    componentDidMount() {
          ...
        
        // 单独启动或者运行在qiankun中 power信息 判断
        const { dispatch } = this.props;
        const userRes =  window.global_qiankun_props?.userRes;
        // qiankun修改
        if (dispatch) {
          if (isQiankunEnv) {
            // 在quankun运行环境中
            dispatch({
              type: 'user/saveUserPower',
              payload: userRes,
            })
            dispatch({
              type: 'user/saveMoney',
              payload: userRes.data.money,
            })
          } else {
            // 不在quankun运行环境中
            dispatch({
              type: 'user/fetchPower',
            }).then((res: { data: any; }) => {
              if (res) {
                const { data } = res;
                generateWaterMark({
                  username: data.user_name,
                  phone: data.ddphone,
                });
                window.localStorage.setItem("user_name", res.data.user_name);
                const imgDate = localStorage.getItem("imgDate");
                if (imgDate) {
                  if (moment().diff(moment(imgDate), 'day') > 0) {
                    window.localStorage.setItem("imgDate", moment().format('YYYYMMDD'));
                    window.localStorage.setItem("delImg", JSON.stringify({}));
                  }
                } else {
                  window.localStorage.setItem("imgDate", moment().format('YYYYMMDD'));
                }
              }
            });
          }
        }
    }
    

    移除目录

    移除当前保留目录之外的其他目录(除user目录外,因为user是sso登录之后跳转的处理逻辑)

    相关文章

      网友评论

          本文标题:微前端调研文档

          本文链接:https://www.haomeiwen.com/subject/ijjzoktx.html