vue-cli项目整合qiankun实现微服务架构

1. 创建主应用和微应用

vue create potal   // 创建基座项目
vue create vue-app-one // 创建微应用1
vue create vue-app-two // 创建微应用2

1.1 通用的qiankun接入准备

1.1.1 微应用生命周期函数添加

import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import routes from "./router";
import "./public-path";

Vue.use(VueRouter);
Vue.config.productionTip = false;

let router = null;
let instance = null;

function render() {
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/vue-app-one" : "/",
    mode: "history",
    routes
  });

  instance = new Vue({
    router,
    render: h => h(App),
    mounted() {
      this.$bus.$emit("postMessage", {
        from: "vue-app-one",
        msg: "我是vue-app-one,我给你发个消息试试看你能收到不"
      });
    }
  }).$mount("#app");
}

// 单独启动时候直接执行渲染
window.__POWERED_BY_QIANKUN__ || render();

export async function bootstrap(props = {}) {
  if (props.fns && props.fns.length > 0) {
    props.fns.map(i => {
      Vue.prototype[i.name] = i[i.name];
    });
  }
  // 子应用内部拿到监听器并绑定到vue实例,子应用若想要发送消息需要使用this.$subscriber.next({from:'', ...})
  if (props.bus) {
    Vue.prototype.$bus = props.bus;
  }
  console.log("vue app bootstraped");
  console.log("接收到父应用传递来的数据:", props.data);
}

export async function mount(props) {
  console.log("props from main app", props);
  render();
}

export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

1.1.2 微应用webpack publicPath针对qiankun的处理

新建src/public-path.js, 并在main.js中引入, public-path.js内容如下

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

1.1.3 微应用webpack打包处理及允许跨域请求配置

vue-app-one和vue-app-two处理逻辑一致

const path = require("path");
const { name } = require("./package");

function resolve(dir) {
  return path.join(__dirname, dir);
}

const port = 8081; // dev port 针对不同微应用配置不同端口

module.exports = {
  /**
   * You will need to set publicPath if you plan to deploy your site under a sub path,
   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
   * then publicPath should be set to "/bar/".
   * In most cases please use '/' !!!
   * Detail: https://cli.vuejs.org/config/#publicpath
   */
  outputDir: "dist",
  assetsDir: "static",
  filenameHashing: true,
  // tweak internal webpack configuration.
  // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
  devServer: {
    // host: '0.0.0.0',
    hot: true,
    disableHostCheck: true,
    port,
    overlay: {
      warnings: false,
      errors: true
    },
    headers: {
      "Access-Control-Allow-Origin": "*"
    }
  },
  // 自定义webpack配置
  configureWebpack: {
    resolve: {
      alias: {
        "@": resolve("src")
      }
    },
    output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: "umd",
      jsonpFunction: `webpackJsonp_${name}`
    }
  }
};

2. 决定主项目是整体使用微前端接入还是部分使用微前端接入

2.1 整体接入

这种情况下考虑不使用vue-router来进行路由管理, 完全将路由管理交给qiankun来进行控制,微应用使用qiankun registerMicroApps(apps, lifeCycles?)进行注册,start启动qiankun,, 比如想在某个页面中进行接入某个微应用, 如果路由没有渲染完成,就无法保证其中微应用的挂载节点的渲染时机,微应用会挂载失败。因此应该保证App.view这个根级组件中定义了微应用挂载的DOM节点。
main.js的修改

import Vue from "vue";
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import { registerApps } from "./registerMicroApps";

Vue.use(Antd);

Vue.config.productionTip = false;

let app = null;
/**
 * 渲染函数
 * appContent 子应用html
 * loading 如果主应用设置loading效果,可不要
 */
function render({ appContent, loading } = {}) {
  if (!app) {
    app = new Vue({
      el: "#container",
      router,
      store,
      data() {
        return {
          content: appContent,
          loading
        };
      },
      render(h) {
      // 创建App实例时候将props传入, 以在渲染盒子挂载
        return h(App, {
          props: {
            content: this.content,
            loading: this.loading
          }
        });
      }
    });
  } else {
    app.content = appContent;		// 切换微应用时候不重复创建Vuew实例, 只更新content和loading值
    app.loading = loading;
  }
}

// 调用渲染主应用
render();

// 注册并启动微应用
registerApps({ store, render });

registerMicroApps.js封装

import {
  registerMicroApps, // 注册子应用
  runAfterFirstMounted, // 第一个子应用装载完毕
  setDefaultMountApp, // 设置默认装载子应用
  addGlobalUncaughtErrorHandler, // 添加全局的未捕获异常处理器
  start // 启动
} from "qiankun";

// 总线对象,用于主应用和子应用之间进行通信
/**
  import Vue from "vue";
  const bus = new Vue();
  export default bus;
**/
import bus from "./utils/Bus";

/**
 * 路由监听
 * @param {*} routerPrefix 前缀
 */
const genActiveRule = routerPrefix => {
  return location => location.pathname.startsWith(routerPrefix);
};

// 在主应用注册监听器,这里可以监听到其他应用的广播
const onMessage = v => {
  console.log(`监听到子应用${v.from}发来消息:`, v);
  // TODO 处理监听到子应用消息后的处理
};

/**
 * qiankun生命周期钩子函数
 */
const lifeCycle = {
  beforeLoad: [
    app => {
      console.log("before load", app);
    }
  ], // 挂载前回调
  beforeMount: [
    app => {
      console.log("before mount", app);
    }
  ],
  // 挂载后回调
  afterMount: [
    app => {
      console.log("after mount", app);
    }
  ],
  // 卸载前回调
  beforeUnmount: [
    app => {
      console.log("before mount", app);
    }
  ],
  // 卸载后回调
  afterUnmount: [
    app => {
      console.log("after unload", app);
    }
  ]
};

export const registerApps = ({ store, render }) => {
  // 监听子应用消息
  bus.$on("postMessage", onMessage);

  // 传递到子应用的数据
  const props = {
    data: store.getters,
    fns: [],
    bus			// 使用bus来进行主应用和微应用通信
  };
  // 注册子应用
  registerMicroApps(
    [
      {
        name: "vue-app-one",
        entry: "http://localhost:8081",
        activeRule: genActiveRule("/vue-app-one"),
        render,
        props
      },
      {
        name: "vue-app-two",
        entry: "http://localhost:8082",
        activeRule: genActiveRule("/vue-app-two"),
        render,
        props
      }
    ],
    lifeCycle
  );

  // 设置默认子应用,参数与注册子应用时genActiveRule("/aaa")函数内的参数一致
  setDefaultMountApp("/vue-app-one");

  // 第一个子应用加载完毕回调
  runAfterFirstMounted(() => {
    console.log("首个子应用完成加载");
  });

  // 启动微服务
  start();

  // 添加全局的未捕获异常处理器。
  addGlobalUncaughtErrorHandler(event => console.log(event));
};

App.view
主应用系统主页面,包含菜单栏和微应用的渲染DOM

<template>
  <div id="root" class="main-container">
    <div class="main-container-menu">
      <a-menu
        :defaultSelectedKeys="defaultSelectedKeys"
        mode="horizontal"
        theme="dark"
      >
        <a-menu-item key="vue-app-one">
          <router-link to="/vue-app-one">
            <a-icon type="appstore" />vue app 1
          </router-link>
        </a-menu-item>
        <a-menu-item key="vue-app-two">
          <router-link to="/vue-app-two">
            <a-icon type="appstore" />vue app 2
          </router-link>
        </a-menu-item>
      </a-menu>
    </div>
    <!-- 子应用盒子, 微应用会渲染在这里 -->
    <div id="root-view" class="app-view-box" v-html="content"></div>
  </div>
</template>

<script>
export default {
  name: "root-view",
  props: {
    loading: Boolean,
    content: String
  },
  data() {
    return {
      defaultSelectedKeys: ["vue-app-one"]
    };
  }
};
</script>

2.2 部分接入(推荐,可控性更高)

这种情况下也就是系统仅有部分页面接入其他微前端项目, 其他页面还是正常的系统内页面。这种情况下我们考虑使用qiankun2.0 loadMicroApp api进行手动微前端应用加载, 这样就能够在vue生命周期mounted中实现微应用挂载destroyed中实现微应用销毁, 在updated中实现微应用更新

2.2.1 封装微应用加载组件, 我们在路由页面中将复用该组件装载和卸载微应用

<template>
  <div ref="microBox"></div>
</template>

<script>
import { loadMicroApp } from "qiankun";

export default {
  name: "micro-app",
  props: ["name", "entry"],
  data() {
    return {
      microApp: null
    };
  },
  mounted() {
    const name = this.name;
    const entry = this.entry;
    this.microApp = loadMicroApp({
      name,
      entry,
      container: this.$refs.microBox,
    });
  },
  destroyed() {
    this.microApp.unmount();
  },
  updated() {
    this.microApp.update({
      name: this.name,
      entry: this.entry
    });
  }
};
</script>

2.2.2 在路由组件中使用MicroApp组件嵌入微应用

AppOne.vue

<template>
  <MicroApp name="vue-app-one" entry="http://localhost:8081" />
</template>

<script>
import MicroApp from "../../components/MicroApp.vue";
export default {
  name: "app-one",
  components: {
    MicroApp
  }
};
</script>

AppTwo.vue

<template>
  <MicroApp name="vue-app-two" entry="http://localhost:8082" />
</template>

<script>
import MicroApp from "../../components/MicroApp.vue";

export default {
  name: "app-two",
  components: {
    MicroApp
  }
};
</script>

2.2.3 主项目路由配置

我们以主微应用都是用history的路由模式

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    component: () => import("../Login.vue")
  },
  {
    path: "/home",
    component: () => import("../Home.vue"),
    redirect: "/vue-app-one",
    children: [
      {
        path: "/vue-app-one*",   // 这个*一定要加, 否则微应用路由跳转时候,加载会不正常
        component: () => import("../views/app-one/AppOne.vue")
      },
      {
        path: "/vue-app-two*", // 这个*一定要加, 否则微应用路由跳转时候,加载会不正常
        component: () => import("../views/app-two/AppTwo.vue")
      }
    ]
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

参考

  • 主应用和微应用不同路由模式下的路径配置方法
  • 以组件的方式使用微应用

你可能感兴趣的:(前端,javascript,vue.js)