Vue 3 微前端架构实战:Module Federation 与 qiankun 深度解析
Orion K Lv6

随着前端应用规模的不断增长,微前端架构成为解决大型应用开发和维护难题的重要方案。本文将深入探讨 Vue 3 在微前端架构中的应用,包括 Module Federation 和 qiankun 两种主流方案的实战经验。

微前端架构概述

1. 微前端核心概念

微前端的优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 微前端架构特点
export const microfrontendBenefits = {
// 技术栈无关
technologyAgnostic: {
description: '不同团队可以使用不同的技术栈',
examples: ['Vue 3', 'React 18', 'Angular 15', 'Svelte']
},

// 独立部署
independentDeployment: {
description: '各个微应用可以独立开发、测试和部署',
benefits: ['降低部署风险', '提高发布频率', '减少团队间依赖']
},

// 团队自治
teamAutonomy: {
description: '团队可以独立决策技术选型和开发流程',
aspects: ['代码组织', '构建工具', '测试策略', '发布流程']
},

// 渐进式升级
incrementalUpgrade: {
description: '可以逐步迁移和升级现有应用',
strategies: ['路由级拆分', '页面级拆分', '组件级拆分']
}
}

// 微前端挑战
export const microfrontendChallenges = {
// 复杂性增加
complexity: {
areas: ['应用间通信', '状态管理', '路由协调', '样式隔离'],
solutions: ['统一通信协议', '共享状态管理', '路由守卫', 'CSS-in-JS']
},

// 性能考虑
performance: {
issues: ['重复依赖', '加载时间', '内存占用'],
optimizations: ['依赖共享', '懒加载', '缓存策略']
},

// 开发体验
developerExperience: {
challenges: ['本地开发', '调试困难', '版本管理'],
improvements: ['开发代理', '调试工具', '版本锁定']
}
}

2. 架构设计原则

微前端设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// src/types/microfrontend.ts

/**
* 微前端应用配置
*/
export interface MicrofrontendApp {
name: string
entry: string
container: string
activeRule: string | ((location: Location) => boolean)
props?: Record<string, any>
loader?: (loading: boolean) => void
sandbox?: boolean | SandboxConfig
singular?: boolean
}

/**
* 沙箱配置
*/
export interface SandboxConfig {
strictStyleIsolation?: boolean
experimentalStyleIsolation?: boolean
excludeAssetFilter?: (assetUrl: string) => boolean
}

/**
* 应用间通信接口
*/
export interface MicrofrontendCommunication {
// 全局状态
globalState: {
get<T>(key: string): T | undefined
set<T>(key: string, value: T): void
subscribe(callback: (state: Record<string, any>) => void): () => void
}

// 事件总线
eventBus: {
emit(event: string, data?: any): void
on(event: string, callback: (data?: any) => void): () => void
off(event: string, callback?: (data?: any) => void): void
}

// 路由通信
router: {
push(path: string, state?: any): void
replace(path: string, state?: any): void
go(delta: number): void
}
}

/**
* 微前端生命周期
*/
export interface MicrofrontendLifecycle {
bootstrap?: (props?: any) => Promise<void>
mount?: (props?: any) => Promise<void>
unmount?: (props?: any) => Promise<void>
update?: (props?: any) => Promise<void>
}

Module Federation 实战

1. 基础配置

主应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// apps/shell/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
plugins: [
vue(),
federation({
name: 'shell',
remotes: {
// 用户管理微应用
userApp: {
external: 'http://localhost:3001/assets/remoteEntry.js',
format: 'esm',
from: 'vite'
},
// 产品管理微应用
productApp: {
external: 'http://localhost:3002/assets/remoteEntry.js',
format: 'esm',
from: 'vite'
},
// 订单管理微应用
orderApp: {
external: 'http://localhost:3003/assets/remoteEntry.js',
format: 'esm',
from: 'vite'
}
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.3.0'
},
'vue-router': {
singleton: true,
requiredVersion: '^4.2.0'
},
pinia: {
singleton: true,
requiredVersion: '^2.1.0'
},
axios: {
singleton: true,
requiredVersion: '^1.4.0'
},
'element-plus': {
singleton: true,
requiredVersion: '^2.3.0'
}
}
})
],

build: {
target: 'esnext',
minify: false,
cssCodeSplit: false,
rollupOptions: {
external: ['vue', 'vue-router', 'pinia']
}
},

server: {
port: 3000,
cors: true
}
})

微应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// apps/user-app/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
plugins: [
vue(),
federation({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserModule': './src/modules/UserModule.vue',
'./UserRoutes': './src/router/routes.ts',
'./UserStore': './src/stores/user.ts'
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.3.0'
},
'vue-router': {
singleton: true,
requiredVersion: '^4.2.0'
},
pinia: {
singleton: true,
requiredVersion: '^2.1.0'
}
}
})
],

build: {
target: 'esnext',
minify: false,
cssCodeSplit: false,
rollupOptions: {
external: ['vue', 'vue-router', 'pinia']
}
},

server: {
port: 3001,
cors: true
}
})

2. 动态模块加载

模块加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// apps/shell/src/utils/moduleLoader.ts

/**
* 动态模块加载器
*/
export class ModuleLoader {
private loadedModules = new Map<string, any>()
private loadingPromises = new Map<string, Promise<any>>()

/**
* 加载远程模块
*/
async loadModule<T = any>(moduleName: string, exposedModule: string): Promise<T> {
const moduleKey = `${moduleName}/${exposedModule}`

// 如果已经加载过,直接返回
if (this.loadedModules.has(moduleKey)) {
return this.loadedModules.get(moduleKey)
}

// 如果正在加载,返回加载 Promise
if (this.loadingPromises.has(moduleKey)) {
return this.loadingPromises.get(moduleKey)
}

// 开始加载模块
const loadingPromise = this.doLoadModule<T>(moduleName, exposedModule)
this.loadingPromises.set(moduleKey, loadingPromise)

try {
const module = await loadingPromise
this.loadedModules.set(moduleKey, module)
return module
} catch (error) {
console.error(`Failed to load module ${moduleKey}:`, error)
throw error
} finally {
this.loadingPromises.delete(moduleKey)
}
}

/**
* 执行模块加载
*/
private async doLoadModule<T>(moduleName: string, exposedModule: string): Promise<T> {
try {
// 动态导入远程模块
const module = await import(/* @vite-ignore */ `${moduleName}/${exposedModule}`)
return module.default || module
} catch (error) {
// 加载失败时的降级处理
console.warn(`Module ${moduleName}/${exposedModule} load failed, using fallback`)
return this.getFallbackModule<T>(moduleName, exposedModule)
}
}

/**
* 获取降级模块
*/
private getFallbackModule<T>(moduleName: string, exposedModule: string): T {
// 返回默认的降级组件或模块
if (exposedModule.includes('Module')) {
return {
template: '<div class="module-error">模块加载失败</div>'
} as T
}

if (exposedModule.includes('Routes')) {
return [] as T
}

if (exposedModule.includes('Store')) {
return {} as T
}

throw new Error(`No fallback available for ${moduleName}/${exposedModule}`)
}

/**
* 预加载模块
*/
async preloadModule(moduleName: string, exposedModule: string): Promise<void> {
try {
await this.loadModule(moduleName, exposedModule)
} catch (error) {
console.warn(`Preload failed for ${moduleName}/${exposedModule}:`, error)
}
}

/**
* 卸载模块
*/
unloadModule(moduleName: string, exposedModule: string): void {
const moduleKey = `${moduleName}/${exposedModule}`
this.loadedModules.delete(moduleKey)
this.loadingPromises.delete(moduleKey)
}

/**
* 清理所有模块
*/
clear(): void {
this.loadedModules.clear()
this.loadingPromises.clear()
}
}

// 导出单例
export const moduleLoader = new ModuleLoader()

动态路由注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// apps/shell/src/router/dynamicRoutes.ts
import type { RouteRecordRaw } from 'vue-router'
import { moduleLoader } from '@/utils/moduleLoader'
import router from './index'

/**
* 动态路由管理器
*/
export class DynamicRouteManager {
private registeredRoutes = new Set<string>()

/**
* 注册微应用路由
*/
async registerMicrofrontendRoutes(appName: string): Promise<void> {
if (this.registeredRoutes.has(appName)) {
return
}

try {
// 加载微应用的路由配置
const routes = await moduleLoader.loadModule<RouteRecordRaw[]>(
appName,
'UserRoutes'
)

// 为路由添加前缀
const prefixedRoutes = this.addRoutePrefix(routes, `/${appName}`)

// 注册到主路由
prefixedRoutes.forEach(route => {
router.addRoute(route)
})

this.registeredRoutes.add(appName)
console.log(`✅ Routes registered for ${appName}`)

} catch (error) {
console.error(`❌ Failed to register routes for ${appName}:`, error)
}
}

/**
* 为路由添加前缀
*/
private addRoutePrefix(routes: RouteRecordRaw[], prefix: string): RouteRecordRaw[] {
return routes.map(route => ({
...route,
path: `${prefix}${route.path}`,
children: route.children ? this.addRoutePrefix(route.children, '') : undefined
}))
}

/**
* 卸载微应用路由
*/
unregisterMicrofrontendRoutes(appName: string): void {
if (!this.registeredRoutes.has(appName)) {
return
}

// 移除路由(Vue Router 4 不直接支持移除路由,需要重新创建路由实例)
// 这里可以通过重新注册基础路由来实现
this.registeredRoutes.delete(appName)
console.log(`🗑️ Routes unregistered for ${appName}`)
}

/**
* 获取已注册的应用
*/
getRegisteredApps(): string[] {
return Array.from(this.registeredRoutes)
}
}

export const dynamicRouteManager = new DynamicRouteManager()

3. 微应用组件

微应用容器组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!-- apps/shell/src/components/MicrofrontendContainer.vue -->
<template>
<div class="microfrontend-container">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<el-skeleton :rows="5" animated />
<div class="loading-text">正在加载 {{ appName }} 应用...</div>
</div>

<!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<el-result
icon="error"
title="应用加载失败"
:sub-title="error.message"
>
<template #extra>
<el-button type="primary" @click="retry">
重新加载
</el-button>
</template>
</el-result>
</div>

<!-- 微应用内容 -->
<Suspense v-else>
<template #default>
<component
:is="microfrontendComponent"
v-bind="componentProps"
@error="handleComponentError"
/>
</template>

<template #fallback>
<div class="suspense-loading">
<el-skeleton :rows="3" animated />
</div>
</template>
</Suspense>
</div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { moduleLoader } from '@/utils/moduleLoader'
import { dynamicRouteManager } from '@/router/dynamicRoutes'
import { useMicrofrontendCommunication } from '@/composables/useMicrofrontendCommunication'

interface Props {
appName: string
moduleName: string
props?: Record<string, any>
autoRegisterRoutes?: boolean
}

const props = withDefaults(defineProps<Props>(), {
autoRegisterRoutes: true
})

const emit = defineEmits<{
loaded: [appName: string]
error: [error: Error]
}>()

const loading = ref(true)
const error = ref<Error | null>(null)
const microfrontendComponent = ref<any>(null)

const { globalState, eventBus } = useMicrofrontendCommunication()

// 组件属性
const componentProps = computed(() => ({
...props.props,
globalState,
eventBus
}))

/**
* 加载微应用
*/
const loadMicrofrontend = async () => {
loading.value = true
error.value = null

try {
// 加载微应用组件
const component = await moduleLoader.loadModule(
props.appName,
props.moduleName
)

microfrontendComponent.value = component

// 自动注册路由
if (props.autoRegisterRoutes) {
await dynamicRouteManager.registerMicrofrontendRoutes(props.appName)
}

emit('loaded', props.appName)

} catch (err) {
error.value = err as Error
emit('error', err as Error)
} finally {
loading.value = false
}
}

/**
* 重试加载
*/
const retry = () => {
loadMicrofrontend()
}

/**
* 处理组件错误
*/
const handleComponentError = (err: Error) => {
console.error(`Component error in ${props.appName}:`, err)
error.value = err
}

// 监听应用名称变化
watch(
() => props.appName,
() => {
loadMicrofrontend()
},
{ immediate: true }
)

// 组件卸载时清理
onUnmounted(() => {
if (props.autoRegisterRoutes) {
dynamicRouteManager.unregisterMicrofrontendRoutes(props.appName)
}
})
</script>

<style scoped>
.microfrontend-container {
width: 100%;
height: 100%;
min-height: 400px;
}

.loading-container {
padding: 20px;
}

.loading-text {
text-align: center;
margin-top: 16px;
color: #666;
}

.error-container {
padding: 40px 20px;
}

.suspense-loading {
padding: 20px;
}
</style>

qiankun 微前端方案

1. 主应用配置

qiankun 主应用设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// apps/main/src/microfrontend/qiankun.ts
import {
registerMicroApps,
start,
setDefaultMountApp,
runAfterFirstMounted,
addGlobalUncaughtErrorHandler
} from 'qiankun'
import type { RegistrableApp } from 'qiankun'
import { createGlobalState } from '@/utils/globalState'
import { createEventBus } from '@/utils/eventBus'

/**
* 微应用配置
*/
const microApps: RegistrableApp[] = [
{
name: 'user-management',
entry: '//localhost:3001',
container: '#user-container',
activeRule: '/user',
props: {
routerBase: '/user',
globalState: createGlobalState(),
eventBus: createEventBus()
}
},
{
name: 'product-management',
entry: '//localhost:3002',
container: '#product-container',
activeRule: '/product',
props: {
routerBase: '/product',
globalState: createGlobalState(),
eventBus: createEventBus()
}
},
{
name: 'order-management',
entry: '//localhost:3003',
container: '#order-container',
activeRule: '/order',
props: {
routerBase: '/order',
globalState: createGlobalState(),
eventBus: createEventBus()
}
}
]

/**
* 初始化 qiankun
*/
export function initQiankun() {
// 注册微应用
registerMicroApps(
microApps,
{
// 加载前
beforeLoad: (app) => {
console.log(`🚀 Loading micro app: ${app.name}`)
showLoading(app.name)
return Promise.resolve()
},

// 挂载前
beforeMount: (app) => {
console.log(`📦 Mounting micro app: ${app.name}`)
return Promise.resolve()
},

// 挂载后
afterMount: (app) => {
console.log(`✅ Mounted micro app: ${app.name}`)
hideLoading()
return Promise.resolve()
},

// 卸载前
beforeUnmount: (app) => {
console.log(`📤 Unmounting micro app: ${app.name}`)
return Promise.resolve()
},

// 卸载后
afterUnmount: (app) => {
console.log(`🗑️ Unmounted micro app: ${app.name}`)
return Promise.resolve()
}
}
)

// 设置默认应用
setDefaultMountApp('/user')

// 第一个微应用挂载后回调
runAfterFirstMounted(() => {
console.log('🎉 First micro app mounted')
})

// 全局错误处理
addGlobalUncaughtErrorHandler((event) => {
console.error('🚨 Global error:', event)

// 错误上报
reportError({
type: 'microfrontend-error',
error: event.error || event.reason,
stack: event.error?.stack,
timestamp: Date.now()
})
})

// 启动 qiankun
start({
sandbox: {
strictStyleIsolation: true,
experimentalStyleIsolation: true
},
prefetch: 'all',
singular: false,
fetch: customFetch
})
}

/**
* 自定义 fetch 函数
*/
function customFetch(url: string, options?: RequestInit): Promise<Response> {
// 添加认证头
const token = localStorage.getItem('auth-token')
if (token) {
options = {
...options,
headers: {
...options?.headers,
'Authorization': `Bearer ${token}`
}
}
}

// 添加超时处理
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000)

return fetch(url, {
...options,
signal: controller.signal
}).finally(() => {
clearTimeout(timeoutId)
})
}

/**
* 显示加载状态
*/
function showLoading(appName: string) {
const loadingEl = document.createElement('div')
loadingEl.id = `loading-${appName}`
loadingEl.innerHTML = `
<div style="
display: flex;
justify-content: center;
align-items: center;
height: 200px;
font-size: 16px;
color: #666;
">
正在加载 ${appName} 应用...
</div>
`

const container = document.querySelector(`#${appName.replace('-', '')}-container`)
if (container) {
container.appendChild(loadingEl)
}
}

/**
* 隐藏加载状态
*/
function hideLoading() {
const loadingEls = document.querySelectorAll('[id^="loading-"]')
loadingEls.forEach(el => el.remove())
}

/**
* 错误上报
*/
function reportError(errorInfo: any) {
// 这里可以集成错误监控服务
console.error('Error reported:', errorInfo)
}

2. 微应用配置

Vue 3 微应用入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// apps/user-management/src/main.ts
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import App from './App.vue'
import routes from './router/routes'
import type { App as VueApp } from 'vue'

let app: VueApp<Element> | null = null
let router: any = null
let pinia: any = null

/**
* 渲染函数
*/
function render(props: any = {}) {
const { container, routerBase = '/user' } = props

// 创建路由
router = createRouter({
history: createWebHistory(routerBase),
routes
})

// 创建状态管理
pinia = createPinia()

// 创建应用实例
app = createApp(App)
app.use(router)
app.use(pinia)

// 注入全局属性
if (props.globalState) {
app.provide('globalState', props.globalState)
}

if (props.eventBus) {
app.provide('eventBus', props.eventBus)
}

// 挂载应用
const containerEl = container
? container.querySelector('#user-app')
: document.querySelector('#user-app')

if (containerEl) {
app.mount(containerEl)
}
}

/**
* 独立运行时的挂载
*/
if (!(window as any).__POWERED_BY_QIANKUN__) {
render()
}

/**
* qiankun 生命周期 - bootstrap
*/
export async function bootstrap() {
console.log('🚀 User app bootstrapped')
}

/**
* qiankun 生命周期 - mount
*/
export async function mount(props: any) {
console.log('📦 User app mounted with props:', props)
render(props)
}

/**
* qiankun 生命周期 - unmount
*/
export async function unmount() {
console.log('🗑️ User app unmounted')

if (app) {
app.unmount()
app = null
}

if (router) {
router = null
}

if (pinia) {
pinia = null
}
}

/**
* qiankun 生命周期 - update
*/
export async function update(props: any) {
console.log('🔄 User app updated with props:', props)
}

微应用 Vite 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// apps/user-management/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

export default defineConfig({
plugins: [
vue(),
qiankun('user-management', {
useDevMode: true
})
],

server: {
port: 3001,
cors: true,
origin: 'http://localhost:3001'
},

build: {
target: 'es2015',
lib: {
name: 'user-management',
entry: 'src/main.ts',
formats: ['umd']
},
rollupOptions: {
external: ['vue', 'vue-router', 'pinia'],
output: {
globals: {
vue: 'Vue',
'vue-router': 'VueRouter',
pinia: 'Pinia'
}
}
}
}
})

3. 应用间通信

全局状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// src/utils/globalState.ts
import { reactive, readonly } from 'vue'

/**
* 全局状态接口
*/
export interface GlobalState {
user: {
id?: number
name?: string
email?: string
role?: string
}
theme: 'light' | 'dark'
language: 'zh-CN' | 'en-US'
permissions: string[]
}

/**
* 全局状态管理器
*/
export class GlobalStateManager {
private state = reactive<GlobalState>({
user: {},
theme: 'light',
language: 'zh-CN',
permissions: []
})

private listeners = new Set<(state: GlobalState) => void>()

/**
* 获取状态
*/
getState(): Readonly<GlobalState> {
return readonly(this.state)
}

/**
* 设置状态
*/
setState(updates: Partial<GlobalState>): void {
Object.assign(this.state, updates)
this.notifyListeners()
}

/**
* 订阅状态变化
*/
subscribe(listener: (state: GlobalState) => void): () => void {
this.listeners.add(listener)

// 返回取消订阅函数
return () => {
this.listeners.delete(listener)
}
}

/**
* 通知监听器
*/
private notifyListeners(): void {
this.listeners.forEach(listener => {
try {
listener(this.getState())
} catch (error) {
console.error('Global state listener error:', error)
}
})
}

/**
* 重置状态
*/
reset(): void {
this.setState({
user: {},
theme: 'light',
language: 'zh-CN',
permissions: []
})
}
}

// 创建全局状态实例
let globalStateInstance: GlobalStateManager | null = null

export function createGlobalState(): GlobalStateManager {
if (!globalStateInstance) {
globalStateInstance = new GlobalStateManager()
}
return globalStateInstance
}

export function getGlobalState(): GlobalStateManager | null {
return globalStateInstance
}

事件总线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// src/utils/eventBus.ts

/**
* 事件总线管理器
*/
export class EventBusManager {
private events = new Map<string, Set<Function>>()

/**
* 监听事件
*/
on(event: string, callback: Function): () => void {
if (!this.events.has(event)) {
this.events.set(event, new Set())
}

this.events.get(event)!.add(callback)

// 返回取消监听函数
return () => {
this.off(event, callback)
}
}

/**
* 监听一次事件
*/
once(event: string, callback: Function): () => void {
const onceCallback = (...args: any[]) => {
callback(...args)
this.off(event, onceCallback)
}

return this.on(event, onceCallback)
}

/**
* 取消监听
*/
off(event: string, callback?: Function): void {
if (!this.events.has(event)) {
return
}

const callbacks = this.events.get(event)!

if (callback) {
callbacks.delete(callback)
} else {
callbacks.clear()
}

if (callbacks.size === 0) {
this.events.delete(event)
}
}

/**
* 触发事件
*/
emit(event: string, ...args: any[]): void {
if (!this.events.has(event)) {
return
}

const callbacks = this.events.get(event)!

callbacks.forEach(callback => {
try {
callback(...args)
} catch (error) {
console.error(`Event callback error for ${event}:`, error)
}
})
}

/**
* 获取事件列表
*/
getEvents(): string[] {
return Array.from(this.events.keys())
}

/**
* 获取事件监听器数量
*/
getListenerCount(event: string): number {
return this.events.get(event)?.size || 0
}

/**
* 清除所有事件
*/
clear(): void {
this.events.clear()
}
}

// 创建事件总线实例
let eventBusInstance: EventBusManager | null = null

export function createEventBus(): EventBusManager {
if (!eventBusInstance) {
eventBusInstance = new EventBusManager()
}
return eventBusInstance
}

export function getEventBus(): EventBusManager | null {
return eventBusInstance
}

通信 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// src/composables/useMicrofrontendCommunication.ts
import { inject, onUnmounted } from 'vue'
import type { GlobalStateManager } from '@/utils/globalState'
import type { EventBusManager } from '@/utils/eventBus'

/**
* 微前端通信 Hook
*/
export function useMicrofrontendCommunication() {
const globalState = inject<GlobalStateManager>('globalState')
const eventBus = inject<EventBusManager>('eventBus')

const unsubscribers: (() => void)[] = []

/**
* 监听全局状态变化
*/
const watchGlobalState = (callback: (state: any) => void) => {
if (globalState) {
const unsubscribe = globalState.subscribe(callback)
unsubscribers.push(unsubscribe)
return unsubscribe
}
return () => {}
}

/**
* 更新全局状态
*/
const updateGlobalState = (updates: any) => {
if (globalState) {
globalState.setState(updates)
}
}

/**
* 监听事件
*/
const onEvent = (event: string, callback: Function) => {
if (eventBus) {
const unsubscribe = eventBus.on(event, callback)
unsubscribers.push(unsubscribe)
return unsubscribe
}
return () => {}
}

/**
* 触发事件
*/
const emitEvent = (event: string, ...args: any[]) => {
if (eventBus) {
eventBus.emit(event, ...args)
}
}

/**
* 应用间导航
*/
const navigateToApp = (appName: string, path: string) => {
emitEvent('navigate', { appName, path })
}

/**
* 显示全局消息
*/
const showGlobalMessage = (type: 'success' | 'error' | 'warning' | 'info', message: string) => {
emitEvent('global-message', { type, message })
}

// 组件卸载时清理订阅
onUnmounted(() => {
unsubscribers.forEach(unsubscribe => unsubscribe())
})

return {
globalState: globalState?.getState(),
eventBus,
watchGlobalState,
updateGlobalState,
onEvent,
emitEvent,
navigateToApp,
showGlobalMessage
}
}

性能优化与最佳实践

1. 依赖共享优化

共享依赖策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// shared-dependencies.config.ts

/**
* 共享依赖配置
*/
export const sharedDependencies = {
// 核心框架 - 必须共享
vue: {
singleton: true,
requiredVersion: '^3.3.0',
eager: true
},

'vue-router': {
singleton: true,
requiredVersion: '^4.2.0',
eager: true
},

pinia: {
singleton: true,
requiredVersion: '^2.1.0',
eager: false
},

// UI 库 - 建议共享
'element-plus': {
singleton: true,
requiredVersion: '^2.3.0',
eager: false
},

// 工具库 - 选择性共享
axios: {
singleton: false,
requiredVersion: '^1.4.0',
eager: false
},

'lodash-es': {
singleton: false,
requiredVersion: '^4.17.0',
eager: false
},

dayjs: {
singleton: false,
requiredVersion: '^1.11.0',
eager: false
}
}

/**
* 依赖分析工具
*/
export class DependencyAnalyzer {
/**
* 分析包大小
*/
static analyzeBundleSize(dependencies: Record<string, any>) {
const analysis = {
shared: 0,
individual: 0,
savings: 0
}

// 这里可以集成 webpack-bundle-analyzer 或类似工具
// 计算共享依赖带来的大小节省

return analysis
}

/**
* 检查版本兼容性
*/
static checkVersionCompatibility(dependencies: Record<string, any>) {
const conflicts: string[] = []

// 检查版本冲突
Object.entries(dependencies).forEach(([name, config]) => {
// 版本检查逻辑
})

return conflicts
}
}

2. 缓存策略

微应用缓存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// src/utils/microfrontendCache.ts

/**
* 微前端缓存管理器
*/
export class MicrofrontendCache {
private static instance: MicrofrontendCache
private cache = new Map<string, any>()
private cacheExpiry = new Map<string, number>()

static getInstance(): MicrofrontendCache {
if (!this.instance) {
this.instance = new MicrofrontendCache()
}
return this.instance
}

/**
* 设置缓存
*/
set(key: string, value: any, ttl: number = 5 * 60 * 1000): void {
this.cache.set(key, value)
this.cacheExpiry.set(key, Date.now() + ttl)
}

/**
* 获取缓存
*/
get<T = any>(key: string): T | null {
const expiry = this.cacheExpiry.get(key)

if (expiry && Date.now() > expiry) {
this.delete(key)
return null
}

return this.cache.get(key) || null
}

/**
* 删除缓存
*/
delete(key: string): void {
this.cache.delete(key)
this.cacheExpiry.delete(key)
}

/**
* 清空缓存
*/
clear(): void {
this.cache.clear()
this.cacheExpiry.clear()
}

/**
* 获取缓存统计
*/
getStats() {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys())
}
}
}

export const microfrontendCache = MicrofrontendCache.getInstance()

总结

微前端架构为大型前端应用提供了强大的解决方案:

  1. Module Federation:适合技术栈统一的团队,提供了更好的开发体验和性能
  2. qiankun:适合技术栈多样化的场景,提供了完善的沙箱隔离和生命周期管理
  3. 通信机制:通过全局状态和事件总线实现应用间的有效通信
  4. 性能优化:通过依赖共享、缓存策略等手段提升整体性能
  5. 最佳实践:建立规范的开发流程和部署策略,确保微前端架构的稳定运行

选择合适的微前端方案需要考虑团队规模、技术栈、业务复杂度等多个因素,并在实践中不断优化和完善。

本站由 提供部署服务