什么是微前端
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. – Micro Frontends
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
特性
- 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
- 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 🛡 样式隔离,确保微应用之间样式互相不干扰。
- 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
快速上手
主应用
安装qiankun
npm i qiankun -S
配置main.ts
// 引入
import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun'
// 注册子应用
registerMicroApps([
{
name: 'child1',
entry: '//localhost:5174',
container: '#container', // app.vue中加载子应用的div的id
activeRule: '#/child1', // 访问子应用的路由路径
props: {
mag: '给child1发消息' // 主应用向微应用传递参数
}
}
])
// 启动
start({
prefetch: 'all', // 预加载
sandbox: {
experimentalStyleIsolation: true // 开启沙箱模式,实验性方案
}
})
// 添加全局异常捕获
addGlobalUncaughtErrorHandler((handler) => {
console.log('异常捕获', handler)
})
配置app.vue
<div id="container"></div>
子应用
安装qiankun适配插件
npm install vite-plugin-qiankun
配置vite.config.ts
// 配置插件
qiankun(`${packName}`, {
useDevMode: true
})
// 配置跨域
headers: {
'Access-Control-Allow-Origin': '*' // 主应用获取子应用时跨域响应头
}
配置index.html
将id=“app”,修改为id=“child1-app”,避免与主应用冲突
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="child1-app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
配置main.ts
// 引入
import { renderWithQiankun, qiankunWindow, QiankunProps } from 'vite-plugin-qiankun/dist/helper'
let router: any = null
let instance: any = null
let history: any = null
declare global {
interface Window {
__POWERED_BY_QIANKUN__: any
__INJECTED_PUBLIC_PATH_BY_QIANKUN__: any
}
}
// renderWithQiankun: 为子应用导出一些生命周期函数 供主应用在特殊的时机调用
// qiankunWindow: qiankunWindow.POWERED_BY_QIANKUN 可判断是否在qiankun环境下
const initQianKun = () => {
renderWithQiankun({
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
// 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
bootstrap() {
console.log('bootstrap')
},
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法,也可以接受主应用传来的参数
mount(_props: any) {
console.log('mount', _props)
render(_props)
},
// 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
unmount(_props: any) {
console.log('销毁child1', _props)
unmountApp()
},
update: function (props: QiankunProps): void | Promise<void> {
console.log('update')
}
})
}
function render(props: any = {}) {
instance = createApp(App)
// 将props挂载到全局上,用于主子应用的交互
instance.config.globalProperties.$sonProps = props
history = createWebHashHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '#/child1' : '/')
// 注册路由
router = createRouter({
history,
routes
})
instance.use(router)
instance.use(createPinia())
instance.mount(props ? props.container.querySelector('#child1-app') : '#child1-app')
if (qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('我正在作为子应用运行。。。')
}
}
const unmountApp = () => {
instance.unmount()
instance._container.innerHTML = ''
instance = null
history.destroy() // 不卸载router会导致其他应用路由失败
router = null
}
// 判断是否为乾坤环境,否则会报错iqiankun]: Target container with #subAppContainerVue3 not existed while subAppVue3 mounting!
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render(null)