微前端qiankun接入vue2&vue3项目

7,311 阅读4分钟

微前端qiankun接入vue2&vue3项目

官方文档

接入vue2项目和vue3项目

对应路由模式分别是hashhistory

主应用

主应用是基于vue3开发的一个门户网站,仅有登录以及门户列表。

基于路由配置方式

通过将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能。

  • 安装qiankun

    npm i qiankun -S # 或者 yarn add qiankun

  • 在主应用中注册微应用以及对应配置

    新建qiankun文件夹,包含一个config.jsindex.js

  1. config下面存放微应用的配置信息,代码如下:

        // qiankun/config.js
        // 这里用函数的方式,方便初始化的时候可以设置一些默认参数传进来,例如token等,
        // 用于下发给微应用
        export const getQiankunConfig = (props = {}) => {
          return {
            subApps: [
              {
                name: "xxx-wms", // 子应用名称,建议跟package.json一致
                entry: "//192.168.1.40:8990/", // 子应用入口,本地环境下指定端口
                container: "#sub-container", // 挂载子应用的dom
                activeRule: "/xxx/wms", // 路由匹配规则
                props // 主应用与子应用通信传值
              },
              {
                name: "xxx-report", // 子应用名称,跟package.json一致
                entry: "//192.168.1.40:9527/#/", // 子应用入口,本地环境下指定端口
                container: "#sub-container", // 挂载子应用的dom
                activeRule: "/xxx/report", // 路由匹配规则
                props // 主应用与子应用通信传值
              },
            ]
          };
        };
    
  2. index放初始化函数以及一些其他需要配置和操作

    // qiankun/index.js
    import { registerMicroApps } from "qiankun";
    import { getQiankunConfig } from "./configs";
    import { getToken } from "@/plugins/cache";
    import useUserStore from "@/store/modules/user";
    
    // 设置初始需要传递的值
    export const getState = () => {
      const { userInfo } = useUserStore();
      const state = {
        token: getToken(),
        accessUser: {
          loginName: userInfo.userName || "",
          realName: userInfo.userDisplayName || ""
        }
      };
      return state;
    };
    
    const { subApps } = getConfig(getState());
    // 暴露注册函数,里面对应存放生命周期的钩子,可以做需要的处理
    export function registerApps() {
      try {
        registerMicroApps(subApps, {
          beforeLoad: [
            app => {
              console.log("before load", app);
            }
          ],
          beforeMount: [
            app => {
              console.log("before mount", app);
            }
          ],
          afterUnmount: [
            app => {
              console.log("before unmount", app);
            }
          ]
        });
      } catch (err) {
        console.log(err);
      }
    }
    
  • 新增容器,以及在路由表上增加对应路径的配置

    官方原话:当微应用信息注册完之后,一旦浏览器的url发生变化,便会自动触发qiankun的匹配逻辑,所有activeRule规则匹配上的微应用就会被插入到指定的container中,同时依次调用微应用暴露出的生命周期钩子。

    1. 新建subContainer组件,代码如下:
    // subContainer.vue
    <template>
     <div id="sub-container"></div>
    </template>
    
    <script setup>
    import { start } from "qiankun";
    import { registerApps } from "@/qiankun";
    
    onMounted(()=>{
       if (!window.qiankunStarted) {
         window.qiankunStarted = true;
         registerApps();
         start({
           prefetch: false, // 是否开启预加载, 默认为 true
           // sandbox: {
           // strictStyleIsolation: true    // 开启严格的样式隔离模式
           //   experimentalStyleIsolation: true // 样式隔离
           // }
         });
       }
    })
    </script>
    
    1. 路由表配置如下:
    // router/xxx.js
    export const routes = [
       // ...
       {
           // history模式需要通配所有路由,
           // 注意:此处xxx需要匹配上面的微前端配置里的activeRule字段
           path: "/xxx/:pathMatch(.*)*",
           name: "xxxxx",
           meta: {},
           component: () => import("@/views/subContainer")
       },
    ]
    

微应用

  • vue3 + vite,路由模式为history

  • 新建qiankun相关配置文件

    1. 安装vite-plugin-qiankun插件(qiankun目前没有支持vite的文档)

    npm i vite-plugin-qiankun -D

    1. vite.config.js中配置插件
        // vite.config.js
        import qiankun from "vite-plugin-qiankun";
    
        export default defineConfig() => {
            const { VITE_POWERED_BY_QIANKUN, VITE_PUBLIC_PATH } = env;
            return {
                base: VITE_PUBLIC_PATH,    // 绝对路径,线上会有跨域问题
                plugins: [
                    // VITE_POWERED_BY_QIANKUN qiankun的路径前缀
                    qiankun(VITE_POWERED_BY_QIANKUN, {
                      useDevMode: true
                    })
                ]
            }
        }
    
    1. 导出对应的生命周期钩子,以及初始化方式
       // qiankun.js
       import { renderWithQiankun, qiankunWindow } from "vite-plugin-qiankun/dist/helper";
    
       export const render = (app, container) => {
         app.mount(container ? container.querySelector("#app") : "#app");
       };
       export const initQianKun = (app) => {
         renderWithQiankun({
           mount(props) {
             const { container } = props;
             render(app, container);
           },
           bootstrap() {
             console.log("bootstrap");
           },
           unmount() {
             app.unmount();
           }
         });
       };
       // 初始化,判断是否在子应用的环境中
       export const init = (app) => {
         qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun(app) : render(app);
       };
    
    • 路由设置,如果是qiankun子应用的环境,增加对应的前缀
        // router/index.js
        import { createWebHistory, createRouter } from "vue-router";
        const VITE_POWERED_BY_QIANKUN = import.meta.env.VITE_POWERED_BY_QIANKUN;
        const router = createRouter({
        history: createWebHistory(
        qiankunWindow.__POWERED_BY_QIANKUN__
          ? `/xxx/${VITE_POWERED_BY_QIANKUN}/`
          : "/"
        ),
        routes: constantRoutes,
        scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition;
        }
        return { top: 0 };
        }
        });
    
        export default router;
    
    • 初始化应用
        // main.js
        import { createApp } from "vue";
        import { init } from "@/qiankun.js";
        import App from "./App";
        if (window.__POWERED_BY_QIANKUN__) {
          // eslint-disable-next-line no-undef
          __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
        }
        const app = createApp(App);
        init(app);
    
  • vue2 + webpack ,路由模式为 hash

    • 新建qiankun相关配置文件

      1. 配置webpack
           // vue.config.js
          const { name } = require('./package');
          module.exports = {
            // 开发跨域配置
            devServer: {
              headers: {
                'Access-Control-Allow-Origin': '*',
              },
            },
            configureWebpack: {
              output: {
                library: `${name}-[name]`,
                libraryTarget: 'umd', // 把微应用打包成 umd 库格式
                jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
              },
            },
          };
      
      1. 导出对应的生命周期钩子,以及初始化方式
          // qiankun.js
          import App from './App'
          import router from './router'
          import store from './store'
          import { setToken, setAccessUser, } from "@/utils/auth";
      
          let instance = null
          export function render(props = {}) {
            const { container } = props
            instance = new Vue({
              router,
              store,
              render: h => h(App)
            }).$mount(container ? container.querySelector('#app') : '#app')
          }
      
          export async function bootstrap(props) {
            console.log('[vue] vue app bootstraped', props)
          }
          export async function mount(props, ...args) {
            console.log('[vue] props from main framework', props, args);
            setData(props)
            props.onGlobalStateChange((state, prev) => {
              // state: 变更后的状态; prev 变更前的状态
              console.log("🚀 ~ props.onGlobalStateChange ~ state:", state)
              setData(state)
            });
            render(props)
          }
          export async function unmount() {
            instance.$destroy()
            instance.$el.innerHTML = ''
            instance = null
          }
          // 拿到主应用传过来的数据进行处理
          function setData(state){
            if(state['token']){
              setToken(state.token)
            }
            if(state['accessUser']) {
              setAccessUser(state.accessUser)
            }
          }
      
          export default render
      
    • 路由配置

       // router/index.js
       import Router from 'vue-router'
       export const constantRouterMap = [ ... ]
       export default new Router({
         scrollBehavior: () => ({ y: 0 }),
         base: window.__POWERED_BY_QIANKUN__ ? '/xxx/report/' : '/',
         routes: constantRouterMap
       })
    
    • 初始化应用
      import render from "./qiankun";
      // 导出所有的钩子
      export * from "./qiankun";
      if (window.__POWERED_BY_QIANKUN__) {
        __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }else {
      // 独立运行时
        render();
      }
    

总结

  1. 部署上线需要注意资源路径的问题
  2. 不同服务器下的项目需要处理跨域问题
  3. qiankun还有手动加载微应用的方式,暂时还没有用到,可查看官方文档