需求来源
近期公司新的产品需要将之前的vue做的项目和react做的项目进行复用,我们第一事件的解决方案肯定是使用iframe,但是iframe方案有几个痛点时无法解决的: 1.弹框遮罩层只能覆盖于父级,无法遮罩全屏;2.主应用和子应用之间的通信比较麻烦,需要通过postMessage实现。最近微服务很火,我们也准备在这个产品上实践。经过调研我们决定使用qiankun。主应用为vue + antd vue,子应用为vue + antd vue 或者 react + antd,主子应用路由模式都为hash模式。
什么是微前端
- 微前端架构具备以下几个核心价值:
- 技术栈无关 主框架不限制接入应用的技术栈,微应用具备完全自主权
- 独立开发、独立部署 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 增量升级 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好 的实施渐进式重构的手段和策略
- 独立运行时 每个微应用之间状态隔离,运行时状态不共享
qiankun 简介
qiankun(乾坤)是由蚂蚁金服推出的基于Single-Spa实现的前端微服务框架。
- qiankun 的核心设计理念
- 简单 由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
- 解耦/技术栈无关 微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
主应用配置
依赖下载yarn add qiankun
// main.js
import { registerMicroApps, start } from 'qiankun'
new Vue({
router,
store,
render: h => h(App),
mounted() {
// hash 路由下验证rule
const getActiveRule = hash => location => location.hash.startsWith(hash)
registerMicroApps([
{
name: 'vue-qiankun-sub', // 子应用package.json name
entry: '//localhost:8081', // 子应用本地调试地址
container: '#sub-container', // 挂载的dom节点
activeRule: getActiveRule('#/sub-vue'),
},
])
start()
},
}).$mount('#app')
路由path添加activeRule
{
path: '/sub-vue/about',
name: 'About',
component: () => import('@/views/Qiankun.vue'),
meta: {
title: '子应用',
keepAlive: true,
},
},
qiankun.vue
<template lang="pug">
page-header-wrapper
#sub-container
</template>
<script>
export default {
name: 'Qiankun',
data() {
return {}
},
beforeCreate() {
this.$router.push({
query: {
...this.$route.query,
hideMenus: true,
},
})
},
}
</script>
<style lang="less" scoped>
#sub-container {
width: 100%;
height: 100%;
}
</style>
子应用配置
在 src 目录新增 public-path.js,并在main.js文件中引入
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
入口文件 main.js 修改,根id名称最好不要和主应用重复。
let instance = null
let router = null
const render = ({ container } = {}) => {
router = routes
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#vue-app') : '#vue-app')
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export async function bootstrap() {
console.log('vue app bootstraped')
}
export async function mount(props) {
console.log('props from main framework', props)
// 首次render在动态路由后进行执行,否则路由会报相关错误,之后则每次进入页面后执行
render(props)
}
export async function unmount() {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
router = null
}
vue.config.js
{
...other,
// qiankun 配置
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
port: '8081',
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
}
路由文件修改
let prefix = ''
if (window.__POWERED_BY_QIANKUN__) {
prefix = '/sub-vue'
}
{
path: prefix + '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: {
title: '关于',
keepAlive: true,
},
},
具体项目地址
主应用地址 主应用需要切换到qiankun分支 vue 子应用地址
需要注意的点
- 主应用路由跳转时,主应用path在子应用路由菜单path中必须存在且一致
- react为子应用时,路由切换不跳转问题解析,相关issue地址: hashchange
- 主应用和子应用在样式名称重复情况下,可能会导致样式问题,使用ui库改变class前缀进行样式隔离
- 主应用和子应用的根结点id名称尽量不要一致