微前端 qiankun vue2 为主应用,接入 vue2、umi4、umi3、react 子项目问题总结

背景:需要搭建一个平台,这个平台的主要功能是集成各个子系统,方面对系统之间的统一管理。在搭建这样一个平台时,前端考虑使用微前端架构方式实现,使用的框架是 qiankun,本文主要记录在 qiankun 框架使用过程中遇到的问题,及解决方法。主要从以下五个方面来分享:

1、主应用 vue2 配置

2、微应用 vue2 配置

3、微应用 umi4 配置

4、微应用 umi3 配置

5、遇到的问题及解决方法

一、主应用 vue2 配置

主应用中的配置主要分为三部分:子应用公共配置;封装加载子应用的组件;配置加载子应用的路由。

1.1、子应用公共配置,文件 /public/register-apps.js 修改如下:

document.subApps = [
  {
    name: 'childApp1',
    entry: '//localhost:8081/child-app1/',
    container: '#container-sub-app',
    activeRule: '/child-app1',
  },
  {
    name: 'subAppReact',
    entry: '//localhost:8000/sub-app-react/',
    container: '#container-sub-app',
    activeRule: '/sub-app-react',
  },
];

1.2、手动加载微应用,微应用组件封装,/components/MicroApp.vue 如下:

<template>
  <div id="container-sub-app"></div>
</template>

<script>
import { loadMicroApp } from 'qiankun';
import actions from '../../actions.js';

export default {
  name: 'microApp',
  mixins: [actions],
  data() {
    return {
      microApp: null,
    };
  },
  mounted() {
    const getMicroInfo = this.getMicroInfo();
    this.microApp = loadMicroApp(getMicroInfo, { singular: true });
  },
  beforeDestroy() {
    console.log('beforeDestroy...');
    this.microApp.unmount();
  },
  methods: {
    // 手动加载微应用
    getMicroInfo() {
      const appIdentifying = this.$route.path.split('/')[1];
      let data = {};
      const href = window.location.host;
      for (let i = 0; i < document.subApps.length; i++) {
        const element = document.subApps[i];
        if (element.activeRule.includes(appIdentifying)) {
          if (typeof element.entry !== 'string') {
            data = {
              ...element,
              entry: element.entry[href]
                ? element.entry[href]
                : Object.values(element.entry)[0],
            };
          } else {
            data = { ...element };
          }
          data.props = {
            token: {
              userInfo: {
                userName: '小明',
                userId: '123',
                date: new Date().toLocaleString(),
              },
            },
          };
          data.activeRule = [appIdentifying];
          break;
        }
      }
      console.log('data::', data);
      return data;
    },
  },
};
</script>

 1.3、路由动态加载应用,文件 /src/routert/ index.js 如下:

import Vue from 'vue';
import VueRouter from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
import MicroApp from '../components/MicroApp.vue';

Vue.use(VueRouter);

const { isNavigationFailure, NavigationFailureType } = VueRouter;
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch((failure) => {
    if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
      throw failure;
    }
  });
};

const originalReplace = VueRouter.prototype.replace;
VueRouter.prototype.replace = function replace(location) {
  return originalReplace.call(this, location).catch((failure) => {
    if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
      throw failure;
    }
  });
};

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
  {
    // 子应用1 的路由配置,* 为匹配当前路由下的所有路由
    path: '/child-app1/*',
    name: 'MicroApp',
    component: MicroApp,
  },
  {
    // 子应用2 的路由配置
    path: '/sub-app-react/*',
    name: 'MicroApp',
    component: MicroApp,
  },
];

const router = new VueRouter({
  mode: 'history',
  routes,
});

export default router;

 小结:当有新增的子应用时,只需要修改子应用配置文件即可。

二、微应用 vue2 配置

vue2 的微应用配置主要分为两部分:暴露子应用的生命周期函数;修改打包配置文件;

2.1、vue2 暴露生命周期钩子函数,main.js 文件修改如下:

import Vue from 'vue';
import App from './App.vue';
import routes from './router';
import VueRouter from 'vue-router';
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false;
Vue.use(ElementUI);

// 这部分代码要写在文件顶部,即 qiankun 文档中的 public-path.js 文件中的代码
if (window.__POWERED_BY_QIANKUN__) {
  /* eslint-disable @typescript-eslint/camelcase */
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

let router = null;
let instance = null;
function render(props = {}) {
  console.log('子应用 render props::', props, 'instance====', instance);
  // sessionStorage.setItem('userInfo', JSON.stringify(props.token.userInfo));
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/child-app1/' : '/',
    mode: 'history',
    routes
  });

  instance = new Vue({
    router,
    store,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
/* eslint-disable */
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('子应用 bootstrap ===========================');
}

let initialState = null;
export async function mount(props) {
  console.log('子应用 mount props ===============', props);
  sessionStorage.setItem('userInfo', JSON.stringify(props.token.userInfo));
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log('子应用获取共享数据 state::', state, 'prev::', prev);
    // 接收主应用中的共享数据 并将其设置为全局变量
    Vue.prototype.$initialState = state;
  });
  props.setGlobalState({
    initialState:
      '子应用中修改主应用中的全局变量,实现住应用子应用间数据的双向双向通信'
  });

  render(props);
}
export async function unmount() {
  console.log('子应用 unmount==========');
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

 2.2 修改打包配置文件,vue.config.js 文件修改如下:

const { defineConfig } = require('@vue/cli-service');

module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: '/child-app1/',
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  configureWebpack: {
    output: {
      /**
       {
          name: 'childApp1',
          entry: '//localhost:8081/child-app1/',
          container: '#container-sub-app',
          activeRule: '/child-app1',
        },

        library 与 主应用中的 name 保持一致
       **/
      library: `childApp1`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      chunkLoadingGlobal: `webpackJsonp_childApp1` // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    }
  }
});

三、微应用 umi4 配置

微应用使用 umi4 + ant design pro 创建,微应用的修改需要注意的有两部分:暴露生命周期函数;启用微应用加载;

在使用 create-umi 选择 Ant Design Pro 模板,就能使用 @umijs/max 来创建项目了。

npx create-umi@latest

启动微应用如下:

npm run start
-- or
yarn run start

3.1  暴露子应用生命周期函数,/src/app.js 代码如下:

注意点:如果没有 app.js 可以新建一个,或者在 app.ts 文件中修改也一样。

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

3.2 运行时注册子应用,.umirc.ts 代码如下:

注意点:publicPath 的值要和主应用中的保持一致,不然主应用配置不到。

import { defineConfig } from '@umijs/max';

export default defineConfig({
  /*
    1、微应用加 publicPath、qiankun 属性
    2、publicPath 的值和主应用配置保持一致
    3、主应用配置如下:
    {
      name: 'subAppReact',
      entry: '//localhost:8000/sub-app-react/',
      container: '#container-sub-app',
      activeRule: '/sub-app-react',
    },
  */
  publicPath: '/sub-app-react/',
  qiankun: {
    slave: {},
  },
  antd: {},
  access: {},
  model: {},
  initialState: {},
  request: {},
  layout: {
    title: '@umijs/max',
  },
  routes: [
    {
      path: '/',
      redirect: '/home',
    },
    {
      name: '首页',
      path: '/home',
      component: './Home',
    },
    {
      name: '权限演示',
      path: '/access',
      component: './Access',
    },
    {
      name: ' CRUD 示例',
      path: '/table',
      component: './Table',
    },
  ],
  npmClient: 'yarn',
});

四、微应用 umi3 配置

五、遇到的问题及解决方法。

问题一:本地联调时报跨域问题及子应用无法 fetch 到,如下:

解决方法,修改主应用的配置,如下:

问题二:主应用可以连接到子应用,但在主应用中未显示子应用,如下:

解决方法,修改主应用的配置,如下:

主应用中未提供子应用需要挂载的 dom 元素

问题三:vue-router3 路由重复点击页面报错,如图:

解决方法,在 router 路由文件中修改 push replace 方法:

import Vue from 'vue';
import VueRouter from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
import MicroApp from '../components/MicroApp.vue';

Vue.use(VueRouter);

const { isNavigationFailure, NavigationFailureType } = VueRouter;
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch((failure) => {
    if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
      throw failure;
    }
  });
};

const originalReplace = VueRouter.prototype.replace;
VueRouter.prototype.replace = function replace(location) {
  return originalReplace.call(this, location).catch((failure) => {
    if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
      throw failure;
    }
  });
};

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
  {
    path: '/child-app1',
    component: MicroApp,
  },
];

const router = new VueRouter({
  mode: 'history',
  routes,
});

export default router;

问题四:主应用加载子应用之后,子应用内的路由跳转失败,如图:

解决方法,在路由文件中修改加载子应用的路由,并在修改主应用中的路由跳转方法,如图:

参考链接:导航故障 | Vue Router

qiankun 接入时的前期问题,大多是配置错误,主应用和子应用 name 对应不上,或者主应用未给子应用提供容器标签等,主要参考链接:

常见问题 - qiankun

问题五:umi4 创建项目之后,主应用加载不到子应用,主应用中未报错且住应用中的生命周期函数为执行,如下:

问题原因,主应用路由文件中未配置子应用路由,解决方法如下:

问题六:正确配置完路由之后,主应用页面报错,如下:

 

问题的原因,主应用和子应用中的路由未匹配上,检查子应用中配置的路由,并修改为一致,如下:

解决成功后看到的效果如下:

问题六:主应用中加载了两个子应用,子应用 vue 和 子应用 react,主应用路由和单个子应用切换正常,子应用之间的切换,后一个切换到的子应用未加载。

方式一通过 a 标签进行跳转:

方式二通过 location.href 进行跳转:

问题七:主应用访问子应用的接口,主项目报错 404,如下:

产生的原因接口跨域,解决的方法,在主应用中配置代理,如下:

const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    proxy: {
      '/api': {
        /**
         * 1、问题:主应用访问 antd-umi-pro 子应用的表格接口 /api/v1/queryUserList 报404错误
         * 2、解决方法:在主应用中配置代理转发到子应用的接口域名
         * 3、修改完配置之后重启项目
         * **/
        target: 'http://192.168.6.103:8000',
        changeOrigin: true,
        pathRewrite: {
          '/api': '/api',
        },
      },
    },
  },
});

问题八: 主应用访问子应用图片资源,转发的图片地址不对导致图片资源无法正确加载

问题产生的原因,在使用 qiankun 框架时,未动态设置资源打包的地址,修改如下:

子应用中的图片渲染的代码,动态配置 webpack 打包的资源路径之后,关于主应用加载子应用图片资源路径,字体资源路径 404 的问题就解决了,如下图:

代码上传到了 github 上,后序会持续更新代码。

GitHub - 13523010484/qiankun-vue

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值