微前端qiankun实践——手动挂载篇

1,415 阅读2分钟

一、项目背景

最近一直在做toB业务,手里负责多个后台管理系统(内网使用),最近要对外开放一个平台,其中需要运用到存量系统的部分能力,避免代码重复搬运以及考虑到后续对其他系统的拓展能力,打算封装该子系统,方便其他系统引入。

1.1 现状分析

  • 新起工程,并作为基座工程(给它起个名字叫做parent),使用vue-cli2搭建
  • 待引入的应用(也给它起个名字叫做child1)是vue-cli3搭建
  • 主应用有自己的页面,需求:child1工程的一个A页面想在parent中使用,由于A页面非常复杂,是一个基于canvas绘制的可拖拽的流程图页面。刚开始想的是直接搬过去,而且还需要花费大量的时间去联调,后续页面只要有需求迭代会重复修改,作为一个开发肯定会考虑效率问题(主要是想偷个懒^_^)!!!

1.2 思路

过一番调研,决定使用当下比较火的qiankun来完成改造。

  1. 主应用中新增页面,作为子应用加载入口
  2. 页面添加DOM元素作为子应用的容器节点
  3. 主工程点击菜单跳转页面,用官网提供的API loadMicroApp(app, configuration?)加载子应用
  4. 通知子应用setGlobalState(state)
  5. http部分,主应用服务端提供转发接口/appProxy/child;修改子应用拦截器,所有子应用请求URL重写为/appProxy/child,子应用的请求URL、methods等信息作为参数传递;主应用服务端转发http请求
  6. 子应用订阅消息onGlobalStateChange(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void
  7. 子应用路由跳转到指定页面

qiankun手动加载.png

二、技术实现

2.1主应用

  • 安装 qiankun
npm install qiankun -S
  • 初始化方法micro.js
import { initGlobalState,loadMicroApp } from 'qiankun'
let state = {
    user:null
}
const { setGlobalState,onGlobalStateChange } = initGlobalState(state)
export const onGlobalStateChangeMicro = onGlobalStateChange
export const setGlobalStateMicro = setGlobalState
  • 新建页面组件,作为子应用的入口,调用loadMicroApp 、setGlobalStateMicro
<template>
    <div>
        <div id="child"></div>
    </div>
</template>
<script>
import { loadMicroApp } from 'qiankun'
import { setGlobalStateMicro } from '@/micro/micro.js'
export default {
    mounted: {
        loadMicroApp({
            name:'child', //与子应用的配置的library一致
            entry:'//localhost:9000/', // 微应用的入口
            container:'#child' //微应用容器
        },{
            sandBox:{
                strictStyleIsolation: true // 开启严格的样式隔离模式
            }
        })
        setGlobalStateMicro({
            path:'/childpage', //子应用页面路由path
            query:{} //路由跳转携带参数
        })
    }
}
</script>

2.2 子应用

  • 新建publicPath.js文件,并在main.js中引入
if(window.__POWERD_BY_FUSION){
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_FUSION__
}
  • main.js声明并导出生命周期函数
export async function bootstrap(){}
export async function mount(){}
export async function unmount(){}
  • 修改webpack配置
output:{
    library:'child',
    libraryTarget:'umd',
    jsonpFunction:'webpackJsonp_child'
}
  • 修改路由配置,不能出现通配符*
  • 在mount生命周期中,接收主工程数据,跳转A页面
export asunc function mount(props){
    render(props)
    props.onGlobalStateChange((state,prev)=>{
        let { path,query } = state
        setTimeout(()=>{
            vm.$router.push({path,query})
        })
    },true)
}
  • http请求处理,修改axios请求拦截器
axios.interceptors.request.use((requestConfig)=>{
    const isMicro = !!window.__INJECTED_PUBLIC_PATH_BY_FUSION__
    IF(isMicro){
        if(requestConfig.method == 'get'){
            let _url = requsetConfig.url
            let _methods = 'get'
            let arr = []
            for(let p in requestConfig.params){
                arr.push(`${p}=${requestConfig.params[p]}`)
            }
            requsetConfig.url = '/appProxy/child'
            requestConfig.params = {
                _url:_url + arr.join('&'),
                _methods
            }
            return requestConfig
        }else{
            let _url = requsetConfig.url
            let _methods = 'post'
            requsetConfig.url = '/appProxy/child'
            requestConfig.params = {
                _url,_methods
            }
            return requestConfig
        }
        
    }
    return requestConfig
})
  • 子应用独立运行
let vm = null
function render (props = {}){
    const { container } = props
    vm = new Vue({
        router,
        store,
        render:h=>h(App)
    }).$mount(container ? container.querySelector('#child') : '#child')
}
if(!window.__POWERED_BY_FUSION__){
    render()
}

更多配置可参考qiankun官方文档