一、核心设计理念
- 简单
由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
- 解耦/技术栈无关
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
二、快速上手
- 安装 qiankun
yarn add qiankun # 或者 npm i qiankun -S
2.在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer',
});
3.微应用配置
微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用(主要是指 Vue、React、Angular)需要做的事情有:
新增 public-path.js 文件,用于修改运行时的 publicPath。
注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。
微应用建议使用 history 模式的路由,需要设置路由 base,值和它的 activeRule 是一样的。
在入口文件最顶部引入 public-path.js,修改并导出三个生命周期函数。
修改 webpack 打包,允许开发环境跨域和 umd 打包。
主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是 index.html 和其他的所有文件分开部署的,说明你们已经将构建时的 publicPath 设置为了完整路径,则不用修改运行时的 publicPath (第一步操作可省)。
无 webpack 构建的微应用直接将 lifecycles 挂载到 window 上即可。
此处以vue为例:
1.在src目录新增 public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2.入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。
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)
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log('微应用获取主应用状态', state, prev)
if (state && state.eicloud) {
store.dispatch('setMicroAppLogin', state.eicloud)
} else {
store.dispatch('clearStorage')
}
}, true)
render(props)
}
export async function unmount () {
store.dispatch('clearStorage')
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
三、qiankun微应用keep-alive实现方案
背景:qiankun提供了两种加载子应用的方式 registerMicroApps和loadMicroApp。官方推荐的是registerMicroApps路由匹配激活子应用,当浏览器 url 发生变化时,会自动检查每一个微应用注册的 activeRule 规则,符合规则的应用将会被自动激活。但是在微应用和主应用之间切换时,每次都会刷新状态,没办法保持上次操作状态。此时就需要用到loadMicroApp来手动加载微应用,这种需要手动去调用加载和卸载方法。
实现方案
- 入口文件改造,主应用微应用用动态组件加载
<template>
<div id="app-wrapper">
<keep-alive>
<component :is="currentName" />
</keep-alive>
</div>
</template>
components: { microA, microB, mainApp }, // 注册微应用,主应用组件
setup() {
const currentName = ref('mainApp')
const userStore = useUserStore()
const route = useRoute()
checkPath(route.path)
// 监听路由,手动匹配加载微应用
watch(
() => route.path,
val => {
checkPath(val)
}
)
function checkPath(path) {
const apps = userStore.getMicroApps
const app = apps.find(item => path.startsWith(`/${item.name}`))
currentName.value = app ? app.name : 'mainApp'
if (app) {
nextTick(() => {
userStore.registerMicroApps(app) // 手动加载微应用
})
}
}
return {
currentName
}
}
- user.ts 预加载微应用静态资源、手动调用加载
microAppsInstance: {}
// 预加载微应用静态资源 在登录之后调用
export function prefetchMircoApps() {
const userStore = useUserStore()
const apps = userStore.getMicroApps.map(({ name, entry }) => {
return {
name: name,
entry
}
})
prefetchApps(apps) // 预加载微应用的静态资源
}
export function registerApps(app) {
const userStore = useUserStore()
const microAppsInstance = userStore.getMicroAppsInstance
if (!microAppsInstance[app.name]) {
console.log('手动加载微应用:', app.name)
microAppsInstance[app.name] = loadMicroApp(app)
userStore.setMicroAppsInstance(microAppsInstance)
}
}
// 卸载微应用
export function uninstallMicroApp() {
const appsInstance = userStore.getMicroAppsInstance
for (const key in appsInstance) {
appsInstance[key].unmount()
}
userStore.setMicroAppsInstance({})
}
- 卸载微应用
到现在加载已经完成了,打开主应用和微应用页面,来回切换已经实现了状态保持,接下来就是卸载微应用。标签被关闭的时候去判断是否需要卸载。
// 编辑标签
function handleEdit(targetKey: string, action) {
if (action === 'remove') unmountMicroApp(targetKey)
// Added operation to hide, currently only use delete operation
if (unref(unClose)) {
return
}
tabStore.closeTabByKey(targetKey, router)
}
function unmountMicroApp(targetKey) {
const appsInstance = userStore.getMicroAppsInstance
const appKey = targetKey.split('/')[1]
if (!appsInstance[appKey]) return
// 判断打开的标签中,是否还有该微应用的页面,如果没有则卸载该子应用
if (
!tabStore.getTabList.some(
tab => tab.path !== targetKey && tab.path.startsWith(`/${appKey}`)
)
) {
appsInstance[appKey].unmount()
delete appsInstance[appKey]
userStore.setMicroAppsInstance(appsInstance)
}
}