美文网首页
基于 qiankun 的微前端实践

基于 qiankun 的微前端实践

作者: 小前端c | 来源:发表于2021-09-07 10:25 被阅读0次

    一、业务背景

    平台系统业务越来越庞大,十几个子应用模块,且部分业务需要同时在两个平台增加一样的模块,基于后续相同需求、分解业务模块、功能模块复用、降低维护成本以及技术同学对自身的成长需求,所以我们选定 乾坤(qiankun) 来解构应用。

    二、乾坤(qiankun)

    1、介绍

    https://qiankun.umijs.org/

    2、优缺点

    • 优点:技术栈无关、代码库隔离、独立部署、应用通信
    • 缺点:性能降低

    三、实现

    根据我们当前的项目业务,创建两个主应用基座App1App2

    微前端.png

    1、主应用

    src根节点下添加qiankun文件夹

    • qiankun/apps.js
     {
        name: 'handbill',
        entry: 'http://localhost:8088',
        container: '#handbillContainer',
        activeRule: 'handbill'
      }
    ]
    module.exports = apps
    
    • qianun/init.js
    import {
      registerMicroApps,
      start
      // setDefaultMountApp
    } from 'qiankun'
    
    import apps from '@/qiankun/apps'
    
    export default function qiankunInit() {
      // 注册微应用
      registerMicroApps(
        apps.map(app => {
          return { ...app }
        }),
        {
          beforeLoad: [
            app => {
              console.log('[初始化微应用 beforeLoad]', app.name)
            }
          ],
          beforeMount: [
            app => {
              console.log('[初始化微应用 LifeCycle]', app.name)
            }
          ],
          afterUnmount: [
            app => {
              console.log('[初始化微应用 afterUnmount]', app.name)
            }
          ]
        }
      )
    
      // 默认加载第一个微应用
      // setDefaultMountApp(apps[0].activeRule)
    
      // 启动 qiankun
      start({
        sandbox: { strictStyleIsolation: true } // 开启沙盒模式
      })
    }
    

    在 src/layouts/App/index.vue 文件下 路由入口添加子应用容器ID: subapp-viewport

    <transition name="fade-transform" mode="out-in">
        <div id="subapp-viewport">
            <keep-alive :include="include" :max="8">
                <router-view />
            </keep-alive>
        </div>
    </transition>
    

    引入qiankun初始化

    import qiankunInit from '@/qiankun/init'
    
    mounted() {
        qiankunInit()
    }
    

    2、微应用

    微应用不需要下载qiankun依赖

    src根节点下添加public-path文件

    /* eslint-disable */
    if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
    }
    /* eslint-disable */
    

    src/main.js改造

    import './public-path
    
    import Vue from 'vue'
    import App from './App.vue'
    import router from '@/router'
    import store from '@/store'
    
    let instance = 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('[vue] props from main framework', props)
      render(props)
    }
    export async function unmount() {
      instance.$destroy()
      instance.$el.innerHTML = ''
      instance = null
      router = null
    }
    

    vue.config.js改造

    "Access-Control-Allow-Origin": "*"
    configureWebpack.output = {
    
    library: `${name}-[name]`,
    
    libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    
    jsonpFunction: `webpackJsonp_${name}`,
    
    }
    
    const { name } = require('./package');
    
    module.exports = {
      publicPath: '/',
      outputDir: 'dist',
      devServer: {
        headers: {
          "Access-Control-Allow-Origin": "*"
        },
        port: 8088,
        https: false,
        hotOnly: true,
        disableHostCheck: true,
        open: true,
        before: app => {}
      },
      productionSourceMap: false,
      configureWebpack: {
        output: {
          library: `${name}-[name]`,
          libraryTarget: 'umd', // 把微应用打包成 umd 库格式
          jsonpFunction: `webpackJsonp_${name}`,
        }
      }
    }
    

    可以基于qiankun的全局变量判断是否微应用打开,隐藏菜单、头部、底部等公共组件。以达到可独立运行又可为微应用引入

    / 是否为微应用引入
    get isMicroApp () {
        return !!window.__POWERED_BY_QIANKUN__
    }
    

    3、应用通信

    https://qiankun.umijs.org/zh/api#initglobalstatestate

    主应用定义全局状态

    全局状态只能在主应用中定义

    import { initGlobalState } from 'qiankun'
    
    // 全局状态数据
    const state = {
        loading: false, // 加载动画
    }
    
    // 初始化全局状态
    const actions = initGlobalState(state)
    
    // 监听全局状态
    actions.onGlobalStateChange((state, prev) => {
        // state: 变更后的状态; prev 变更前的状态
    })
    // 打开加载动画
    state.loading = true
    // 设置全局状态 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
    actions.setGlobalState(state)
    
    // 移除当前应用的状态监听,微应用 umount 时会默认调用
    actions.offGlobalStateChange()
    

    微应用定义全局状态

    跟主应用使用规则基本一致,不需要定义全局状态

    // src/main.js
    const globalState = null
    export async function mount(props) {
      // 监听全局状态
      props.onGlobalStateChange((state, prev) => {
        globalState = state
        console.log('微应用 state', state)
        console.log('微应用 prev', prev)
      })
      // 关闭加载动画
      globalState.loading = false
      // 设置全局状态 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
      props.setGlobalState(globalState)
      render(props)
    }
    

    四、踩坑记录

    探索qiankun的过程中碰到最多的就是找不到容器节点

    not existed while xx mounting!

    not existed while xxx loading!

    查了两天发现是index.html引入的地图插件导致容器节点被重绘,导致qiankun的初始化过程中找不到容器,修改为依赖引入就好

    相关文章

      网友评论

          本文标题:基于 qiankun 的微前端实践

          本文链接:https://www.haomeiwen.com/subject/fgvuwltx.html