微前端架构是前端工程化领域解决巨石应用拆分的主流方案。qiankun是蚂蚁集团开源的微前端框架,基于Single-SPA实现,通过JS沙箱和CSS隔离机制让多个独立子应用在同一个页面中运行。本文介绍qiankun的接入配置、子应用隔离机制和实际项目中样式冲突、全局变量污染等问题的解决方案。
qiankun主应用注册子应用配置
主应用负责加载和管理子应用的生命周期。在主应用入口文件中注册子应用:
// main-app/src/micro.ts
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
const microApps = [
{
name: 'user-app',
entry: '//localhost:8081',
container: '#subapp-container',
activeRule: '/user',
props: {
token: localStorage.getItem('token'),
onGlobalStateChange: (state: any) => {
console.log('子应用状态变更:', state)
}
}
},
{
name: 'order-app',
entry: '//localhost:8082',
container: '#subapp-container',
activeRule: '/order',
props: {
apiBase: '/api/v2'
}
}
]
registerMicroApps(microApps, {
beforeLoad: [(app) => {
console.log('加载前:', app.name)
return Promise.resolve()
}],
beforeMount: [(app) => {
console.log('挂载前:', app.name)
return Promise.resolve()
}],
afterUnmount: [(app) => {
console.log('卸载后:', app.name)
return Promise.resolve()
}]
})
start({
prefetch: true,
sandbox: { experimentalStyleIsolation: true },
excludeAssetFilter: (url: string) => {
// 过滤不需要被qiankun劫持的静态资源
return url.includes('analytics') || url.includes('tracker')
}
})
主应用需要为子应用提供挂载容器:
<!-- main-app/src/App.vue -->
<template>
<div id="main-layout">
<nav class="sidebar">
<router-link to="/user">用户管理</router-link>
<router-link to="/order">订单管理</router-link>
</nav>
<main class="content">
<!-- 子应用挂载点 -->
<div id="subapp-container"></div>
</main>
</div>
</template>
子应用接入qiankun生命周期
子应用需要导出bootstrap、mount、unmount三个生命周期函数。以Vue3子应用为例:
// user-app/src/main.ts
import { createApp, type App } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import AppComponent from './App.vue'
import routes from './router/routes'
let app: App<any> | null = null
const APP_NAME = 'user-app'
function render(props: any) {
const { container } = props || {}
app = createApp(AppComponent)
const router = createRouter({
history: createWebHistory(
window.__POWERED_BY_QIANKUN__ ? `/${APP_NAME}` : '/'
),
routes
})
app.use(router)
app.mount(container ? container.querySelector('#app') : '#app')
}
// qiankun生命周期
export async function bootstrap() {
console.log('user-app bootstrap')
}
export async function mount(props: any) {
render(props)
console.log('user-app mounted')
}
export async function unmount() {
if (app) {
app.unmount()
app = null
}
console.log('user-app unmounted')
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
子应用webpack配置需要修改输出格式,使主应用能够通过import-html-entry加载:
// user-app/vue.config.js
const { name } = require('./package.json')
module.exports = {
devServer: {
port: 8081,
headers: { 'Access-Control-Allow-Origin': '*' }
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${name}`
}
}
}
JS沙箱机制与全局变量隔离
qiankun的JS沙箱有三种模式:LegacySandbox(单实例代理沙箱)、ProxySandbox(多实例代理沙箱)、SnapshotSandbox(快照沙箱,降级方案)。ProxySandbox是默认模式,每个子应用拥有独立的window对象副本,互不影响。
沙箱自动隔离window全局变量,但有些场景需要主子应用通信。qiankun提供initGlobalState方法:
// 主应用 - 初始化全局状态
import { initGlobalState } from 'qiankun'
const actions = initGlobalState({
user: null,
theme: 'light'
})
// 监听状态变化
actions.onGlobalStateChange((state, prev) => {
console.log('主应用收到:', state)
})
// 修改状态
actions.setGlobalState({ user: { name: 'admin' } })
// 子应用通过props获取
// 在mount生命周期中
export async function mount(props) {
props.onGlobalStateChange((state, prev) => {
console.log('子应用收到:', state)
})
props.setGlobalState({ theme: 'dark' })
render(props)
}
CSS样式隔离方案
qiankun提供experimentalStyleIsolation(实验性样式隔离)和strictStyleIsolation(严格样式隔离)两种CSS隔离方案。strictStyleIsolation使用Shadow DOM,隔离最彻底但可能导致弹窗组件(如el-dialog、ant-modal)样式丢失,因为弹窗默认挂载到document.body而非Shadow DOM内部。
生产环境推荐使用experimentalStyleIsolation,它通过给子应用所有CSS选择器添加特殊前缀实现隔离:
// qiankun启动配置
start({
sandbox: {
experimentalStyleIsolation: true
// 生成的CSS会变成: div[data-qiankun="user-app"] .el-button { ... }
}
})
对于无法通过框架自动隔离的场景,可以手动添加CSS作用域前缀:
// user-app/src/styles/scoped.scss
// 子应用根容器添加唯一属性选择器
#user-app-root {
.el-button { font-size: 14px }
.el-table { margin: 16px 0 }
}
// Vite构建添加PostCSS前缀插件
// vite.config.ts
import prefixer from 'postcss-prefix-selector'
export default {
css: {
postcss: {
plugins: [
prefixer({
prefix: '#user-app-root',
transform(prefix, selector) {
if (selector.startsWith('html') || selector.startsWith('body')) {
return selector.replace(/^html|^body/, prefix)
}
return `${prefix} ${selector}`
}
})
]
}
}
}
子应用路由与资源路径处理
子应用在qiankun环境下路由base路径会变化,需要动态处理。同时静态资源(图片、字体)的publicPath也需要适配:
// user-app/src/main.ts
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置publicPath
// @ts-ignore
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
// 路由配置中处理base路径
const router = createRouter({
history: createWebHistory(
window.__POWERED_BY_QIANKUN__ ? '/user' : '/'
),
routes
})
// 防止子应用路由跳转影响主应用
router.afterEach((to) => {
if (window.__POWERED_BY_QIANKUN__) {
// 确保URL包含子应用前缀
if (!to.path.startsWith('/user')) {
window.history.pushState(null, '', '/user' + to.path)
}
}
})
生产环境部署与常见问题排查
生产环境中子应用通常部署在不同域名或路径下,需要配置CORS和CDN。子应用资源跨域问题是最常见的报错,确保子应用服务器返回正确的CORS头部:
// Nginx配置子应用CORS
location /user-app/ {
add_header Access-Control-Allow-Origin 'https://main.example.com';
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers '*';
try_files $uri $uri/ /index.html;
}
资源加载404问题通常因为子应用publicPath配置错误。解决方法是子应用入口JS中动态设置publicPath。应用切换闪烁问题可以通过qiankun的prefetch预加载和loading配置解决。内存泄漏问题主要出现在子应用卸载时未清理定时器、事件监听器和全局变量,在unmount生命周期中必须手动清理。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-qian-duan-jia-gou-shi-zhan-qiankun-zi-ying-yong-ge-li/