qiankun微前端

1,241 阅读3分钟

背景

平台系统如果全部做在一起那必然体量巨大,并且在多人合作下难以维护,拆分成多个子系统是必然结果。 平台系统现在有OMS、SRM,那么就以OMS、SRM来说,用户同时使用这两个系统,需要两个账号,在两个系统切来切去,使用麻烦,最直接的解决办法是采用单点登录,这样用户这需要登录一次,但还是要来回切换浏览器TAB页面,就引申出微前端的概念。

需求:

1、C系统需要同时包含A系统的a、b模块和B系统的c、d模块

2、C系统模块跳转时交互更友好(⾮系统跳转或打开⽅式)

3、A、B、C系统可能存在框架或技术栈不同

image.png

qiankun 优点

技 术 栈 ⽆ 关

独 ⽴ 开 发

独 ⽴ 部 署

增 量 升 级

独 ⽴ 运 ⾏

需要后端做的工作

主应用是一个窗口系统,有账号,需要关联各个系统的账号,拉取各个系统的菜单并且整合成一个新的菜单用来渲染,而各个系统的权限管理仍然在原始系统里面,无需关注【仅仅用来整合菜单】

注册主应用

import { registerMicroApps, start } from 'qiankun'

registerMicroApps([
  {
    name: '/oms',
    entry: '//localhost:8002/',
    container: '#qiankun-subview',
    activeRule: '/oms',
    props: {}
  },
])

start()

注册微应用

// 在`src`目录新增`public-path.js`
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// 设置 `history` 模式路由的 `base`
export default new Router({
  mode: 'history',
  base: window.__POWERED_BY_QIANKUN__ ? '/oms' : process.env.BASE_URL, // oms 是微应用标识
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

import './public-path';
import router from '@/router'
import store from '@/store'

let instance = null
// let router = null
function render (props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}
export async function bootstrap () {
  // console.log('[vue] vue app bootstraped')
}
export async function mount (props) {
  // console.log('OMS挂载', props)
  render(props)
}
export async function unmount () {
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
  // router = null
}
// 是否在`qiankun`环境下运行 - 方便实用
Vue.prototype.$qiankun = window.__POWERED_BY_QIANKUN__

// 修改 `webpack` 配置
module.exports = {
  // 必要
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';
    return config;
  },
  devServer: (_) => {
    const config = _;
    config.headers = {
      'Access-Control-Allow-Origin': '*', // 必要
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;
    return config;
  },
};

应用通信

这里不用官网的通信方式,而通过直接共享主应用的Store去实现通信

// 主应用
registerMicroApps([
  {
    name: '/oms',
    entry: '//localhost:8002/', // 微应用入口地址(需根据环境去写,这里笔者就不具体去写了)
    container: '#qiankun-subview', // 微应用挂载节点
    activeRule: '/oms', // 匹配微应用
    props: {
      store: store, // 注册子应用的时候把sotre传递过去
    }
  },
])

// 子应用
export async function mount (props) {
  Vue.prototype.$mainStore = props.store // 把主应用的store挂载到原型上
  Vue.observable(Vue.prototype.$mainStore) // 设置store为响应式 (否则主应用数据变化,子应用时监听不到的)
  render(props)
}

// 子应用修改主应用store,this.$mainStore.commit() 就是正常的store实例使用方式
// 至此,子应用可以实时访问最新的的主应用store,并且可以修改
// 子应用之间的通信,利用主应用作为bus-store进行通信就可以了

路由方案

对于微应用,只需要路由层级最后一对对应的components,也就是说不需要菜单栏那些东西,在各个子应用使用$qiankun(window.__POWERED_BY_QIANKUN__是否在微应用环境下),同v-if把菜单栏给隐藏了(不隐藏的话,自页面将会有里外两层菜单栏)

菜单融合方案,主应用需要做一个统合各个子应用菜单的功能,就是把不同系统的菜单合并为一个主应用菜单树,todo

子应用时局部挂载的,如果遇到子应用需要占满整个页面(就是不需要主应用的菜单栏layout),需要怎么做?

<template>
  <a-config-provider :locale="locale">
    <div id="qiankun-container">
      <!-- 这个router-view是渲染主应用本身路由页面的区域(仅自身页面) -->
      <router-view />
      
      <!-- 微应用挂载区域 -->
      <!-- fullPage为路由Meta信息,通过这个去判断是否需要layout -->
      <!-- 需要layout,菜单栏用的是主应用的,页面内容用的是子应用的 -->
      <!-- layout里面须有有 id="qiankun-subview"供子应用挂载 -->
      <BaseLayout v-if="!fullPage" />
      <!-- 不需要layout 整个页面都是子应用内容 -->
      <div v-else id="qiankun-subview" />
    </div>
  </a-config-provider>
</template>

常⻅问题

⼦系统未暴露⽣命周期 - xxx died in status LOADING_SOURCE_CODE: [qiankun] You need to export lifecycle functions in xx

1、⼦系统是以umd的模式打包⽽在webpack的配置⾥我们定义的是取package.json的name属性作为 打包后的⽂件名称,因此不同的⼦系统命名相同时就会导致主应⽤⽆法正确识别对应的⼦系统,这也是 为何前⾯强调package.json⾥的name属性必须保证唯⼀,这个错误的话多数情况就是这个原因导致, ⽽不是真正⽣命周期没导出的原因。

2、当然如果⼦系统main.js⾥确实没有将⼦系统的⽣命周期导出的话,那肯定也会出现该错误,这时候 就需要将bootstrap、mount等⽣命周期钩⼦函数export出来