模板微应用配置说明文档

微应用不需要额外安装任何其他依赖即可接入 qiankun

一. 配置

1. 创建并引入 public-path.js

src/qiankun目录中创建 public-path.js

  1. if (window.__POWERED_BY_QIANKUN__) {
  2. // eslint-disable-next-line
  3. __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  4. }

main.js顶部引入

  1. import "./qiankun/public-path";

2. 在 main.js 封装 render 方法创建 vue 实例

  1. // new Vue({
  2. // store,
  3. // router,
  4. // render: (h) => h(App)
  5. // }).$mount('#app');
  6. let instance = null;
  7. function render(props = {}) {
  8. const { container } = props;
  9. container &&
  10. container.setAttribute(
  11. "style",
  12. "height:100%;overflow-y: auto;background-color: #ffffff;"
  13. );
  14. instance = new Vue({
  15. router,
  16. store,
  17. render: (h) => h(App),
  18. }).$mount(container ? container.querySelector("#app") : "#app");
  19. }
  20. // 独立运行时
  21. if (!window.__POWERED_BY_QIANKUN__) {
  22. render();
  23. }

3. 在 main.js 导出相应的生命周期钩子

微应用需要在自己的入口 js (vue 项目在 main.js) 导出 bootstrapmountunmount 三个生命周期钩子,以供 qiankun 在适当的时机调用。

在 mount 生命周期中通过 props 参数获取到基座应用传递过来的参数

  1. qiankunEventBus 用于 eventBus 通信
  2. qiankunStore 用于 vuex 通信
  3. setGlobalState 用于 改变 gloabalState 的值并触发全局监听
  4. onGlobalStateChange 用于 注册监听 gloabalState 的监听器
  5. qiankunCommonStore 用于 接收基座应用的 common 模块并注册到微应用的 vuex 中
  1. // bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
  2. export async function bootstrap() {
  3. console.log("[vue] vue app bootstraped");
  4. }
  5. // 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
  6. export async function mount(props) {
  7. console.log("[vue] props from main framework", props);
  8. Vue.prototype.$qiankunEventBus = props.qiankunEventBus;
  9. Vue.prototype.$qiankunStore = props.qiankunStore;
  10. Vue.prototype.$setGlobalState = props.setGlobalState;
  11. Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange;
  12. // 将基座的common注册的微应用自己的vuex实例上,这样微应用就可以使用自己的vuex实例访问该模块
  13. if (store && store.hasModule) {
  14. if (!store.hasModule("qiankunCommonStore")) {
  15. store.registerModule("qiankunCommonStore", props.qiankunCommonStore);
  16. }
  17. }
  18. store.dispatch("common/setIsPoweredByQiankun", true);
  19. render(props);
  20. }
  21. // 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
  22. export async function unmount() {
  23. instance.$destroy();
  24. instance.$el.innerHTML = "";
  25. instance = null;
  26. }

4. 配置 vue.config.js

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

  1. 允许跨域让基座加载微应用
  2. 配置打包格式为 umd 打包
  1. const { defineConfig } = require("@vue/cli-service");
  2. // 每个微应用的name必须唯一
  3. const { name } = require("./package.json");
  4. module.exports = defineConfig({
  5. // 部署应用包时的基本 URL
  6. publicPath: `/child/${name}`,
  7. devServer: {
  8. // 推荐固定端口,方便调试(可选)
  9. port: 9081,
  10. // 允许跨域让基座加载微应用
  11. headers: {
  12. "Access-Control-Allow-Origin": "*",
  13. },
  14. },
  15. configureWebpack: {
  16. // 配置打包格式
  17. output: {
  18. library: `${name}-[name]`,
  19. libraryTarget: "umd",
  20. // webpack5以下使用 jsonpFunction 配置
  21. // jsonpFunction: `webpackJsonp_${name}`
  22. // webpack5及以上使用 chunkLoadingGlobal 配置
  23. chunkLoadingGlobal: `webpackJsonp_${name}`,
  24. },
  25. },
  26. });

5. 配置 Vuex 的 common 模块

  1. function initState() {
  2. return {
  3. // 是否处于乾坤环境
  4. isPoweredByQiankun: false,
  5. };
  6. }
  7. const state = initState(),
  8. mutations = {
  9. /**
  10. * @description 设置当前是否处于乾坤环境
  11. * @return {void}
  12. * @example
  13. * this.$store.commit('common/setIsPoweredByQiankun')
  14. */
  15. setIsPoweredByQiankun(state, payload) {
  16. state.isPoweredByQiankun = payload;
  17. },
  18. },
  19. actions = {
  20. /**
  21. * @description 设置当前是否处于乾坤环境
  22. * @return {void}
  23. * @example
  24. * this.$store.dispatch('common/setIsPoweredByQiankun')
  25. */
  26. setIsPoweredByQiankun({ commit }, payload) {
  27. commit("setIsPoweredByQiankun", payload);
  28. },
  29. },
  30. getters = {
  31. /**
  32. * @description 获取当前是否处于乾坤环境
  33. * @return {boolean}
  34. * @example
  35. * this.$store.getters['common/isPoweredByQiankun']
  36. */
  37. isPoweredByQiankun(state) {
  38. return state.isPoweredByQiankun;
  39. },
  40. };
  41. export default {
  42. namespaced: true,
  43. state,
  44. mutations,
  45. getters,
  46. actions,
  47. };

6. 配置路由

修改router/routes.js

每个微应用的路由地址都需要一个不重复的路由前缀,用于让 qiankun 根据当前路由匹配并启动对应的微应用。

  1. const routes = [
  2. {
  3. path: "/",
  4. redirect: "/heaven-sub-app1",
  5. },
  6. {
  7. path: "/index",
  8. redirect: "/heaven-sub-app1",
  9. },
  10. {
  11. path: "/heaven-sub-app1",
  12. name: "Index",
  13. component: () => import("@/views/index.vue"),
  14. children: [
  15. {
  16. path: "example-a",
  17. name: "App1ExampleA",
  18. },
  19. {
  20. path: "example-a/1",
  21. name: "App1ExampleA1",
  22. component: () => import("@/views/example-a/example-a-1.vue"),
  23. },
  24. {
  25. path: "404",
  26. name: "NotFound",
  27. component: () => import("@/views/404.vue"),
  28. },
  29. ],
  30. },
  31. ];
  32. export default routes;

修改router/index.js

  1. router.beforeEach((to, from, next) => {
  2. // 如果处于乾坤环境,那么权限交由基座处理
  3. if (store.state.common.isPoweredByQiankun) {
  4. next();
  5. } else {
  6. if (process.env.NODE_ENV === "development") {
  7. // 如果在开发环境下独立运行项目时,为了便于开发调试直接放行。
  8. next();
  9. } else {
  10. // eslint-disable-next-line no-alert
  11. window.alert("请使用基座访问本项目");
  12. }
  13. }
  14. });

7. 微应用页面如何与主应用通信

  1. 通过 qiankunEventBus 进行通信,示例:
    1. this.$qiankunEventBus.$emit("logout");
  1. 通过 setGlobalState 进行通信,示例:
    1. this.$setGlobalState({
    2. // 事件触发来源
    3. eventFrom: "microApp",
    4. // 事件的标识
    5. eventCode: "logout",
    6. // 事件传递的参数
    7. eventData: {
    8. time: new Date().getTime(),
    9. },
    10. });
  1. 使用 globalState 进行全局状态改变监听,示例:
    1. this.$onGlobalStateChange((state, prev) => {
    2. console.log("微应用监听到全局状态改变", state, prev);
    3. });
  1. 通过 Vuex 进行通信,示例:
    1. this.$store.dispatch("qiankunCommonStore/logout");
  1. 使用 store 获取基座应用的数据,示例:
    1. ...mapGetters('qiankunCommonStore', {
    2. // token
    3. token: 'getToken',
    4. // 用户信息
    5. userInfo: 'getUserInfo'
    6. })

8. 微应用跳转页面

  1. 跳转当前微应用的其他页面,推荐使用 name,示例:
    1. this.$router.push({
    2. name: "App1ExampleA2",
    3. });
  1. 跳转其他微应用的页面,需要写完整路径,示例:
    1. this.$router.push({
    2. path: "/heaven-sub-app2/example-a/1",
    3. query: {
    4. from: "App1ExampleA1",
    5. },
    6. });

二. Q&A

  1. 路由模式应如何选择?
    为了方便集成和部署,基座应用以及微应用的路由都要求使用 hash 模式。
  2. 微应用的路由必须要加前缀吗?
    是,每个微应用的路由地址都需要一个不重复的路由前缀,用于让 qiankun 根据当前路由匹配并启动对应的微应用。
  3. 每个页面都要定义 name 吗
    是,为了保证当前已经打开的同一个微应用下的页面可以正确的被 keep-alive组件缓存。
  4. 使用$setGlobalState的修改全局数据失败?
    微应用中只能修改已存在的一级属性,基座应用不受该限制。
  5. qiankunActions.onGlobalStateChange 事件监听被覆盖
    onGlobalStateChange只能同时创建一个监听,新创建的事件监听会覆盖上一个事件监听。推荐在 index.vuemain.js 等仅创建一次监听,根据eventCode做不同的动作
  6. class、id 选择器命名有什么注意事项吗?
    为了避免影响基座的样式,请勿使用 main-app 开头的 class、id 选择器修改样式。
    修改 Element-UI 等组件库的样式时,推荐限制样式作用范围如 .my-table .el-table{}
  7. 集成后报错 Uncaught Error: application ‘heaven-demo-digital’ died in status LOADING_SOURCE_CODE: only one instance of babel-polyfill is allowed
    这是因为多个应用的 babel 被重复引入了,解决方法如下:
    在 main.js 直接引入 import “babel-polyfill”; 改为判断是否存在再引入
    1. if (!global._babelPolyfill) {
    2. require('babel-polyfill');
    3. }

    如果还不行查看 webpack 配置中是否也引入了 babel