引言:微前端时代的到来
随着企业应用规模的不断扩大,传统的单体前端架构已经难以满足多团队协作、技术栈多样化和快速迭代的需求。微前端架构作为解决方案,让我们能够将大型应用拆分为多个独立的、可独立开发和部署的前端应用。
本文将以 Vue 3 为核心,全面介绍微前端架构的设计原理、技术实现和生产级部署实践。
1. 微前端架构设计原则
1.1 核心设计理念
typescript
// types/micro-frontend.ts
interface MicroFrontendConfig {
name: string
entry: string
container: string
activeRule: string | ((location: Location) => boolean)
props?: Record<string, any>
}
interface MicroFrontendApp {
// 应用元信息
metadata: {
name: string
version: string
description: string
maintainers: string[]
dependencies: Record<string, string>
}
// 生命周期钩子
lifecycle: {
bootstrap: () => Promise<void>
mount: (props: any) => Promise<void>
unmount: (props: any) => Promise<void>
update?: (props: any) => Promise<void>
}
// 路由配置
routes: {
base: string
routes: Array<{
path: string
component: any
meta?: Record<string, any>
}>
}
// 共享资源
shared: {
dependencies: string[]
components: Record<string, any>
utils: Record<string, any>
}
}
// 微前端架构原则
export const MICRO_FRONTEND_PRINCIPLES = {
技术栈无关: '每个子应用可以使用不同的技术栈',
独立开发: '团队可以独立开发、测试和部署',
增量升级: '可以逐步迁移和升级各个子应用',
运行时集成: '在浏览器运行时动态加载和集成',
样式隔离: '避免子应用间的样式冲突',
状态隔离: '子应用状态相互独立,通过明确接口通信'
} as const
1.2 架构模式选择
typescript
// config/architecture-patterns.ts
export const ARCHITECTURE_PATTERNS = {
// 1. 路由分发模式
routeDistribution: {
description: '基于路由规则分发到不同子应用',
适用场景: '功能边界清晰的大型应用',
优点: ['简单易理解', '性能好', 'SEO友好'],
缺点: ['页面级集成', '共享状态复杂']
},
// 2. 组件集成模式
componentIntegration: {
description: '将子应用作为组件集成到主应用',
适用场景: '需要细粒度集成的应用',
优点: ['灵活性高', '组件级复用', '集成度高'],
缺点: ['复杂度高', '性能开销', '版本管理难']
},
// 3. iframe 模式
iframeBased: {
description: '使用 iframe 隔离子应用',
适用场景: '强隔离需求、遗留系统集成',
优点: ['完全隔离', '简单实现', '安全性好'],
缺点: ['用户体验差', '通信复杂', 'SEO不友好']
}
}
2. Module Federation 实现方案
2.1 主应用配置
typescript
// main-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: 'main-app',
remotes: {
'user-app': 'http://localhost:3001/assets/remoteEntry.js',
'product-app': 'http://localhost:3002/assets/remoteEntry.js',
'order-app': 'http://localhost:3003/assets/remoteEntry.js'
},
shared: {
vue: {
singleton: true,
eager: true,
requiredVersion: '^3.3.0'
},
'vue-router': {
singleton: true,
eager: true
},
pinia: {
singleton: true,
eager: true
},
'element-plus': {
singleton: true
}
}
})
],
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false
}
})
vue
<!-- main-app/src/App.vue -->
<script setup lang="ts">
import { ref, onMounted, defineAsyncComponent } from 'vue'
import { useRouter } from 'vue-router'
import { useMicroFrontend } from '@/composables/useMicroFrontend'
// 动态加载微前端应用
const UserApp = defineAsyncComponent(() => import('user-app/App'))
const ProductApp = defineAsyncComponent(() => import('product-app/App'))
const OrderApp = defineAsyncComponent(() => import('order-app/App'))
const router = useRouter()
const { registerApp, getSharedStore, broadcastMessage } = useMicroFrontend()
// 应用注册配置
const microApps = [
{
name: 'user-app',
component: UserApp,
route: '/users',
props: { apiBase: '/api/users' }
},
{
name: 'product-app',
component: ProductApp,
route: '/products',
props: { apiBase: '/api/products' }
},
{
name: 'order-app',
component: OrderApp,
route: '/orders',
props: { apiBase: '/api/orders' }
}
]
onMounted(() => {
// 注册所有微前端应用
microApps.forEach(app => {
registerApp(app.name, {
component: app.component,
route: app.route,
props: app.props
})
})
})
// 全局事件处理
const handleGlobalEvent = (event: CustomEvent) => {
const { type, data, source } = event.detail
switch (type) {
case 'NAVIGATE':
router.push(data.path)
break
case 'UPDATE_USER':
broadcastMessage('USER_UPDATED', data, source)
break
case 'LOGOUT':
// 通知所有子应用用户已登出
broadcastMessage('USER_LOGOUT', {}, source)
router.push('/login')
break
}
}
onMounted(() => {
window.addEventListener('micro-frontend-event', handleGlobalEvent)
})
onUnmounted(() => {
window.removeEventListener('micro-frontend-event', handleGlobalEvent)
})
</script>
<template>
<div id="main-app">
<header class="main-header">
<nav class="main-nav">
<router-link to="/dashboard">首页</router-link>
<router-link to="/users">用户管理</router-link>
<router-link to="/products">产品管理</router-link>
<router-link to="/orders">订单管理</router-link>
</nav>
</header>
<main class="main-content">
<router-view />
</main>
<footer class="main-footer">
<p>© 2024 企业管理系统</p>
</footer>
</div>
</template>
<style scoped>
.main-app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-header {
background: #2c3e50;
color: white;
padding: 1rem;
}
.main-nav {
display: flex;
gap: 1rem;
}
.main-nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.main-nav a:hover,
.main-nav a.router-link-active {
background: #34495e;
}
.main-content {
flex: 1;
padding: 2rem;
}
</style>
2.2 子应用配置
typescript
// 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: 'user-app',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.vue',
'./UserList': './src/components/UserList.vue',
'./UserDetail': './src/components/UserDetail.vue'
},
shared: {
vue: {
singleton: true,
eager: true,
requiredVersion: '^3.3.0'
},
'vue-router': {
singleton: true,
eager: true
},
pinia: {
singleton: true,
eager: true
}
}
})
],
server: {
port: 3001,
cors: true
},
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false
}
})
vue
<!-- user-app/src/App.vue -->
<script setup lang="ts">
import { provide, onMounted, onUnmounted } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import UserList from './components/UserList.vue'
import UserDetail from './components/UserDetail.vue'
import { useMicroFrontendBridge } from './composables/useMicroFrontendBridge'
// 接收主应用传递的 props
interface AppProps {
apiBase?: string
theme?: string
permissions?: string[]
}
const props = withDefaults(defineProps<AppProps>(), {
apiBase: '/api/users',
theme: 'default',
permissions: () => []
})
// 创建子应用路由
const router = createRouter({
history: createWebHistory('/users'),
routes: [
{
path: '/',
name: 'UserList',
component: UserList
},
{
path: '/:id',
name: 'UserDetail',
component: UserDetail,
props: true
}
]
})
// 创建状态管理
const pinia = createPinia()
// 微前端通信桥梁
const { emitToMain, subscribeFromMain, unsubscribe } = useMicroFrontendBridge()
// 提供全局配置
provide('appConfig', {
apiBase: props.apiBase,
theme: props.theme,
permissions: props.permissions
})
// 监听主应用消息
const handleMainMessage = (type: string, data: any) => {
switch (type) {
case 'USER_LOGOUT':
// 清理用户相关状态
router.push('/')
break
case 'THEME_CHANGE':
// 更新主题
document.documentElement.setAttribute('data-theme', data.theme)
break
}
}
onMounted(() => {
subscribeFromMain(handleMainMessage)
// 通知主应用子应用已加载
emitToMain('APP_LOADED', {
name: 'user-app',
version: '1.0.0',
routes: router.getRoutes().map(r => r.path)
})
})
onUnmounted(() => {
unsubscribe()
emitToMain('APP_UNLOADED', { name: 'user-app' })
})
</script>
<template>
<div class="user-app" :data-theme="theme">
<div class="user-app-header">
<h2>用户管理系统</h2>
<div class="user-app-actions">
<button @click="emitToMain('NAVIGATE', { path: '/dashboard' })">
返回首页
</button>
</div>
</div>
<div class="user-app-content">
<router-view />
</div>
</div>
</template>
<style scoped>
.user-app {
border: 2px solid #e1e8ed;
border-radius: 8px;
padding: 1rem;
background: white;
}
.user-app-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e1e8ed;
}
.user-app-content {
min-height: 400px;
}
/* 主题样式 */
.user-app[data-theme="dark"] {
background: #1a1a1a;
color: white;
border-color: #333;
}
</style>
3. Single-SPA 集成方案
3.1 主应用 Single-SPA 配置
typescript
// main-app/src/main.ts
import { createApp } from 'vue'
import { registerApplication, start } from 'single-spa'
import App from './App.vue'
import { createSingleSpaVue } from 'single-spa-vue'
// 主应用启动
const app = createApp(App)
app.mount('#main-app')
// 注册微前端应用
registerApplication({
name: 'user-management',
app: () => System.import('user-app'),
activeWhen: ['/users'],
customProps: {
apiBase: '/api/users',
mountParcel: true
}
})
registerApplication({
name: 'product-management',
app: () => System.import('product-app'),
activeWhen: ['/products'],
customProps: {
apiBase: '/api/products'
}
})
registerApplication({
name: 'order-management',
app: () => System.import('order-app'),
activeWhen: (location) => location.pathname.startsWith('/orders'),
customProps: {
apiBase: '/api/orders'
}
})
// 启动 single-spa
start({
urlRerouteOnly: true
})
3.2 子应用 Single-SPA 适配
typescript
// user-app/src/main.ts
import { createApp, type App as VueApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import singleSpaVue from 'single-spa-vue'
import App from './App.vue'
import { routes } from './router'
const vueLifecycles = singleSpaVue({
createApp,
appOptions: {
render() {
return h(App, {
// 接收 single-spa 传递的 props
...this.$props
})
}
},
handleInstance(app: VueApp, props: any) {
// 创建路由实例
const router = createRouter({
history: createWebHistory(props.base || '/users'),
routes
})
// 创建状态管理
const pinia = createPinia()
app.use(router)
app.use(pinia)
// 设置全局属性
app.config.globalProperties.$props = props
}
})
export const { bootstrap, mount, unmount } = vueLifecycles
// 独立运行模式(开发环境)
if (!window.singleSpaNavigate) {
delete window.singleSpaNavigate
const app = createApp(App)
const router = createRouter({
history: createWebHistory('/users'),
routes
})
const pinia = createPinia()
app.use(router)
app.use(pinia)
app.mount('#app')
}
4. 跨应用通信机制
4.1 事件总线模式
typescript
// shared/event-bus.ts
interface EventPayload {
type: string
data: any
source: string
timestamp: number
id: string
}
class MicroFrontendEventBus {
private listeners = new Map<string, Set<Function>>()
private history: EventPayload[] = []
private maxHistorySize = 100
// 发布事件
emit(type: string, data: any, source: string = 'unknown') {
const payload: EventPayload = {
type,
data,
source,
timestamp: Date.now(),
id: this.generateId()
}
// 存储到历史记录
this.addToHistory(payload)
// 通知监听器
const typeListeners = this.listeners.get(type)
if (typeListeners) {
typeListeners.forEach(listener => {
try {
listener(payload)
} catch (error) {
console.error(`Error in event listener for ${type}:`, error)
}
})
}
// 通知通用监听器
const allListeners = this.listeners.get('*')
if (allListeners) {
allListeners.forEach(listener => {
try {
listener(payload)
} catch (error) {
console.error('Error in universal event listener:', error)
}
})
}
// 发送自定义事件到 window
window.dispatchEvent(new CustomEvent('micro-frontend-event', {
detail: payload
}))
}
// 订阅事件
on(type: string, listener: (payload: EventPayload) => void) {
if (!this.listeners.has(type)) {
this.listeners.set(type, new Set())
}
this.listeners.get(type)!.add(listener)
// 返回取消订阅函数
return () => {
const typeListeners = this.listeners.get(type)
if (typeListeners) {
typeListeners.delete(listener)
if (typeListeners.size === 0) {
this.listeners.delete(type)
}
}
}
}
// 一次性监听
once(type: string, listener: (payload: EventPayload) => void) {
const unsubscribe = this.on(type, (payload) => {
listener(payload)
unsubscribe()
})
return unsubscribe
}
// 移除监听器
off(type: string, listener?: Function) {
if (!listener) {
this.listeners.delete(type)
} else {
const typeListeners = this.listeners.get(type)
if (typeListeners) {
typeListeners.delete(listener)
}
}
}
// 获取事件历史
getHistory(type?: string, limit: number = 10): EventPayload[] {
let filtered = this.history
if (type) {
filtered = this.history.filter(event => event.type === type)
}
return filtered.slice(-limit)
}
// 清空历史
clearHistory() {
this.history = []
}
private addToHistory(payload: EventPayload) {
this.history.push(payload)
if (this.history.length > this.maxHistorySize) {
this.history = this.history.slice(-this.maxHistorySize)
}
}
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}
}
export const eventBus = new MicroFrontendEventBus()
// Vue 组合式函数
export function useEventBus() {
const emit = (type: string, data: any) => {
eventBus.emit(type, data, getCurrentInstance()?.appContext.app.config.globalProperties.$appName || 'unknown')
}
const on = (type: string, listener: (payload: EventPayload) => void) => {
return eventBus.on(type, listener)
}
const once = (type: string, listener: (payload: EventPayload) => void) => {
return eventBus.once(type, listener)
}
return {
emit,
on,
once,
off: eventBus.off.bind(eventBus),
getHistory: eventBus.getHistory.bind(eventBus)
}
}
4.2 共享状态管理
typescript
// shared/global-store.ts
import { reactive, readonly } from 'vue'
import { defineStore } from 'pinia'
// 全局共享状态接口
interface GlobalState {
user: {
id: string | null
name: string
email: string
permissions: string[]
isAuthenticated: boolean
}
theme: {
mode: 'light' | 'dark'
primaryColor: string
language: string
}
navigation: {
currentApp: string
breadcrumb: Array<{ label: string; path: string }>
activeMenus: string[]
}
notifications: Array<{
id: string
type: 'info' | 'success' | 'warning' | 'error'
title: string
message: string
timestamp: number
read: boolean
}>
}
// 创建全局状态
const globalState = reactive<GlobalState>({
user: {
id: null,
name: '',
email: '',
permissions: [],
isAuthenticated: false
},
theme: {
mode: 'light',
primaryColor: '#409eff',
language: 'zh-CN'
},
navigation: {
currentApp: '',
breadcrumb: [],
activeMenus: []
},
notifications: []
})
// 全局状态管理器
class GlobalStateManager {
private state = globalState
private subscribers = new Set<(state: GlobalState) => void>()
// 获取只读状态
getState() {
return readonly(this.state)
}
// 更新用户信息
updateUser(userData: Partial<GlobalState['user']>) {
Object.assign(this.state.user, userData)
this.notify()
}
// 更新主题
updateTheme(themeData: Partial<GlobalState['theme']>) {
Object.assign(this.state.theme, themeData)
this.notify()
// 通知所有子应用主题变更
eventBus.emit('THEME_CHANGE', themeData)
}
// 更新导航状态
updateNavigation(navData: Partial<GlobalState['navigation']>) {
Object.assign(this.state.navigation, navData)
this.notify()
}
// 添加通知
addNotification(notification: Omit<GlobalState['notifications'][0], 'id' | 'timestamp' | 'read'>) {
const newNotification = {
...notification,
id: `notification-${Date.now()}`,
timestamp: Date.now(),
read: false
}
this.state.notifications.unshift(newNotification)
this.notify()
return newNotification.id
}
// 标记通知为已读
markNotificationRead(id: string) {
const notification = this.state.notifications.find(n => n.id === id)
if (notification) {
notification.read = true
this.notify()
}
}
// 清空通知
clearNotifications() {
this.state.notifications = []
this.notify()
}
// 订阅状态变化
subscribe(callback: (state: GlobalState) => void) {
this.subscribers.add(callback)
return () => {
this.subscribers.delete(callback)
}
}
// 通知订阅者
private notify() {
this.subscribers.forEach(callback => {
try {
callback(this.state)
} catch (error) {
console.error('Error in global state subscriber:', error)
}
})
}
}
export const globalStateManager = new GlobalStateManager()
// Pinia Store
export const useGlobalStore = defineStore('global', () => {
const state = globalStateManager.getState()
const updateUser = (userData: Partial<GlobalState['user']>) => {
globalStateManager.updateUser(userData)
}
const updateTheme = (themeData: Partial<GlobalState['theme']>) => {
globalStateManager.updateTheme(themeData)
}
const updateNavigation = (navData: Partial<GlobalState['navigation']>) => {
globalStateManager.updateNavigation(navData)
}
const addNotification = (notification: Omit<GlobalState['notifications'][0], 'id' | 'timestamp' | 'read'>) => {
return globalStateManager.addNotification(notification)
}
return {
state,
updateUser,
updateTheme,
updateNavigation,
addNotification,
markNotificationRead: globalStateManager.markNotificationRead.bind(globalStateManager),
clearNotifications: globalStateManager.clearNotifications.bind(globalStateManager)
}
})
5. 样式隔离方案
5.1 CSS-in-JS 方案
typescript
// utils/styled-components.ts
import { computed, type Ref } from 'vue'
interface StyleConfig {
prefix: string
theme: Ref<any>
scoped: boolean
}
class StyledComponentsManager {
private prefix: string
private theme: Ref<any>
private scoped: boolean
private styleCache = new Map<string, string>()
constructor(config: StyleConfig) {
this.prefix = config.prefix
this.theme = config.theme
this.scoped = config.scoped
}
// 创建样式类
createStyles(styles: Record<string, any>) {
const classNames: Record<string, string> = {}
Object.entries(styles).forEach(([key, value]) => {
const className = this.generateClassName(key)
const cssText = this.processCSSText(value, className)
this.injectStyles(cssText)
classNames[key] = className
})
return classNames
}
// 主题感知样式
createThemedStyles(styleFactory: (theme: any) => Record<string, any>) {
return computed(() => {
const styles = styleFactory(this.theme.value)
return this.createStyles(styles)
})
}
// 响应式样式
createResponsiveStyles(breakpoints: Record<string, string>, styles: Record<string, Record<string, any>>) {
const mediaQueries = Object.entries(breakpoints).map(([name, query]) => {
const mediaStyles = styles[name]
if (!mediaStyles) return ''
const cssRules = Object.entries(mediaStyles).map(([selector, rules]) => {
const className = this.generateClassName(selector)
const cssText = this.processCSSText(rules, className)
return cssText
}).join('\n')
return `@media ${query} {\n${cssRules}\n}`
}).join('\n')
this.injectStyles(mediaQueries)
}
private generateClassName(key: string): string {
const hash = this.hashCode(key + this.prefix)
return `${this.prefix}-${key}-${Math.abs(hash).toString(36)}`
}
private processCSSText(styles: any, className: string): string {
const cssProperties = Object.entries(styles).map(([prop, value]) => {
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase()
return ` ${kebabProp}: ${value};`
}).join('\n')
return `.${className} {\n${cssProperties}\n}`
}
private injectStyles(cssText: string) {
if (this.styleCache.has(cssText)) return
const styleElement = document.createElement('style')
styleElement.textContent = cssText
if (this.scoped) {
styleElement.setAttribute('data-app', this.prefix)
}
document.head.appendChild(styleElement)
this.styleCache.set(cssText, 'injected')
}
private hashCode(str: string): number {
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // Convert to 32bit integer
}
return hash
}
}
// Vue 组合式函数
export function useStyledComponents(appName: string) {
const theme = inject('theme', ref({}))
const manager = new StyledComponentsManager({
prefix: `mf-${appName}`,
theme,
scoped: true
})
const createStyles = (styles: Record<string, any>) => {
return manager.createStyles(styles)
}
const createThemedStyles = (styleFactory: (theme: any) => Record<string, any>) => {
return manager.createThemedStyles(styleFactory)
}
return {
createStyles,
createThemedStyles,
createResponsiveStyles: manager.createResponsiveStyles.bind(manager)
}
}
5.2 CSS Module 隔离
vue
<!-- components/IsolatedComponent.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import styles from './IsolatedComponent.module.css'
interface ComponentProps {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
}
const props = withDefaults(defineProps<ComponentProps>(), {
variant: 'primary',
size: 'medium',
disabled: false
})
// 动态类名组合
const componentClasses = computed(() => [
styles.component,
styles[`variant-${props.variant}`],
styles[`size-${props.size}`],
{
[styles.disabled]: props.disabled
}
])
</script>
<template>
<div :class="componentClasses">
<slot />
</div>
</template>
<style module>
.component {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.variant-primary {
background-color: #409eff;
color: white;
}
.variant-primary:hover {
background-color: #66b1ff;
}
.variant-secondary {
background-color: #909399;
color: white;
}
.variant-danger {
background-color: #f56c6c;
color: white;
}
.size-small {
padding: 4px 8px;
font-size: 12px;
}
.size-medium {
padding: 8px 16px;
font-size: 14px;
}
.size-large {
padding: 12px 24px;
font-size: 16px;
}
.disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
6. 部署与运维
6.1 Docker 容器化部署
dockerfile
# Dockerfile.main-app
FROM node:18-alpine as builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
COPY pnpm-lock.yaml ./
# 安装依赖
RUN npm install -g pnpm
RUN pnpm install
# 复制源代码
COPY . .
# 构建应用
RUN pnpm build
# 生产镜像
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
yaml
# docker-compose.yml
version: '3.8'
services:
# 主应用
main-app:
build:
context: ./main-app
dockerfile: Dockerfile
ports:
- "3000:80"
environment:
- NODE_ENV=production
networks:
- micro-frontend-network
# 用户管理微应用
user-app:
build:
context: ./user-app
dockerfile: Dockerfile
ports:
- "3001:80"
environment:
- NODE_ENV=production
- API_BASE_URL=http://api-gateway:8080/api/users
networks:
- micro-frontend-network
# 产品管理微应用
product-app:
build:
context: ./product-app
dockerfile: Dockerfile
ports:
- "3002:80"
environment:
- NODE_ENV=production
- API_BASE_URL=http://api-gateway:8080/api/products
networks:
- micro-frontend-network
# API 网关
api-gateway:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./gateway/nginx.conf:/etc/nginx/nginx.conf
networks:
- micro-frontend-network
# Redis (用于共享会话)
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- micro-frontend-network
networks:
micro-frontend-network:
driver: bridge
6.2 Kubernetes 部署配置
yaml
# k8s/main-app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-app
namespace: micro-frontend
spec:
replicas: 3
selector:
matchLabels:
app: main-app
template:
metadata:
labels:
app: main-app
spec:
containers:
- name: main-app
image: micro-frontend/main-app:latest
ports:
- containerPort: 80
env:
- name: NODE_ENV
value: "production"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: main-app-service
namespace: micro-frontend
spec:
selector:
app: main-app
ports:
- port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-app-ingress
namespace: micro-frontend
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: main-app-service
port:
number: 80
6.3 CI/CD 流水线
yaml
# .github/workflows/deploy.yml
name: Micro Frontend Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
app: [main-app, user-app, product-app, order-app]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'pnpm'
- name: Install dependencies
run: |
cd ${{ matrix.app }}
pnpm install
- name: Run tests
run: |
cd ${{ matrix.app }}
pnpm test
- name: Build application
run: |
cd ${{ matrix.app }}
pnpm build
- name: Build Docker image
run: |
cd ${{ matrix.app }}
docker build -t micro-frontend/${{ matrix.app }}:${{ github.sha }} .
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push micro-frontend/${{ matrix.app }}:${{ github.sha }}
deploy:
needs: build-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to Kubernetes
run: |
# 更新 K8s 部署配置中的镜像标签
sed -i 's/latest/${{ github.sha }}/g' k8s/*.yaml
# 应用配置
kubectl apply -f k8s/
# 等待部署完成
kubectl rollout status deployment/main-app -n micro-frontend
kubectl rollout status deployment/user-app -n micro-frontend
kubectl rollout status deployment/product-app -n micro-frontend
7. 监控与日志
7.1 应用性能监控
typescript
// utils/monitoring.ts
interface PerformanceMetric {
name: string
value: number
unit: string
timestamp: number
tags: Record<string, string>
}
class MicroFrontendMonitor {
private metrics: PerformanceMetric[] = []
private endpoint: string
constructor(endpoint: string) {
this.endpoint = endpoint
this.setupPerformanceObserver()
this.setupErrorTracking()
}
// 性能指标收集
private setupPerformanceObserver() {
// 监控页面加载性能
new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
this.recordMetric({
name: 'page_load_time',
value: entry.duration,
unit: 'ms',
timestamp: Date.now(),
tags: {
entryType: entry.entryType,
name: entry.name
}
})
})
}).observe({ entryTypes: ['navigation', 'resource'] })
// 监控用户交互
new PerformanceObserver((list) => {
list.getEntries().forEach((entry: any) => {
this.recordMetric({
name: 'user_interaction',
value: entry.processingStart - entry.startTime,
unit: 'ms',
timestamp: Date.now(),
tags: {
interactionType: entry.name
}
})
})
}).observe({ entryTypes: ['first-input'] })
}
// 错误追踪
private setupErrorTracking() {
window.addEventListener('error', (event) => {
this.recordError({
type: 'javascript_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
})
})
window.addEventListener('unhandledrejection', (event) => {
this.recordError({
type: 'unhandled_promise_rejection',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack
})
})
}
// 记录自定义指标
recordMetric(metric: PerformanceMetric) {
this.metrics.push(metric)
// 批量发送(避免频繁请求)
if (this.metrics.length >= 10) {
this.flushMetrics()
}
}
// 记录错误
recordError(error: any) {
this.recordMetric({
name: 'error_count',
value: 1,
unit: 'count',
timestamp: Date.now(),
tags: {
type: error.type,
message: error.message
}
})
}
// 发送指标数据
private async flushMetrics() {
if (this.metrics.length === 0) return
try {
await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
metrics: this.metrics,
app: window.__MICRO_FRONTEND_APP_NAME__ || 'unknown',
version: window.__MICRO_FRONTEND_APP_VERSION__ || 'unknown',
timestamp: Date.now()
})
})
this.metrics = []
} catch (error) {
console.error('Failed to send metrics:', error)
}
}
}
export const monitor = new MicroFrontendMonitor('/api/metrics')
// Vue 插件
export function createMonitoringPlugin(endpoint: string) {
return {
install(app: any) {
const monitor = new MicroFrontendMonitor(endpoint)
app.config.globalProperties.$monitor = monitor
app.provide('monitor', monitor)
// 组件渲染性能监控
app.mixin({
beforeCreate() {
this._renderStart = performance.now()
},
mounted() {
const renderTime = performance.now() - this._renderStart
monitor.recordMetric({
name: 'component_render_time',
value: renderTime,
unit: 'ms',
timestamp: Date.now(),
tags: {
component: this.$options.name || 'anonymous'
}
})
}
})
}
}
}
总结
微前端架构为大型企业应用提供了强大的解决方案,通过本文的实践指南,我们掌握了:
🏗️ 核心技术栈
- Module Federation - 运行时模块共享
- Single-SPA - 应用注册与生命周期管理
- 事件总线 - 跨应用通信机制
- 样式隔离 - CSS Module 与 CSS-in-JS
🔧 工程化实践
- Docker 容器化 - 标准化部署环境
- Kubernetes 编排 - 高可用集群部署
- CI/CD 流水线 - 自动化构建与部署
- 监控与日志 - 完整的可观测性体系
💡 最佳实践
- 渐进式迁移 - 平滑过渡到微前端架构
- 团队自治 - 独立开发、测试、部署
- 性能优化 - 懒加载、缓存策略、资源共享
- 错误隔离 - 单个应用故障不影响整体系统
微前端架构不仅解决了技术债务和团队协作问题,更为企业数字化转型提供了可扩展的技术底座。通过合理的架构设计和工程化实践,能够构建出高性能、高可用、易维护的现代化企业应用。