Vue 微前端搭建 qiankun
2022-11-09 本文已影响0人
Cherry丶小丸子
主应用
1、安装 qiankun
npm i qiankun -S
2、项目中 src 文件夹下新建 qiankun 文件夹以及 qiankun.js 文件
import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';
/**
* 在主应用中注册微应用
*/
registerMicroApps([
{
name: 'zsyftxglpt-mh', // 微应用名称 - 具有唯一性
entry: 'http://192.168.20.4:1973', // 微应用入口 - 通过该地址加载微应用
container: '#microApp__zsyftxglptMh', // 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
activeRule: '/zsyftxglptMh', // 微应用触发的路由规则 - 触发路由规则后将加载该微应用
props: {} // 主应用需要传递给微应用的数据
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#microApp__yourContainer',
activeRule: '/yourActiveRule',
},
]);
addGlobalUncaughtErrorHandler(event => {
const { msg } = event;
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
console.log('加载失败');
}
});
// 启动 qiankun
// start(); // 推荐在页面中开启 qiankun
3、在 main.js 中 引入 qiankun.js
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
/**
* 引入 qiankun
*/
import './qiankun/qiankun.js';
createApp(App).use(router).mount('#app');
4、主应用路由文件修改
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "home" */ '@/views/login/login.vue'),
meta: {
title: '登陆'
}
},
{
path: '/home',
component: () => import(/* webpackChunkName: "home" */ '@/views/home/home.vue'),
children: [
{
path: '/zsyftxglptMh/:page*', // 如果微应用单独作为主应用的一个路由使用,vue-router 4.0 版本以上写法
// path: '/zsyftxglptMh/*', // vue-router 3.0 版本写法
name: 'zsyftxglptMh',
component: () => import(/* webpackChunkName: "home" */ '@/views/zsyftxglptMh/zsyftxglptMh.vue'),
meta: {
title: '知识门户'
}
},
]
}
]
});
router.afterEach((to, from) => {
document.title = '研发体系管理平台 - ' + to.meta.title;
});
export default router;
4、在页面中启动 qiankun
<template>
<div id="zsyftxglptMh" class="zsyftxglptMh"></div>
</template>
<script setup>
import { onMounted } from 'vue';
import { start } from 'qiankun';
onMounted(() => {
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start(); // 启动 qiankun
}
});
</script>
<style src="./knowledgePortal.scss" lang="scss" scoped></style>
<style>
#__qiankun_microapp_wrapper_for_zsyftxglpt_mh__ {
height: 100%;
}
</style>
微应用
1、在 src 目录新增 public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2、入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围
import './public-path'; // public-path 必须第一行就引入
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// 重新包装 render 方法
let instance = null;
function render(props = {}) {
const { container } = props;
// 如果是作为微应用,则渲染到主应用中 qiankun 节点下的 #app 中, 如果作为独立运行时,则渲染到 #app 节点中
const renderContainer = container ? container.querySelector('#app') : '#app';
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(renderContainer);
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 导出主应用识别所需的三个必要的生命周期钩子
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props); // 作为微应用渲染时将主应用传入的配置作为参数去渲染
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
// router = null;
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
3、修改路由文件
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ '../views/HomeView.vue')
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/zsyftxglptMh/' : '/', // 如果作为 qiankun 的微应用则使用主应用中配置的路由前缀,如果是独立运行则使用根目录
routes,
});
export default router
4、修改 vue.config.js
const packageName = require('./package.json').name;
module.exports = defineConfig({
configureWebpack: config => {
config.output.library = `${packageName}-[name]`;
config.output.libraryTarget = 'umd'; // 把微应用打包成 umd 库格式
config.output.jsonpFunction = `webpackJsonp_${packageName}`; // webpack 4 使用此选项,如果是 webpack 5 使用下边的选项,两者选择其一
// config.output.chunkLoadingGlobal = `webpackJsonp_${packageName}`;
},
devServer: {
port: 7100,
headers: {
'Access-Control-Allow-Origin': '*'
},
}
})
常见问题
1、主应用 与 微应用之间的服务代理
主应用需要配置 与 微应用一样的服务代理
image.png
image.png
2、跳转
// 主应用 跳转 微应用
router.push()
// 微应用 跳转 主应用
window.history.pushState(null, null, '/zsyftxglptMh/home');
3、跳转微应用时遇到的问题
主应用跳转微应用页面为空,如果控制台提示 Uncaught Error: application 'xxxx' died in status LOADING_SOURCE_CODE: [qiankun]: Target container with #xxxx not existed while xxxx loading!
,这个是情况的出现是主应用注册微应用时 container 参数配置的元素不存在导致
registerMicroApps([
{
name: 'zsyftxglpt-mh', // 微应用名称 - 具有唯一性
entry: 'http://192.168.20.4:1973', // 微应用入口 - 通过该地址加载微应用
container: '#zsyftxglptMh', // 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
activeRule: '/zsyftxglptMh', // 微应用触发的路由规则 - 触发路由规则后将加载该微应用
props: {} // 主应用需要传递给微应用的数据
}
]);
// 需要在 public 下的 index.html 中 或者 APP.vue 或者 嵌套路由 <router-view></router-view> 同级添加 `zsyftxglptMh` 元素
<div id="zsyftxglptMh"></div>
4、主应用与微应用之间互相通信
主应用向微应用传值
主应用使用 qiankun 内置函数 initGlobalState,设置全局变量,通过 setGlobalState 向微应用传递 lang 参数的 CN 值
import { initGlobalState } from 'qiankun'
data () {
return {
globalState: null
}
},
mounted () {
console.log('Main App Home mounted')
this.globalState = initGlobalState({
lang: ''
})
},
postMsgToVueAPP () {
this.globalState.setGlobalState({
lang: 'CN'
})
}
子应用在 mount 函数中接受 props 参数,通过 onGlobalStateChange 函数监听主应用传递过来的值
export async function mount(props) {
// 使用 Vue 原型属性
Vue.prototype.parentStore = props
props.onGlobalStateChange((state) => {
console.log('子应用接受的主应用数据')
console.log(state)
}, true);
render(props);
}
微应用向主应用传值
主应用设置 onGlobalStateChange 监听全局数据状态变化
import { initGlobalState } from 'qiankun'
data () {
return {
globalState: null
}
},
mounted () {
console.log('Main App Home mounted')
this.globalState = initGlobalState({
lang: ''
})
this.globalState.onGlobalStateChange(state => {
// 监听全局状态,子应用更新主应用数据后触发
console.log(state)
})
},
子应用使用 setGlobalState 更新全局状态数据
// parentStore 为 `mount` 中设置到 Vue 原型属性中的值
this.parentStore.setGlobalState({
lang: 'ZN'
})
5、主应用与微应用的样式隔离
elementui 修改前缀
参考地址
https://blog.csdn.net/weixin_44008717/article/details/121617721
使用 change-prefix-loader [https://www.npmjs.com/package/change-prefix-loader] 隔离 js
使用 postcss-change-css-prefix [https://www.npmjs.com/package/postcss-change-css-prefix] 隔离 css
element plus 修改前缀
参考地址
https://element-plus.gitee.io/zh-CN/guide/namespace.html
<!-- App.vue -->
<template>
<el-config-provider namespace="ep">
<!-- ... -->
</el-config-provider>
</template>
// src/assets/css/index.scss
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with (
$namespace: 'ep'
);
@use "element-plus/theme-chalk/src/index.scss" as *;
// vue.config.js
module.exports = defineConfig({
chainWebpack: config => {
/* 自定义配置路径别名 */
config.resolve.alias.set('@', resolve('src'))
},
css: {
loaderOptions: {
scss: {
additionalData: `@use "~@/assets/css/index.scss" as *;`
}
}
}
})