Skip to content

第72天:Element Plus 微前端架构实践

学习目标

  • 理解微前端架构的核心概念和优势
  • 掌握在微前端环境中使用 Element Plus 的最佳实践
  • 学习组件库和主题的跨应用共享策略
  • 了解微前端应用间的通信和状态管理

知识点概览

1. 微前端架构基础

1.1 微前端概念与架构

typescript
// 微前端架构定义
interface MicrofrontendArchitecture {
  // 主应用(容器应用)
  shell: {
    framework: 'Vue 3' | 'React' | 'Angular'
    responsibilities: string[]
    routing: 'client-side' | 'server-side'
    communication: 'event-bus' | 'shared-state' | 'props'
  }
  
  // 微应用
  microfrontends: {
    id: string
    name: string
    framework: string
    entry: string
    routes: string[]
    dependencies: string[]
    shared: string[]
  }[]
  
  // 共享资源
  shared: {
    libraries: string[]
    components: string[]
    themes: string[]
    utilities: string[]
  }
  
  // 部署策略
  deployment: {
    strategy: 'independent' | 'coordinated'
    registry: string
    cdn: string
    versioning: 'semantic' | 'timestamp'
  }
}

// 微前端管理器
class MicrofrontendManager {
  private applications: Map<string, MicrofrontendApp> = new Map()
  private sharedResources: SharedResourceManager
  private communicationBus: EventBus
  
  constructor() {
    this.sharedResources = new SharedResourceManager()
    this.communicationBus = new EventBus()
    this.initializeSharedResources()
  }
  
  // 注册微应用
  registerApp(config: MicrofrontendConfig): void {
    const app = new MicrofrontendApp(config, this.sharedResources, this.communicationBus)
    this.applications.set(config.name, app)
    
    console.log(`Microfrontend '${config.name}' registered successfully`)
  }
  
  // 启动微应用
  async startApp(name: string, container: HTMLElement): Promise<void> {
    const app = this.applications.get(name)
    if (!app) {
      throw new Error(`Microfrontend '${name}' not found`)
    }
    
    try {
      await app.mount(container)
      console.log(`Microfrontend '${name}' started successfully`)
    } catch (error) {
      console.error(`Failed to start microfrontend '${name}':`, error)
      throw error
    }
  }
  
  // 停止微应用
  async stopApp(name: string): Promise<void> {
    const app = this.applications.get(name)
    if (app) {
      await app.unmount()
      console.log(`Microfrontend '${name}' stopped successfully`)
    }
  }
  
  // 初始化共享资源
  private initializeSharedResources(): void {
    // 共享 Element Plus
    this.sharedResources.register('element-plus', {
      type: 'library',
      version: '2.4.0',
      url: 'https://unpkg.com/element-plus@2.4.0/dist/index.full.min.js',
      css: 'https://unpkg.com/element-plus@2.4.0/dist/index.css'
    })
    
    // 共享 Vue
    this.sharedResources.register('vue', {
      type: 'library',
      version: '3.3.0',
      url: 'https://unpkg.com/vue@3.3.0/dist/vue.global.prod.js'
    })
    
    // 共享主题
    this.sharedResources.register('design-tokens', {
      type: 'theme',
      version: '1.0.0',
      url: '/shared/design-tokens.css'
    })
  }
  
  // 获取应用列表
  getApplications(): string[] {
    return Array.from(this.applications.keys())
  }
  
  // 获取共享资源
  getSharedResources(): SharedResource[] {
    return this.sharedResources.getAll()
  }
}

// 微前端应用类
class MicrofrontendApp {
  private config: MicrofrontendConfig
  private sharedResources: SharedResourceManager
  private communicationBus: EventBus
  private instance: any = null
  private container: HTMLElement | null = null
  
  constructor(
    config: MicrofrontendConfig,
    sharedResources: SharedResourceManager,
    communicationBus: EventBus
  ) {
    this.config = config
    this.sharedResources = sharedResources
    this.communicationBus = communicationBus
  }
  
  // 挂载应用
  async mount(container: HTMLElement): Promise<void> {
    this.container = container
    
    // 加载依赖
    await this.loadDependencies()
    
    // 加载应用代码
    const appModule = await this.loadAppModule()
    
    // 创建应用实例
    this.instance = await appModule.mount({
      container,
      props: this.config.props || {},
      shared: this.getSharedContext()
    })
  }
  
  // 卸载应用
  async unmount(): Promise<void> {
    if (this.instance && typeof this.instance.unmount === 'function') {
      await this.instance.unmount()
    }
    
    if (this.container) {
      this.container.innerHTML = ''
    }
    
    this.instance = null
    this.container = null
  }
  
  // 加载依赖
  private async loadDependencies(): Promise<void> {
    const dependencies = this.config.dependencies || []
    
    for (const dep of dependencies) {
      await this.sharedResources.load(dep)
    }
  }
  
  // 加载应用模块
  private async loadAppModule(): Promise<any> {
    const script = document.createElement('script')
    script.src = this.config.entry
    script.type = 'module'
    
    return new Promise((resolve, reject) => {
      script.onload = () => {
        const appModule = (window as any)[this.config.globalName]
        if (appModule) {
          resolve(appModule)
        } else {
          reject(new Error(`Global '${this.config.globalName}' not found`))
        }
      }
      
      script.onerror = () => {
        reject(new Error(`Failed to load script: ${this.config.entry}`))
      }
      
      document.head.appendChild(script)
    })
  }
  
  // 获取共享上下文
  private getSharedContext(): any {
    return {
      eventBus: this.communicationBus,
      sharedResources: this.sharedResources,
      theme: this.sharedResources.get('design-tokens')
    }
  }
}

// 微前端配置接口
interface MicrofrontendConfig {
  name: string
  entry: string
  globalName: string
  routes?: string[]
  dependencies?: string[]
  props?: Record<string, any>
}

// 共享资源接口
interface SharedResource {
  type: 'library' | 'component' | 'theme' | 'utility'
  version: string
  url: string
  css?: string
  dependencies?: string[]
}

1.2 共享资源管理

typescript
// 共享资源管理器
class SharedResourceManager {
  private resources: Map<string, SharedResource> = new Map()
  private loadedResources: Set<string> = new Set()
  private loadingPromises: Map<string, Promise<void>> = new Map()
  
  // 注册共享资源
  register(name: string, resource: SharedResource): void {
    this.resources.set(name, resource)
  }
  
  // 加载共享资源
  async load(name: string): Promise<void> {
    if (this.loadedResources.has(name)) {
      return
    }
    
    if (this.loadingPromises.has(name)) {
      return this.loadingPromises.get(name)
    }
    
    const resource = this.resources.get(name)
    if (!resource) {
      throw new Error(`Shared resource '${name}' not found`)
    }
    
    const loadingPromise = this.loadResource(name, resource)
    this.loadingPromises.set(name, loadingPromise)
    
    try {
      await loadingPromise
      this.loadedResources.add(name)
    } finally {
      this.loadingPromises.delete(name)
    }
  }
  
  // 加载资源实现
  private async loadResource(name: string, resource: SharedResource): Promise<void> {
    // 加载依赖
    if (resource.dependencies) {
      for (const dep of resource.dependencies) {
        await this.load(dep)
      }
    }
    
    // 加载 CSS
    if (resource.css) {
      await this.loadCSS(resource.css)
    }
    
    // 加载 JavaScript
    await this.loadScript(resource.url)
  }
  
  // 加载 CSS
  private loadCSS(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      
      link.onload = () => resolve()
      link.onerror = () => reject(new Error(`Failed to load CSS: ${url}`))
      
      document.head.appendChild(link)
    })
  }
  
  // 加载 JavaScript
  private loadScript(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      
      script.onload = () => resolve()
      script.onerror = () => reject(new Error(`Failed to load script: ${url}`))
      
      document.head.appendChild(script)
    })
  }
  
  // 获取资源
  get(name: string): SharedResource | undefined {
    return this.resources.get(name)
  }
  
  // 获取所有资源
  getAll(): SharedResource[] {
    return Array.from(this.resources.values())
  }
  
  // 检查资源是否已加载
  isLoaded(name: string): boolean {
    return this.loadedResources.has(name)
  }
}

// 事件总线
class EventBus {
  private events: Map<string, Set<Function>> = new Map()
  
  // 订阅事件
  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.events.get(event)?.delete(callback)
    }
  }
  
  // 发布事件
  emit(event: string, ...args: any[]): void {
    const callbacks = this.events.get(event)
    if (callbacks) {
      callbacks.forEach(callback => {
        try {
          callback(...args)
        } catch (error) {
          console.error(`Error in event callback for '${event}':`, error)
        }
      })
    }
  }
  
  // 一次性订阅
  once(event: string, callback: Function): void {
    const unsubscribe = this.on(event, (...args: any[]) => {
      callback(...args)
      unsubscribe()
    })
  }
  
  // 移除所有监听器
  off(event?: string): void {
    if (event) {
      this.events.delete(event)
    } else {
      this.events.clear()
    }
  }
}

2. Element Plus 在微前端中的应用

2.1 主应用配置

vue
<!-- Shell Application (主应用) -->
<template>
  <div id="app" class="shell-app">
    <!-- 全局导航 -->
    <el-container>
      <el-header class="shell-header">
        <div class="header-content">
          <div class="logo">
            <img src="/logo.png" alt="Logo" />
            <span>微前端平台</span>
          </div>
          
          <el-menu
            :default-active="activeMenu"
            mode="horizontal"
            @select="handleMenuSelect"
            class="main-menu"
          >
            <el-menu-item
              v-for="app in applications"
              :key="app.name"
              :index="app.name"
            >
              {{ app.title }}
            </el-menu-item>
          </el-menu>
          
          <div class="header-actions">
            <!-- 主题切换 -->
            <ThemeSelector />
            
            <!-- 用户菜单 -->
            <el-dropdown @command="handleUserCommand">
              <span class="user-info">
                <el-avatar :src="userInfo.avatar" />
                <span>{{ userInfo.name }}</span>
                <el-icon><ArrowDown /></el-icon>
              </span>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="profile">个人资料</el-dropdown-item>
                  <el-dropdown-item command="settings">设置</el-dropdown-item>
                  <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </div>
        </div>
      </el-header>
      
      <el-main class="shell-main">
        <!-- 微应用容器 -->
        <div
          v-for="app in applications"
          :key="app.name"
          :ref="el => setAppContainer(app.name, el)"
          v-show="activeApp === app.name"
          class="microfrontend-container"
        >
          <!-- 加载状态 -->
          <div v-if="loadingApps.has(app.name)" class="loading-container">
            <el-loading-service />
            <p>正在加载 {{ app.title }}...</p>
          </div>
          
          <!-- 错误状态 -->
          <div v-else-if="errorApps.has(app.name)" class="error-container">
            <el-result
              icon="error"
              title="加载失败"
              :sub-title="errorApps.get(app.name)"
            >
              <template #extra>
                <el-button type="primary" @click="retryLoadApp(app.name)">
                  重试
                </el-button>
              </template>
            </el-result>
          </div>
        </div>
      </el-main>
    </el-container>
    
    <!-- 全局通知 -->
    <GlobalNotifications />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { MicrofrontendManager } from '@/utils/microfrontend'
import ThemeSelector from '@/components/ThemeSelector.vue'
import GlobalNotifications from '@/components/GlobalNotifications.vue'

// 微前端管理器
const microfrontendManager = new MicrofrontendManager()

// 应用配置
const applications = ref([
  {
    name: 'user-management',
    title: '用户管理',
    entry: '/microfrontends/user-management/index.js',
    globalName: 'UserManagementApp',
    dependencies: ['vue', 'element-plus', 'design-tokens']
  },
  {
    name: 'product-catalog',
    title: '商品目录',
    entry: '/microfrontends/product-catalog/index.js',
    globalName: 'ProductCatalogApp',
    dependencies: ['vue', 'element-plus', 'design-tokens']
  },
  {
    name: 'analytics-dashboard',
    title: '数据分析',
    entry: '/microfrontends/analytics-dashboard/index.js',
    globalName: 'AnalyticsDashboardApp',
    dependencies: ['vue', 'element-plus', 'design-tokens', 'echarts']
  }
])

// 状态管理
const activeApp = ref('user-management')
const activeMenu = ref('user-management')
const loadingApps = reactive(new Set<string>())
const errorApps = reactive(new Map<string, string>())
const appContainers = reactive(new Map<string, HTMLElement>())

// 用户信息
const userInfo = reactive({
  name: '张三',
  avatar: '/avatars/default.png'
})

// 设置应用容器引用
function setAppContainer(appName: string, el: HTMLElement | null): void {
  if (el) {
    appContainers.set(appName, el)
  }
}

// 处理菜单选择
async function handleMenuSelect(appName: string): Promise<void> {
  if (activeApp.value === appName) {
    return
  }
  
  // 停止当前应用
  if (activeApp.value) {
    await microfrontendManager.stopApp(activeApp.value)
  }
  
  // 启动新应用
  activeApp.value = appName
  activeMenu.value = appName
  
  await nextTick()
  await loadMicrofrontend(appName)
}

// 加载微前端应用
async function loadMicrofrontend(appName: string): Promise<void> {
  const container = appContainers.get(appName)
  if (!container) {
    console.error(`Container for app '${appName}' not found`)
    return
  }
  
  loadingApps.add(appName)
  errorApps.delete(appName)
  
  try {
    await microfrontendManager.startApp(appName, container)
  } catch (error) {
    console.error(`Failed to load microfrontend '${appName}':`, error)
    errorApps.set(appName, error instanceof Error ? error.message : '未知错误')
  } finally {
    loadingApps.delete(appName)
  }
}

// 重试加载应用
async function retryLoadApp(appName: string): Promise<void> {
  await loadMicrofrontend(appName)
}

// 处理用户命令
function handleUserCommand(command: string): void {
  switch (command) {
    case 'profile':
      // 打开个人资料页面
      break
    case 'settings':
      // 打开设置页面
      break
    case 'logout':
      // 执行登出操作
      break
  }
}

// 初始化
onMounted(async () => {
  // 注册所有微应用
  applications.value.forEach(app => {
    microfrontendManager.registerApp(app)
  })
  
  // 加载默认应用
  await nextTick()
  await loadMicrofrontend(activeApp.value)
})
</script>

<style lang="scss" scoped>
.shell-app {
  height: 100vh;
  
  .shell-header {
    background: var(--el-color-white);
    border-bottom: 1px solid var(--el-border-color-light);
    padding: 0;
    
    .header-content {
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 100%;
      padding: 0 20px;
      
      .logo {
        display: flex;
        align-items: center;
        gap: 12px;
        
        img {
          height: 32px;
        }
        
        span {
          font-size: 18px;
          font-weight: 600;
          color: var(--el-text-color-primary);
        }
      }
      
      .main-menu {
        flex: 1;
        margin: 0 40px;
        border-bottom: none;
      }
      
      .header-actions {
        display: flex;
        align-items: center;
        gap: 16px;
        
        .user-info {
          display: flex;
          align-items: center;
          gap: 8px;
          cursor: pointer;
          padding: 8px;
          border-radius: 4px;
          transition: background-color 0.3s;
          
          &:hover {
            background-color: var(--el-fill-color-light);
          }
        }
      }
    }
  }
  
  .shell-main {
    padding: 0;
    
    .microfrontend-container {
      height: 100%;
      
      .loading-container,
      .error-container {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 100%;
        
        p {
          margin-top: 16px;
          color: var(--el-text-color-secondary);
        }
      }
    }
  }
}
</style>

2.2 微应用开发模板

typescript
// 微应用入口文件 (microfrontend-entry.ts)
import { createApp, App as VueApp } from 'vue'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import type { MicrofrontendContext } from '@/types/microfrontend'

// 微应用类
class MicrofrontendApp {
  private app: VueApp | null = null
  private container: HTMLElement | null = null
  
  // 挂载应用
  async mount(context: MicrofrontendContext): Promise<void> {
    const { container, props = {}, shared } = context
    
    this.container = container
    
    // 创建 Vue 应用
    this.app = createApp(App, props)
    
    // 配置 Pinia
    const pinia = createPinia()
    this.app.use(pinia)
    
    // 配置路由
    this.app.use(router)
    
    // 配置 Element Plus
    this.app.use(ElementPlus, {
      // 使用共享的主题配置
      namespace: 'el',
      locale: shared?.locale,
      size: shared?.size || 'default'
    })
    
    // 注册图标
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
      this.app.component(key, component)
    }
    
    // 提供共享上下文
    this.app.provide('microfrontendContext', context)
    this.app.provide('eventBus', shared?.eventBus)
    this.app.provide('sharedResources', shared?.sharedResources)
    
    // 挂载到容器
    this.app.mount(container)
    
    // 通知主应用挂载完成
    shared?.eventBus?.emit('microfrontend:mounted', {
      name: 'user-management',
      timestamp: Date.now()
    })
  }
  
  // 卸载应用
  async unmount(): Promise<void> {
    if (this.app) {
      this.app.unmount()
      this.app = null
    }
    
    if (this.container) {
      this.container.innerHTML = ''
      this.container = null
    }
  }
  
  // 更新属性
  update(props: Record<string, any>): void {
    // 更新应用属性的逻辑
    console.log('Updating microfrontend props:', props)
  }
}

// 导出微应用实例
const microfrontendApp = new MicrofrontendApp()

// 全局暴露
;(window as any).UserManagementApp = microfrontendApp

export default microfrontendApp
vue
<!-- 微应用主组件 (App.vue) -->
<template>
  <div class="microfrontend-app">
    <!-- 应用内导航 -->
    <div class="app-nav" v-if="showLocalNav">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item>{{ currentPageTitle }}</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    
    <!-- 路由视图 -->
    <div class="app-content">
      <router-view v-slot="{ Component }">
        <transition name="fade" mode="out-in">
          <component :is="Component" />
        </transition>
      </router-view>
    </div>
    
    <!-- 应用级通知 -->
    <AppNotifications />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, inject, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import AppNotifications from './components/AppNotifications.vue'
import type { EventBus } from '@/types/microfrontend'

// 注入共享上下文
const microfrontendContext = inject('microfrontendContext')
const eventBus = inject<EventBus>('eventBus')
const sharedResources = inject('sharedResources')

// 路由信息
const route = useRoute()

// 计算属性
const showLocalNav = computed(() => {
  return route.meta?.showBreadcrumb !== false
})

const currentPageTitle = computed(() => {
  return route.meta?.title || '页面'
})

// 事件处理
let unsubscribeEvents: (() => void)[] = []

onMounted(() => {
  // 监听全局事件
  if (eventBus) {
    // 监听主题变化
    const unsubscribeTheme = eventBus.on('theme:changed', (theme: string) => {
      console.log('Theme changed to:', theme)
      // 处理主题变化
    })
    
    // 监听用户状态变化
    const unsubscribeUser = eventBus.on('user:changed', (user: any) => {
      console.log('User changed:', user)
      // 处理用户状态变化
    })
    
    unsubscribeEvents.push(unsubscribeTheme, unsubscribeUser)
  }
  
  // 通知应用就绪
  eventBus?.emit('microfrontend:ready', {
    name: 'user-management',
    timestamp: Date.now()
  })
})

onUnmounted(() => {
  // 清理事件监听
  unsubscribeEvents.forEach(unsubscribe => unsubscribe())
  
  // 通知应用卸载
  eventBus?.emit('microfrontend:unmounted', {
    name: 'user-management',
    timestamp: Date.now()
  })
})
</script>

<style lang="scss" scoped>
.microfrontend-app {
  height: 100%;
  display: flex;
  flex-direction: column;
  
  .app-nav {
    padding: 16px 24px;
    background: var(--el-bg-color-page);
    border-bottom: 1px solid var(--el-border-color-lighter);
  }
  
  .app-content {
    flex: 1;
    overflow: auto;
  }
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

3. 跨应用通信

3.1 事件驱动通信

typescript
// 跨应用通信管理器
class CrossAppCommunication {
  private eventBus: EventBus
  private messageQueue: Map<string, any[]> = new Map()
  private subscriptions: Map<string, Set<string>> = new Map()
  
  constructor(eventBus: EventBus) {
    this.eventBus = eventBus
    this.setupGlobalEvents()
  }
  
  // 设置全局事件
  private setupGlobalEvents(): void {
    // 监听应用挂载事件
    this.eventBus.on('microfrontend:mounted', (data: any) => {
      this.handleAppMounted(data.name)
    })
    
    // 监听应用卸载事件
    this.eventBus.on('microfrontend:unmounted', (data: any) => {
      this.handleAppUnmounted(data.name)
    })
  }
  
  // 处理应用挂载
  private handleAppMounted(appName: string): void {
    // 发送排队的消息
    const queuedMessages = this.messageQueue.get(appName) || []
    queuedMessages.forEach(message => {
      this.eventBus.emit(`app:${appName}:message`, message)
    })
    this.messageQueue.delete(appName)
  }
  
  // 处理应用卸载
  private handleAppUnmounted(appName: string): void {
    // 清理订阅
    this.subscriptions.delete(appName)
  }
  
  // 发送消息到指定应用
  sendMessage(targetApp: string, message: any): void {
    const eventName = `app:${targetApp}:message`
    
    // 检查目标应用是否已挂载
    if (this.subscriptions.has(targetApp)) {
      this.eventBus.emit(eventName, message)
    } else {
      // 应用未挂载,将消息加入队列
      if (!this.messageQueue.has(targetApp)) {
        this.messageQueue.set(targetApp, [])
      }
      this.messageQueue.get(targetApp)!.push(message)
    }
  }
  
  // 广播消息到所有应用
  broadcast(message: any, excludeApps: string[] = []): void {
    this.eventBus.emit('global:broadcast', {
      message,
      excludeApps,
      timestamp: Date.now()
    })
  }
  
  // 订阅消息
  subscribe(appName: string, callback: (message: any) => void): () => void {
    if (!this.subscriptions.has(appName)) {
      this.subscriptions.set(appName, new Set())
    }
    
    const eventName = `app:${appName}:message`
    const unsubscribe = this.eventBus.on(eventName, callback)
    
    // 订阅广播消息
    const unsubscribeBroadcast = this.eventBus.on('global:broadcast', (data: any) => {
      if (!data.excludeApps.includes(appName)) {
        callback(data.message)
      }
    })
    
    return () => {
      unsubscribe()
      unsubscribeBroadcast()
    }
  }
  
  // 请求-响应模式
  async request(targetApp: string, request: any, timeout = 5000): Promise<any> {
    const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`Request timeout: ${targetApp}`)
      }, timeout)
      
      // 监听响应
      const unsubscribe = this.eventBus.on(`response:${requestId}`, (response: any) => {
        clearTimeout(timer)
        unsubscribe()
        
        if (response.error) {
          reject(new Error(response.error))
        } else {
          resolve(response.data)
        }
      })
      
      // 发送请求
      this.sendMessage(targetApp, {
        type: 'request',
        id: requestId,
        data: request
      })
    })
  }
  
  // 响应请求
  respond(requestId: string, response: any): void {
    this.eventBus.emit(`response:${requestId}`, {
      data: response,
      timestamp: Date.now()
    })
  }
  
  // 响应错误
  respondError(requestId: string, error: string): void {
    this.eventBus.emit(`response:${requestId}`, {
      error,
      timestamp: Date.now()
    })
  }
}

3.2 共享状态管理

typescript
// 跨应用状态管理
class SharedStateManager {
  private state: Map<string, any> = new Map()
  private subscribers: Map<string, Set<Function>> = new Map()
  private eventBus: EventBus
  
  constructor(eventBus: EventBus) {
    this.eventBus = eventBus
    this.setupStateSync()
  }
  
  // 设置状态同步
  private setupStateSync(): void {
    this.eventBus.on('state:update', (data: any) => {
      this.handleStateUpdate(data.key, data.value, data.source)
    })
    
    this.eventBus.on('state:request', (data: any) => {
      this.handleStateRequest(data.key, data.requestId)
    })
  }
  
  // 处理状态更新
  private handleStateUpdate(key: string, value: any, source: string): void {
    const oldValue = this.state.get(key)
    this.state.set(key, value)
    
    // 通知订阅者
    const keySubscribers = this.subscribers.get(key)
    if (keySubscribers) {
      keySubscribers.forEach(callback => {
        try {
          callback(value, oldValue, source)
        } catch (error) {
          console.error('Error in state subscriber:', error)
        }
      })
    }
  }
  
  // 处理状态请求
  private handleStateRequest(key: string, requestId: string): void {
    const value = this.state.get(key)
    this.eventBus.emit(`state:response:${requestId}`, { key, value })
  }
  
  // 设置状态
  setState(key: string, value: any, source = 'unknown'): void {
    this.state.set(key, value)
    
    // 广播状态更新
    this.eventBus.emit('state:update', { key, value, source })
  }
  
  // 获取状态
  getState(key: string): any {
    return this.state.get(key)
  }
  
  // 异步获取状态(从其他应用)
  async getStateAsync(key: string, timeout = 3000): Promise<any> {
    const requestId = `state_req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`State request timeout: ${key}`))
      }, timeout)
      
      const unsubscribe = this.eventBus.on(`state:response:${requestId}`, (data: any) => {
        clearTimeout(timer)
        unsubscribe()
        resolve(data.value)
      })
      
      this.eventBus.emit('state:request', { key, requestId })
    })
  }
  
  // 订阅状态变化
  subscribe(key: string, callback: (value: any, oldValue: any, source: string) => void): () => void {
    if (!this.subscribers.has(key)) {
      this.subscribers.set(key, new Set())
    }
    
    this.subscribers.get(key)!.add(callback)
    
    // 立即调用一次回调
    const currentValue = this.state.get(key)
    if (currentValue !== undefined) {
      callback(currentValue, undefined, 'initial')
    }
    
    // 返回取消订阅函数
    return () => {
      this.subscribers.get(key)?.delete(callback)
    }
  }
  
  // 批量更新状态
  batchUpdate(updates: Record<string, any>, source = 'batch'): void {
    Object.entries(updates).forEach(([key, value]) => {
      this.setState(key, value, source)
    })
  }
  
  // 清除状态
  clearState(key: string, source = 'clear'): void {
    this.state.delete(key)
    this.eventBus.emit('state:update', { key, value: undefined, source })
  }
  
  // 获取所有状态
  getAllState(): Record<string, any> {
    return Object.fromEntries(this.state)
  }
}

4. 主题和样式共享

4.1 主题共享策略

typescript
// 主题共享管理器
class ThemeShareManager {
  private currentTheme: string = 'default'
  private themeTokens: Map<string, any> = new Map()
  private eventBus: EventBus
  private cssVariables: Map<string, string> = new Map()
  
  constructor(eventBus: EventBus) {
    this.eventBus = eventBus
    this.setupThemeSync()
    this.loadSharedThemes()
  }
  
  // 设置主题同步
  private setupThemeSync(): void {
    this.eventBus.on('theme:change', (data: any) => {
      this.applyTheme(data.theme, data.source)
    })
    
    this.eventBus.on('theme:tokens:update', (data: any) => {
      this.updateThemeTokens(data.theme, data.tokens, data.source)
    })
  }
  
  // 加载共享主题
  private async loadSharedThemes(): Promise<void> {
    try {
      const response = await fetch('/shared/themes.json')
      const themes = await response.json()
      
      Object.entries(themes).forEach(([name, tokens]) => {
        this.themeTokens.set(name, tokens)
      })
    } catch (error) {
      console.error('Failed to load shared themes:', error)
    }
  }
  
  // 应用主题
  applyTheme(themeName: string, source = 'unknown'): void {
    const tokens = this.themeTokens.get(themeName)
    if (!tokens) {
      console.warn(`Theme '${themeName}' not found`)
      return
    }
    
    this.currentTheme = themeName
    this.updateCSSVariables(tokens)
    
    // 更新 HTML 属性
    document.documentElement.setAttribute('data-theme', themeName)
    
    // 通知其他应用
    if (source !== 'sync') {
      this.eventBus.emit('theme:change', {
        theme: themeName,
        source: 'sync'
      })
    }
  }
  
  // 更新主题令牌
  updateThemeTokens(themeName: string, tokens: any, source = 'unknown'): void {
    this.themeTokens.set(themeName, tokens)
    
    // 如果是当前主题,立即应用
    if (this.currentTheme === themeName) {
      this.updateCSSVariables(tokens)
    }
    
    // 通知其他应用
    if (source !== 'sync') {
      this.eventBus.emit('theme:tokens:update', {
        theme: themeName,
        tokens,
        source: 'sync'
      })
    }
  }
  
  // 更新 CSS 变量
  private updateCSSVariables(tokens: any): void {
    const root = document.documentElement
    const flatTokens = this.flattenTokens(tokens)
    
    Object.entries(flatTokens).forEach(([key, value]) => {
      const cssVar = `--${key.replace(/\./g, '-')}`
      root.style.setProperty(cssVar, String(value))
      this.cssVariables.set(cssVar, String(value))
    })
  }
  
  // 扁平化令牌
  private flattenTokens(obj: any, prefix = ''): Record<string, any> {
    const flattened: Record<string, any> = {}
    
    Object.entries(obj).forEach(([key, value]) => {
      const newKey = prefix ? `${prefix}.${key}` : key
      
      if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
        Object.assign(flattened, this.flattenTokens(value, newKey))
      } else {
        flattened[newKey] = value
      }
    })
    
    return flattened
  }
  
  // 获取当前主题
  getCurrentTheme(): string {
    return this.currentTheme
  }
  
  // 获取主题令牌
  getThemeTokens(themeName: string): any {
    return this.themeTokens.get(themeName)
  }
  
  // 获取所有主题
  getAllThemes(): string[] {
    return Array.from(this.themeTokens.keys())
  }
  
  // 注册新主题
  registerTheme(name: string, tokens: any): void {
    this.themeTokens.set(name, tokens)
    
    // 通知其他应用
    this.eventBus.emit('theme:registered', {
      name,
      tokens
    })
  }
  
  // 获取 CSS 变量值
  getCSSVariable(name: string): string | undefined {
    return this.cssVariables.get(name)
  }
  
  // 监听主题变化
  onThemeChange(callback: (theme: string) => void): () => void {
    return this.eventBus.on('theme:change', (data: any) => {
      callback(data.theme)
    })
  }
}

实践练习

练习 1:搭建微前端基础架构

  1. 创建主应用(Shell Application)
  2. 开发两个微应用(用户管理、商品管理)
  3. 实现应用的动态加载和卸载
  4. 配置共享的 Element Plus 组件库

练习 2:实现跨应用通信

  1. 实现事件总线通信机制
  2. 开发共享状态管理系统
  3. 实现请求-响应通信模式
  4. 测试应用间的数据同步

练习 3:主题和样式共享

  1. 实现主题的跨应用共享
  2. 开发主题动态切换功能
  3. 确保样式隔离和一致性
  4. 支持自定义主题的注册和应用

学习资源

作业

  1. 完成所有实践练习
  2. 设计并实现一个完整的微前端系统
  3. 编写微前端开发和部署指南
  4. 分析微前端架构的优缺点和适用场景

下一步学习计划

接下来我们将学习 Element Plus 组件库二次开发,了解如何基于 Element Plus 开发自定义组件库,实现企业级的组件扩展和定制。