微前端系列 - 应用集成(qiankun + umi + vue)

2,067 阅读3分钟

微前端项目选型

主应用:umi + Ant Design 子应用:vue + element

路由模式:主应用(history),子应用 (hash)

基础配置

1. 主应用 qiankun 配置

(1) 安装依赖包

yarn add -D @umijs/plugin-qiankun

(2) 注册子应用

子应用注册有两种方式,二选一即可:

a.插件构建期配置子应用
// config.js

export default: {
    qiankun: {
        master: {
            // 注册子应用信息
            apps: [
                {
                    name: 'app1',
                    entry: '//localhost:7001'
                },
                {
                    name: 'app2',
                    entry: '//localhost:7002'
                }
            ]
        }
    }
}
b.运行时动态配置子应用(src/app.ts里开启)
// 从接口中获取子应用配置,export 出的 qiankun 变量时一个 promise
export const qiankun = fetch('/config').then(({ apps }) => ({
    // 注册子应用信息
    apps,
    lifeCycles: {
        afterMount: (props) => {
            console.log(props);
        },
    },
    // 支持更多的其他配置,详细请看这里:https://qiankun.umijs.org/zh/api/#start-opts
}));

(3) 装载子应用

子应用的装载有两种方式,二选一即可:

a.使用路由绑定的方式

建议使用这种方式来引入自带路由的子应用

// config/router.config.ts
export default {
  routes: [
    // 配置子应用 reactApp 关联的路由
    {
      name: 'react子应用',
      path: '/subreact',
      microApp: 'reactApp',
    },
    // 配置子应用 vueApp 关联的路由
    {
      name: 'vue子应用',
      path: '/subvue',
      microApp: 'vueApp',
    },
  ],
};
b.使用 <MicroApp /> 组件的方式

建议使用这种方式来引入不带路由的子应用。

import { MicroApp } from 'umi'

export function MyPage() {
    return (
        <div>
            <MicroApp name="app1" />
        </div>
    )
}

2. 子应用 qiankun 配置

(1) 入口文件 main.js

子应用需要在自己的入口文件导出 bootstrap, mount, unmount 三个声明周期钩子,以供主应用在适当的时机调用。

bootstrap: 只会在子应用初始化的时候调用一次,下次子应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。

mount:应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法。

unmount:应用每次切除/卸载会调用的方法,通常在这里我们会卸载子应用的应用实例。

let instance = null;
function render() {
    instance = new Vue({
        router, store, render: h => h(App)
    }).$mount('#app')
}

// 独立运行时
if(!window.__POWERED_BY_QIANKUN__) {
    render()
}

export async function bootstrap() {
    console.log('[vue] vue app bootstraped')
}

export async function mount(props) {
    console.log('[vue] props from main framework', props)
    render(props)
}

export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
}

(2) 在项目根目录下,新增文件 public-path.js

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

(3) 配置文件 vue.config.js 新增

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

const port = 9528;
module.exports = {
    publicPath: process.env.ENV === 'development' ? `//localhost:${port}` : './',
    devServer: {
        port: port,
        headers: {
            // 允许跨域
            'Access-Control-Allow-Origin': '*'
        }
    },
    configureWebpack: {
        output: {
          // 输出暴露的名称
          library: `${name}-[name]`,
          libraryTarget: 'umd', // 将微应用打包成 umd 库格式
          jsonpFunction: `webpackJsonp_${name}`
        }
    }
}

3. 主子应用实现通信配置

(1) 主应用 umi 项目 向 vue 子应用通信

  • 新建 src => action.ts 文件
import { initGlobalState } from 'qiankun';

const initialState = {
    // 初始化数据
    projectId: ''
};

// 初始化 state 
const actions = initGlobalState(initialState);

export default actions;
  • 在组件中使用 actions
import actions from '@/actions';

export function ProComp() {
    const onChangePro = () => {
        actions.setGlobalState({ projectId: '123' })
    };
    return <button onClick={onChangePro} >更改项目信息</button>
}

(2) 子应用 vue 项目

使用 qiankun 官方提供的通信方式 - Actions 通信去实现

  • 新建 src => actions.js 文件
function emptyAction() {
    // 设置一个 actions 实例
    // 提示当前使用的是空 Action
    console.log('Current execute action is empty!')
}

class Actions {
    // 默认值为空 Action
    actions = {
        onGlobalStateChange: emptyAction,
        setGlobalState: emptyAction
    }
    
    // 设置 actions
    setActions(actions) {
        this.actions = actions;
    }
    
    // 映射,注册观察者函数,响应 globalState 变化
    onGlobalStateChange(...args) {
        return this.actions.onGlobalStateChange(...args)
    }
    
    // 映射,设置 globalState
    setGlobalState(...args) {
        return this.actions.setGlobalState(...args)
    }
}

const actions = new Actions();
export default actions;
  • 入口文件 main.js

在 mount 生命周期里注入 actions 实例

import actions from './actions';
export async function mount(props) {
    actions.setActions(props);  // 注入 actions 实例
    render(props)
}
  • 使用范例

<template>  
  <div>  
    <div>这是子应用</div>  
    <p>接收到的消息: {{mes}}</p>  
    <button @click= "btnClick">点击向父应用发送消息</button>  
  </div>  
</template>  
<script>  
import actions from '../actions'; // 导入实例  
export default {  
  data() {  
    return {  
      mes: '',  
    }  
  },  
  mounted() {  
    actions.onGlobalStateChange((state) => { // 监听全局状态  
      this.mes = state  
    }, true);  
  },  
  methods:{  
    btnClick(){  
      actions.setGlobalState({ info: '123'}) // 改变全局状态  
    }  
  }  
}  
</script>

子应用改造

登录逻辑改造

打通登录权限,主应用登录后,切换子应用不需要再次进行登录检验,实现免登录功能

未命名白板 (1).png