构建vue子应用

更新时间: 2021-01-29 13:28:44

上一讲教大家如何将普通vue项目改造成主应用,这一讲教大家如何将普通vue项目改造成子应用

# 创建vue项目

首先创建一个vue项目,使用vue-cli4,过程不再赘述

# 配置微应用

子应用本身并不需要安装qiankun。 将普通的项目改造成qiankun子应用,需要进行两步操作:

  1. 导出相应的生命周期钩子;
  2. 配置微应用的打包工具

# 导出相应的生命周期钩子

微应用需要在自己的入口 js (通常就是你配置的 webpackentry js) 导出 bootstrapmountunmount三个生命周期钩子,以供主应用在适当的时机调用。

  /**
  * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
  * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
  */
  export async function bootstrap() {
      console.log('react app bootstraped');
  }
  /**
  * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
  */
  export async function mount(props) {
      ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
  }
  /**
  * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
  */
  export async function unmount(props) {
      ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
  }
  /**
  * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
  */
  export async function update(props) {
      console.log('update props', props);
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

首先我们在子应用的入口文件main.js中,导出qiankun主应用所需要的三个生命周期钩子函数,代码实现如下

  // src/main.js


  import Vue from 'vue'
  import VueRouter from 'vue-router'

  import "./public-path";

  import App from './App.vue'
  import routes from "./router"

  import store from './store'

  const packageName = require('../package.json').name;

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

  let instance = null;
  let router = null;

  /**
  * 渲染函数
  * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
  */
  function render(props) {
      // 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
      router = new VueRouter({
          routes,
      });

      // 判断 qiankun 环境则进行路由拦截,判断跳转路由是否有 packageName 开头前缀,没有则加上
      if(window.__POWERED_BY_QIANKUN__){
          router.beforeEach((to, from, next) => {
              if(!to.path.includes('/'+packageName)){
                  next({
                  path: '/' + packageName + to.path
                  })
              }else{
                  next()
              }
          })
      }

      // 挂载应用
      instance = new Vue({
          router,
          store,
          render: (h) => h(App),
      }).$mount("#app");
  }

  // 独立运行时,直接挂载应用
  if (!window.__POWERED_BY_QIANKUN__) {
      render();
  }

  /**
  * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
  * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
  */
  export async function bootstrap() {
      console.log("VueMicroApp bootstraped");
  }

  /**
  * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
  */
  export async function mount(props) {
      console.log("VueMicroApp mount", props);
      render(props);
  }

  /**
  * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
  */
  export async function unmount() {
      console.log("VueMicroApp unmount");
      instance.$destroy();
      instance = null;
      router = null;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 设置public-path.js

我们将render函数进行了封装,如此一来保证了子应用可以单独运行,也可以在主应用中运行时在mount钩子里触发render函数 webpack 默认的 publicPath 为 "" 空字符串,会基于当前路径来加载资源。我们在主应用中加载微应用时需要重新设置 publicPath,这样才能正确加载微应用的相关资源。(public-path.js 具体实现在后面)

  // src/public-path.js
  if (window.__POWERED_BY_QIANKUN__) {
      // 动态设置 webpack publicPath,防止资源加载出错
      // eslint-disable-next-line no-undef
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }
1
2
3
4
5
6

# 配置子应用自身路由

网上资料大多数都使用history模式路由,但是此模式比较依赖后端,所以我将路由模式改成了 hash 模式

  // src/router/index.js

  const packageName = require('../../package.json').name;

  // 判断是 qiankun 环境则增加路由前缀
  let prefix = ''
  if(window.__POWERED_BY_QIANKUN__){
      prefix = '/'+packageName
  }

  const routes = [
      {
          path:prefix + "/",
          name:"Map",
          component:() => import('@/pages/map.vue')
      },
      {
          path:prefix + "/three",
          name:"Three",
          component:() => import('@/pages/three.vue')
      },
      {
          path:prefix + "/data",
          name:"Getdata",
          component:() => import('@/pages/getData.vue')
      },
      {
          path:prefix + "/query",
          name:"Query",
          component:() => import('@/pages/projects.vue')
      },
      {
          path:prefix + "/charts",
          name:"Charts",
          component:() => import('@/pages/charts.vue')
      }
  ]

  export default routes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 配置子应用的打包工具

我们配置好main.js和路由后,还需要配置webpack,这个项目中我们配置 vue.config.js,代码实现如下:

  const path = require("path");
  const packageName = require('./package.json').name;

  module.exports = {
  devServer: {
      // 监听端口
      port: 10300,
      // 关闭主机检查,使微应用可以被 fetch
      disableHostCheck: true,
      // 配置跨域请求头,解决开发环境的跨域问题
      headers: {
          "Access-Control-Allow-Origin": "*",
      },
  },
  configureWebpack: {
      resolve: {
          alias: {
              "@": path.resolve(__dirname, "src"),
          },
      },
      output: {
          // 微应用的包名,这里与主应用中注册的微应用名称一致
          library: `${packageName}-[name]`,
          // 将你的 library 暴露为所有的模块定义下都可运行的方式
          libraryTarget: "umd",
          // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
          jsonpFunction: `webpackJsonp_${packageName}`,
      },
    },
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

我们需要重点关注一下output选项,当我们把 libraryTarget 设置为 umd 后,我们的 library 就暴露为所有的模块定义下都可运行的方式了,主应用就可以获取到微应用的生命周期钩子函数了。

到这里子应用已经改造完毕了,下一讲我们学习如何将子应用接入到主应用