背景:需要搭建一个平台,这个平台的主要功能是集成各个子系统,方面对系统之间的统一管理。在搭建这样一个平台时,前端考虑使用微前端架构方式实现,使用的框架是 qiankun,本文主要记录在 qiankun 框架使用过程中遇到的问题,及解决方法。
问题一:本地联调时报跨域问题及子应用无法 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
简单总结:
1、主应用配置
主应用挂载组件:/component/Micro.vue
安装 qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
document.subApps = [{name: 'childApp1',entry: '//localhost:8081/child-app1/',container: '#container-child-app1',activeRule: '/child-app1',},
];
<template><div id="container-child-app1"></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() {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>
组应用挂载子应用的路由修改:
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;
App.vue 可以指定子应用容器位置:
<template><div id="app"><container-main><el-breadcrumb separator-class="el-icon-arrow-right"><el-breadcrumb-item><el-button type="text" @click="handleJumpParentHome">主应用 home</el-button></el-breadcrumb-item><el-breadcrumb-item><el-button type="text" @click="handleJumpParentAbout">主应用 about</el-button></el-breadcrumb-item><el-breadcrumb-item><el-button type="text" @click="handleJumpChildApp1">子应用 home</el-button></el-breadcrumb-item></el-breadcrumb><!-- 可以不写,写了之后未指定了子应用挂载位置 --><div id="container-child-app1"></div></container-main><router-view /></div>
</template><script>
import ContainerMain from './components/ContainerMain.vue';export default {name: 'App',components: {ContainerMain,},data() {return {};},methods: {handleJumpParentHome() {this.$router.push({path: '/',});},handleJumpParentAbout() {this.$router.push({path: '/about',});},handleJumpChildApp1() {this.$router.push({path: '/child-app1',});},},
};
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}
</style>
2、子应用配置
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);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;
}
子应用中的打包配置修改:
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-child-app1',activeRule: '/child-app1',},library 与 主应用中的 name 保持一致**/library: `childApp1`,libraryTarget: 'umd', // 把微应用打包成 umd 库格式chunkLoadingGlobal: `webpackJsonp_childApp1` // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal}}
});
代码上传到了 github 上,后序会持续更新代码。
GitHub - 13523010484/qiankun-vue