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 *;`
            }
        }
    }
})
上一篇下一篇

猜你喜欢

热点阅读