背景:需要搭建一个平台,这个平台的主要功能是集成各个子系统,方面对系统之间的统一管理。在搭建这样一个平台时,前端考虑使用微前端架构方式实现,使用的框架是 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