背景:需要搭建一个平台,这个平台的主要功能是集成各个子系统,方面对系统之间的统一管理。在搭建这样一个平台时,前端考虑使用微前端架构方式实现,使用的框架是 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 对应不上,或者主应用未给子应用提供容器标签等,主要参考链接:
问题五: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 上,后序会持续更新代码。