微前端框架(qiankun)使用记录

参考:

疑惑

  1. 主框架和子框架是否都要安装乾坤框架
答:不是,只需要安装在主框架即可,子框架不需要引入

使用方法

主应用

  1. 路由建议使用history模式
  2. 安装 qiankun

    yarn add qiankun 或者 npm i qiankun -S

  3. 配置微应用

    // /src/micro/apps.js
    const apps = [
        {
            name: 'planResource',
            entry: '//localhost:8083',
            container: '#iframe',
            activeRule: '/plan'
        },
        {
            name: 'configration',
            entry: '//localhost:8081',
            container: '#iframe',
            activeRule: '/configure'
        }
    ];
    
    export default apps;
  1. 在主应用中注册微应用

    // src/micro/index.js
    
    import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';
    
    import apps from './apps';
    
    // registerMicroApps 第二个参数可以不要,如果要做点啥,就写到对应的地方
    registerMicroApps(apps,
    {
      beforeLoad: [
        app => {
          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
        },
      ],
      beforeMount: [
        app => {
          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
        },
      ],
      afterUnmount: [
        app => {
          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
        },
      ],
    },);
    
    // 出错时显示的内容
    addGlobalUncaughtErrorHandler((event) => {
      // console.log(event);
      const { message } = event;
      if (message && message.includes('died in status LOADING_SOURCE_CODE')) {
        console.log('微应用加载失败,请检查应用是否可运行');
      }
    });
    
    export default start;
  2. 使用 start

    // /src/main.js
    import microApp from './micro';
    
    new Vue({
      router,
      store,
      render: (h) => h(App)
    }).$mount('#app');
    
    microApp();

注意:

<font color=orange>不一定要放在入口文件内,看你的项目而定</font>

  1. 如果你的微应用容器在index.html里的话,是没有多大问题,
  2. 但是很多时候是嵌入在Layout框架里的,正常进入是没有问题,如果是在微应用路径下刷新页面,会有如下图片的报错

    <font color=red>Uncaught Error: application 'cloud' died in status LOADING_SOURCE_CODE: [qiankun] Target container with #cloud not existed while cloud loading!</font>

这种情况下建议在改组件的mounted事件钩子下start, main.js下引入,不执行即可

eg:

//main.js
...
import microApp from './micro' // 加载乾坤app
microApp // 注册应用


// 包含子应用容器的VUE组件
<template>
  <div class="vab-app-main">
    <section>
      <transition mode="out-in" name="fade-transform">
        <vab-keep-alive v-if="routerView" />
      </transition>
      <div id="cloud"></div>
      <div id="vue3"></div>
    </section>
    <vab-footer />
  </div>
</template>

<script>
  ...
  import { start } from 'qiankun'
  export default {
    name: 'VabAppMain',
    data() {
      return {
        ...
      }
    },
    ...
    mounted() {
      start({
        prefetch: false, // 取消预加载
      }) // 开启
    },
    ...
  }
</script>

子应用

  1. 修改webpack配置

    // webpack.config.js || vue.config.js
    const port = 8088;
    const packageName = require("./package.json").name;
    module.exports = {
      lintOnSave: false, // 关闭eslint检测
    
      devServer: {
        port, // 这里的端口是必须和父应用配置的子应用端口一致
        disableHostCheck: false, // 关闭主机检查,保证子应用可以被主应用fetch到
        headers: {
          //因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域
          "Access-Control-Allow-Origin": "*",
        },
      },
      configureWebpack: {
        output: {
          //资源打包路径
          library: `${packageName}`,
          libraryTarget: "umd",
          jsonpFunction: `webpackJsonp_${packageName}`,
          // publicPath: `//localhost:${port}`,
        },
      },
      // outputDir: 'dist',
      // assetsDir: 'static',
      // filenameHashing: true,
    };
  2. 增加 public-path配置文件并在入口文件引入

    //  /src/micro/public-path.js
    if (window.__POWERED_BY_QIANKUN__) {
        // eslint-disable-next-line no-undef
        __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
    
    //  /src/main.js
    import '@/micro/public-path'
    
  1. 路由实例化配置

    // src/router/index.js
    
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    const routes = [
        {
            path: '/',
            name: 'Home',
            component: () => import('../views/Home.vue')
        },
        {
            path: '/about',
            name: 'About',
            component: () => import('../views/About.vue')
        }
    ]
    const router = new VueRouter({
        mode: 'history',
        // @ts-ignore
        base: window.__POWERED_BY_QIANKUN__ ? '/cloud' : '/', // 独立运行是用"/"作为根路径
        // base: '/cloud',
        routes
    })
    export default router
  2. Vue实例化包装和生命周期钩子导出

    // /src/main.js
    
    import "@/micro/public-path";
    import Vue from "vue";
    import App from "./App.vue";
    import actions from "./micro/actions.js";
    import router from "./router";
    import store from "./sotre";
    let instance = null;
    
    function render(props = {}) {
      // 经过实践这个判断要有,不然在独立运行时报错
      if (window.__POWERED_BY_QIANKUN__) {
        if (props) {
          actions.setActions(props);
          actions.onGlobalStateChange((state) => {
            console.log("app1", state);
          }, true);
        }
      }
      const { container } = props;
      // 这里是挂载到自己的html中  基座会拿到这个挂载后的html 将其插入进去
      const renderContainer = container ? container.querySelector("#app") : "#app";
      instance = new Vue({
        router,
        store,
        render: (h) => h(App),
      }).$mount(renderContainer);
    }
    
    // @ts-ignore
    if (!window.__POWERED_BY_QIANKUN__) {
      // 默认独立运行
      render();
    }
    
    // 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
    export async function bootstrap() {
      console.log("cloud app bootstraped");
    }
    
    export async function mount(props) {
      console.log("mount");
      render(props);
    }
    
    export async function unmount() {
      console.log("unmount");
      instance.$destroy();
    }

传值 <font color=red>Actions</font>

props 传值不够灵活,不推荐使用

<font color=red>Actions</font>推荐使用方法,可以在状态改变时触发

主应用

  1. 初始化全局跨应用通信方法
// /src/micro/actions.js

import { initGlobalState } from 'qiankun';
const initState = {};
const actions = initGlobalState(initState);
export default actions;
  1. 在需要设置跨应用全局状态的组件内:
// /src/app.vue
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
  import actions from '@/micro/actions'
  export default {
    name: 'App',
    computed: {
      visitedRoutes() {
        return this.$store.state.tabs.visitedRoutes //需要监听的数据
      },
    },
    watch: {
      visitedRoutes(newVal, oldVal) {
        this.setGlobalState()
      },
    },
    created() {
      // this.setGlobalState()
    },
    methods: {
      setGlobalState() {
        let data = {
          time: this.$store.state.user.time,
          token: this.$store.state.user.token,
          ability: this.$store.state.acl,
          routes: this.$store.state.routes,
          tabs: this.$store.state.tabs.visitedRoutes,
        }
        actions.setGlobalState(data)
      },
    },
  }
</script>

注意:

<font color=orange>可以监听vuex数据,如果变化,执行setGlobalState</font>

子应用

  1. 全局actions定义
//  /src/micro/actions.js

function emptyAction() {
    // 警告:提示当前使用的是空 Action
    console.warn('Current execute action is empty!');
  }
  class Actions {
    // 默认值为空 Action
    actions = {
      onGlobalStateChange: emptyAction,
      setGlobalState: emptyAction
    };
    /**
     * 设置 actions
     */
    setActions(actions) {
      this.actions = actions;
    }
    /**
     * 映射
     */
    onGlobalStateChange(...args) {
      return this.actions.onGlobalStateChange(...args);
    }
    /**
     * 映射
     */
    setGlobalState(...args) {
      return this.actions.setGlobalState(...args);
    }
  }
  const actions = new Actions();
  export default actions;
  1. 在应用渲染前获取从主应用上的通信方法并注入到actions里
// /src/main.js

import "@/micro/public-path";
import Vue from "vue";
import App from "./App.vue";
import actions from "./micro/actions.js";
import router from "./router";
import store from "./sotre";
let instance = null;

function render(props = {}) {
  // @ts-ignore
  if (window.__POWERED_BY_QIANKUN__) {
    if (props) {
      actions.setActions(props);
      actions.onGlobalStateChange((state) => {
        console.log("app1", state);
        const { token, ability, tabs } = state;
        store.state.ability = ability
        store.state.token = token
        store.state.tabs = tabs
      }, true);
    }
  }
  const { container } = props;
  // 这里是挂载到自己的html中  基座会拿到这个挂载后的html 将其插入进去
  const renderContainer = container ? container.querySelector("#app") : "#app";
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(renderContainer);
}

// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
  // 默认独立运行
  render();
}

// 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
export async function bootstrap() {
  console.log("cloud app bootstraped");
}

export async function mount(props) {
  console.log("mount");
  render(props);
}

export async function unmount() {
  console.log("unmount");
  instance.$destroy();
}

开发DevTools

如果基座和子应用使用的都是VUE,要使用devtools是比较老火的事情:

  1. 如果使用的devtools 5.xx 的版本,那么只能查看主框架的数据
  2. 如果使用devtools 6.xx(目前只有bata版本),且基座和子应用使用不同的VUE版本,那么可以查看对应应用的数据,<font color=orange>可是开发开发工具会不停的报错,蛋痛</font>

建议:基座框架和子框架使用不同的框架语言开发(这样是否要好一些,或者有解决的办法)

<font color=red>主应用在什么时候传值</font>

很重要:不然首次是拿不到值的哦

参考传值Actions 主应用下的设置 监听vuex


wujh
9 声望1 粉丝