Web Components 微前端实现与应用

Web Components 微前端实现与应用

1. 核心架构设计

1.1 微前端架构模式

// 微前端核心接口定义
interface MicroFrontendConfig {
  name: string;
  entry: string;          // 应用入口
  container: string;      // 挂载容器
  activeRule: string;     // 激活规则
  props?: Record<string, any>; // 传递的属性
}

interface WebComponentConfig {
  tagName: string;        // 自定义元素标签
  shadowDom?: boolean;    // 是否使用 Shadow DOM
  styleSheets?: string[]; // 样式表
}

class MicroFrontendApp {
  private config: MicroFrontendConfig;
  private componentConfig: WebComponentConfig;
  private isMounted: boolean = false;
  
  constructor(config: MicroFrontendConfig, componentConfig: WebComponentConfig) {
    this.config = config;
    this.componentConfig = componentConfig;
  }
  
  async load(): Promise<void> {
    // 动态加载应用资源
  }
  
  mount(container: HTMLElement): void {
    // 挂载应用到容器
  }
  
  unmount(): void {
    // 卸载应用
  }
}

2. Web Components 基础实现

2.1 基础 Web Component 类

// 基础微前端 Web Component
abstract class MicroFrontendComponent extends HTMLElement {
  protected shadow: ShadowRoot;
  protected appContainer: HTMLElement;
  protected styles: string;
  
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: 'open' });
    this.styles = this.getStyles();
    this.appContainer = document.createElement('div');
    this.appContainer.className = 'micro-app-container';
  }
  
  // 生命周期方法
  connectedCallback() {
    this.render();
    this.mountApp();
  }
  
  disconnectedCallback() {
    this.unmountApp();
  }
  
  attributeChangedCallback(name: string, oldValue: string, newValue: string) {
    if (oldValue !== newValue) {
      this.onAttributeChange(name, newValue);
    }
  }
  
  // 抽象方法
  protected abstract getStyles(): string;
  protected abstract mountApp(): Promise<void>;
  protected abstract unmountApp(): void;
  protected abstract onAttributeChange(name: string, value: string): void;
  
  // 渲染基础结构
  protected render() {
    this.shadow.innerHTML = `
      <style>${this.styles}</style>
      <div class="micro-app">
        <div class="loading" id="loading">加载中...</div>
        ${this.appContainer.outerHTML}
      </div>
    `;
    this.appContainer = this.shadow.getElementById('app-container') as HTMLElement;
  }
  
  // 显示/隐藏加载状态
  protected showLoading() {
    const loading = this.shadow.getElementById('loading');
    if (loading) loading.style.display = 'block';
  }
  
  protected hideLoading() {
    const loading = this.shadow.getElementById('loading');
    if (loading) loading.style.display = 'none';
  }
}

2.2 具体的微前端组件实现

// 具体的微前端应用组件
class UserManagementApp extends MicroFrontendComponent {
  private scriptElement?: HTMLScriptElement;
  private styleElement?: HTMLLinkElement;
  private appInstance: any;
  
  static get observedAttributes() {
    return ['api-url', 'theme', 'locale'];
  }
  
  protected getStyles(): string {
    return `
      :host {
        display: block;
        width: 100%;
        height: 100%;
      }
      
      .micro-app {
        width: 100%;
        height: 100%;
        font-family: inherit;
      }
      
      .loading {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 200px;
        font-size: 16px;
        color: #666;
      }
      
      .micro-app-container {
        width: 100%;
        height: 100%;
      }
    `;
  }
  
  protected async mountApp(): Promise<void> {
    try {
      this.showLoading();
      
      // 动态加载应用资源
      await this.loadResources();
      
      // 初始化应用
      await this.initializeApp();
      
      this.hideLoading();
    } catch (error) {
      console.error('Failed to mount user management app:', error);
      this.showError('应用加载失败');
    }
  }
  
  protected unmountApp(): void {
    // 清理资源
    if (this.appInstance && typeof this.appInstance.unmount === 'function') {
      this.appInstance.unmount();
    }
    
    // 移除动态添加的脚本和样式
    if (this.scriptElement) {
      document.head.removeChild(this.scriptElement);
    }
    if (this.styleElement) {
      document.head.removeChild(this.styleElement);
    }
    
    // 清理容器
    this.appContainer.innerHTML = '';
  }
  
  protected onAttributeChange(name: string, value: string): void {
    switch (name) {
      case 'api-url':
        this.updateApiUrl(value);
        break;
      case 'theme':
        this.updateTheme(value);
        break;
      case 'locale':
        this.updateLocale(value);
        break;
    }
  }
  
  private async loadResources(): Promise<void> {
    const baseUrl = this.getAttribute('base-url') || 'https://cdn.example.com/user-app';
    
    // 加载样式
    this.styleElement = document.createElement('link');
    this.styleElement.rel = 'stylesheet';
    this.styleElement.href = `${baseUrl}/styles.css`;
    document.head.appendChild(this.styleElement);
    
    // 加载脚本
    this.scriptElement = document.createElement('script');
    this.scriptElement.src = `${baseUrl}/app.js`;
    this.scriptElement.type = 'module';
    
    await new Promise((resolve, reject) => {
      this.scriptElement!.onload = resolve;
      this.scriptElement!.onerror = reject;
      document.head.appendChild(this.scriptElement!);
    });
  }
  
  private async initializeApp(): Promise<void> {
    // 从全局对象获取应用工厂函数
    const appFactory = (window as any).UserManagementApp;
    if (!appFactory) {
      throw new Error('UserManagementApp not found');
    }
    
    // 获取配置属性
    const config = {
      apiUrl: this.getAttribute('api-url') || '/api',
      theme: this.getAttribute('theme') || 'light',
      locale: this.getAttribute('locale') || 'zh-CN',
      container: this.appContainer,
      onEvent: this.handleAppEvent.bind(this)
    };
    
    // 初始化应用
    this.appInstance = appFactory(config);
    if (this.appInstance && typeof this.appInstance.mount === 'function') {
      await this.appInstance.mount();
    }
  }
  
  private handleAppEvent(event: any) {
    // 处理应用内部事件
    this.dispatchEvent(new CustomEvent('app-event', {
      detail: event,
      bubbles: true,
      composed: true
    }));
  }
  
  private updateApiUrl(url: string) {
    if (this.appInstance && typeof this.appInstance.updateApiUrl === 'function') {
      this.appInstance.updateApiUrl(url);
    }
  }
  
  private updateTheme(theme: string) {
    if (this.appInstance && typeof this.appInstance.updateTheme === 'function') {
      this.appInstance.updateTheme(theme);
    }
  }
  
  private updateLocale(locale: string) {
    if (this.appInstance && typeof this.appInstance.updateLocale === 'function') {
      this.appInstance.updateLocale(locale);
    }
  }
  
  private showError(message: string) {
    this.appContainer.innerHTML = `
      <div class="error-container">
        <h3>应用加载失败</h3>
        <p>${message}</p>
        <button onclick="location.reload()">重试</button>
      </div>
    `;
  }
}

// 注册自定义元素
customElements.define('user-management-app', UserManagementApp);

3. 微前端路由系统

3.1 基于 Web Components 的路由器

class MicroFrontendRouter {
  private apps: Map<string, MicroFrontendApp> = new Map();
  private currentApp: string | null = null;
  private routeConfig: RouteConfig[] = [];
  
  constructor(private container: HTMLElement) {
    this.initRouter();
  }
  
  // 注册微前端应用
  registerApp(config: MicroFrontendConfig): void {
    const app = new MicroFrontendApp(config);
    this.apps.set(config.name, app);
    this.routeConfig.push({
      name: config.name,
      activeRule: config.activeRule
    });
  }
  
  // 启动路由
  start(): void {
    this.navigateTo(window.location.pathname);
    window.addEventListener('popstate', () => {
      this.navigateTo(window.location.pathname);
    });
  }
  
  // 导航到指定路径
  navigateTo(path: string): void {
    const appConfig = this.findAppByPath(path);
    
    if (!appConfig) {
      this.showNotFound();
      return;
    }
    
    if (this.currentApp === appConfig.name) {
      return; // 已经是当前应用
    }
    
    // 卸载当前应用
    if (this.currentApp) {
      const currentApp = this.apps.get(this.currentApp);
      currentApp?.unmount();
    }
    
    // 加载新应用
    const nextApp = this.apps.get(appConfig.name);
    if (nextApp) {
      this.currentApp = appConfig.name;
      nextApp.mount(this.container);
      
      // 更新浏览器历史
      if (window.location.pathname !== path) {
        window.history.pushState(null, '', path);
      }
    }
  }
  
  private findAppByPath(path: string): RouteConfig | null {
    return this.routeConfig.find(route => 
      typeof route.activeRule === 'function' 
        ? route.activeRule(path)
        : path.startsWith(route.activeRule as string)
    ) || null;
  }
  
  private showNotFound(): void {
    this.container.innerHTML = `
      <div class="not-found">
        <h1>404 - 页面未找到</h1>
        <p>请求的路径 ${window.location.pathname} 不存在</p>
      </div>
    `;
  }
  
  private initRouter(): void {
    // 拦截链接点击事件
    document.addEventListener('click', (e) => {
      const target = e.target as HTMLElement;
      const link = target.closest('a[href]');
      
      if (link && this.isInternalLink(link as HTMLAnchorElement)) {
        e.preventDefault();
        const href = (link as HTMLAnchorElement).href;
        const path = new URL(href).pathname;
        this.navigateTo(path);
      }
    });
  }
  
  private isInternalLink(link: HTMLAnchorElement): boolean {
    return link.origin === window.location.origin;
  }
}

interface RouteConfig {
  name: string;
  activeRule: string | ((path: string) => boolean);
}

3.2 应用间通信系统

// 微前端事件总线
class MicroFrontendEventBus {
  private listeners: Map<string, Function[]> = new Map();
  
  // 发布事件
  emit(event: string, data?: any): void {
    const eventListeners = this.listeners.get(event) || [];
    eventListeners.forEach(listener => {
      try {
        listener(data);
      } catch (error) {
        console.error(`Error in event listener for ${event}:`, error);
      }
    });
    
    // 同时派发到全局
    window.dispatchEvent(new CustomEvent(`micro-frontend:${event}`, {
      detail: data
    }));
  }
  
  // 订阅事件
  on(event: string, listener: Function): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(listener);
    
    // 返回取消订阅函数
    return () => this.off(event, listener);
  }
  
  // 取消订阅
  off(event: string, listener: Function): void {
    const eventListeners = this.listeners.get(event);
    if (eventListeners) {
      const index = eventListeners.indexOf(listener);
      if (index > -1) {
        eventListeners.splice(index, 1);
      }
    }
  }
  
  // 一次性订阅
  once(event: string, listener: Function): void {
    const onceListener = (data: any) => {
      listener(data);
      this.off(event, onceListener);
    };
    this.on(event, onceListener);
  }
}

// 全局状态管理
class GlobalStateManager {
  private state: Record<string, any> = {};
  private subscribers: Map<string, Function[]> = new Map();
  
  setState(key: string, value: any): void {
    const oldValue = this.state[key];
    this.state[key] = value;
    
    // 通知订阅者
    const keySubscribers = this.subscribers.get(key) || [];
    keySubscribers.forEach(subscriber => {
      try {
        subscriber(value, oldValue);
      } catch (error) {
        console.error(`Error in state subscriber for ${key}:`, error);
      }
    });
  }
  
  getState<T = any>(key: string): T | undefined {
    return this.state[key];
  }
  
  subscribe(key: string, subscriber: (newValue: any, oldValue: any) => void): () => void {
    if (!this.subscribers.has(key)) {
      this.subscribers.set(key, []);
    }
    this.subscribers.get(key)!.push(subscriber);
    
    return () => this.unsubscribe(key, subscriber);
  }
  
  unsubscribe(key: string, subscriber: Function): void {
    const keySubscribers = this.subscribers.get(key);
    if (keySubscribers) {
      const index = keySubscribers.indexOf(subscriber);
      if (index > -1) {
        keySubscribers.splice(index, 1);
      }
    }
  }
}

// 全局单例
export const eventBus = new MicroFrontendEventBus();
export const globalState = new GlobalStateManager();

4. 完整的微前端应用示例

4.1 主应用 (Shell App)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>微前端主应用</title>
    <style>
        body {
            margin: 0;
            font-family: Arial, sans-serif;
        }
        
        .header {
            background: #f5f5f5;
            padding: 1rem;
            border-bottom: 1px solid #ddd;
        }
        
        .nav {
            display: flex;
            gap: 1rem;
        }
        
        .nav a {
            text-decoration: none;
            color: #333;
            padding: 0.5rem 1rem;
            border-radius: 4px;
        }
        
        .nav a:hover {
            background: #e9e9e9;
        }
        
        .nav a.active {
            background: #007acc;
            color: white;
        }
        
        .main-container {
            padding: 1rem;
            height: calc(100vh - 80px);
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>微前端平台</h1>
        <div class="nav">
            <a href="/" data-route="/">首页</a>
            <a href="/user-management" data-route="/user-management">用户管理</a>
            <a href="/product-management" data-route="/product-management">产品管理</a>
            <a href="/order-management" data-route="/order-management">订单管理</a>
        </div>
    </div>
    
    <div class="main-container" id="app-container">
        <!-- 微前端应用将在这里渲染 -->
    </div>

    <script type="module">
        import { MicroFrontendRouter } from './micro-frontend-router.js';
        
        // 初始化路由
        const router = new MicroFrontendRouter(
            document.getElementById('app-container')
        );
        
        // 注册微前端应用
        router.registerApp({
            name: 'home',
            entry: '/apps/home/app.js',
            container: '#app-container',
            activeRule: '/'
        });
        
        router.registerApp({
            name: 'user-management',
            entry: '/apps/user-management/app.js',
            container: '#app-container',
            activeRule: '/user-management'
        });
        
        router.registerApp({
            name: 'product-management',
            entry: '/apps/product-management/app.js',
            container: '#app-container',
            activeRule: '/product-management'
        });
        
        router.registerApp({
            name: 'order-management',
            entry: '/apps/order-management/app.js',
            container: '#app-container',
            activeRule: '/order-management'
        });
        
        // 启动路由
        router.start();
        
        // 导航高亮
        document.addEventListener('click', (e) => {
            const target = e.target as HTMLElement;
            const link = target.closest('a[data-route]');
            if (link) {
                document.querySelectorAll('a[data-route]').forEach(a => {
                    a.classList.remove('active');
                });
                link.classList.add('active');
            }
        });
    </script>
</body>
</html>

4.2 用户管理微应用

// apps/user-management/app.js
class UserManagementApp {
  constructor(config) {
    this.config = config;
    this.container = config.container;
    this.apiUrl = config.apiUrl;
    this.eventHandler = config.onEvent;
  }
  
  async mount() {
    console.log('Mounting User Management App');
    
    // 渲染应用界面
    this.render();
    
    // 加载用户数据
    await this.loadUsers();
    
    // 绑定事件
    this.bindEvents();
  }
  
  unmount() {
    console.log('Unmounting User Management App');
    this.unbindEvents();
    this.container.innerHTML = '';
  }
  
  render() {
    this.container.innerHTML = `
      <div class="user-management-app">
        <h2>用户管理系统</h2>
        <div class="toolbar">
          <button id="add-user">添加用户</button>
          <input type="text" id="search" placeholder="搜索用户...">
        </div>
        <div class="user-list" id="user-list">
          <div class="loading">加载中...</div>
        </div>
      </div>
    `;
  }
  
  async loadUsers() {
    try {
      const response = await fetch(`${this.apiUrl}/users`);
      const users = await response.json();
      
      const userList = this.container.querySelector('#user-list');
      userList.innerHTML = users.map(user => `
        <div class="user-item" data-user-id="${user.id}">
          <span class="user-name">${user.name}</span>
          <span class="user-email">${user.email}</span>
          <button class="edit-btn">编辑</button>
          <button class="delete-btn">删除</button>
        </div>
      `).join('');
    } catch (error) {
      console.error('Failed to load users:', error);
      this.container.querySelector('#user-list').innerHTML = 
        '<div class="error">加载用户失败</div>';
    }
  }
  
  bindEvents() {
    this.container.addEventListener('click', this.handleClick.bind(this));
    this.container.addEventListener('input', this.handleSearch.bind(this));
  }
  
  unbindEvents() {
    this.container.removeEventListener('click', this.handleClick.bind(this));
    this.container.removeEventListener('input', this.handleSearch.bind(this));
  }
  
  handleClick(event) {
    const target = event.target;
    
    if (target.id === 'add-user') {
      this.eventHandler({ type: 'ADD_USER' });
    } else if (target.classList.contains('edit-btn')) {
      const userId = target.closest('.user-item').dataset.userId;
      this.eventHandler({ type: 'EDIT_USER', userId });
    } else if (target.classList.contains('delete-btn')) {
      const userId = target.closest('.user-item').dataset.userId;
      this.deleteUser(userId);
    }
  }
  
  handleSearch(event) {
    if (event.target.id === 'search') {
      const searchTerm = event.target.value;
      // 实现搜索逻辑
      this.eventHandler({ type: 'SEARCH_USERS', searchTerm });
    }
  }
  
  async deleteUser(userId) {
    if (confirm('确定要删除这个用户吗?')) {
      try {
        await fetch(`${this.apiUrl}/users/${userId}`, {
          method: 'DELETE'
        });
        await this.loadUsers(); // 重新加载列表
        this.eventHandler({ type: 'USER_DELETED', userId });
      } catch (error) {
        console.error('Failed to delete user:', error);
        alert('删除用户失败');
      }
    }
  }
  
  updateApiUrl(newUrl) {
    this.apiUrl = newUrl;
    this.loadUsers(); // 重新加载数据
  }
  
  updateTheme(theme) {
    this.container.setAttribute('data-theme', theme);
  }
  
  updateLocale(locale) {
    // 更新本地化
    console.log('Update locale to:', locale);
  }
}

// 暴露给全局,供 Web Component 调用
window.UserManagementApp = (config) => new UserManagementApp(config);

4.3 样式文件

/* apps/user-management/styles.css */
.user-management-app {
  padding: 20px;
  font-family: Arial, sans-serif;
}

.user-management-app[data-theme="dark"] {
  background: #333;
  color: white;
}

.user-management-app .toolbar {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  align-items: center;
}

.user-management-app button {
  padding: 8px 16px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 4px;
  cursor: pointer;
}

.user-management-app button:hover {
  background: #f5f5f5;
}

.user-management-app input {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.user-list {
  border: 1px solid #eee;
  border-radius: 4px;
}

.user-item {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
  gap: 10px;
}

.user-item:last-child {
  border-bottom: none;
}

.user-name {
  font-weight: bold;
  min-width: 100px;
}

.user-email {
  color: #666;
  flex: 1;
}

.loading, .error {
  text-align: center;
  padding: 40px;
  color: #666;
}

.error {
  color: #d32f2f;
}

5. 高级特性

5.1 应用预加载

class AppPreloader {
  private preloadedApps: Map<string, boolean> = new Map();
  
  // 预加载应用资源
  async preloadApp(appName: string, entry: string): Promise<void> {
    if (this.preloadedApps.has(appName)) {
      return;
    }
    
    try {
      // 预加载脚本
      const script = document.createElement('script');
      script.src = entry;
      script.type = 'module';
      script.async = true;
      
      await new Promise((resolve, reject) => {
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
      });
      
      this.preloadedApps.set(appName, true);
      console.log(`Preloaded app: ${appName}`);
    } catch (error) {
      console.error(`Failed to preload app ${appName}:`, error);
    }
  }
  
  // 基于用户行为预测预加载
  predictivePreload(currentPath: string): void {
    const likelyNextApps = this.predictNextApps(currentPath);
    likelyNextApps.forEach(app => {
      this.preloadApp(app.name, app.entry);
    });
  }
  
  private predictNextApps(currentPath: string): Array<{name: string, entry: string}> {
    // 基于当前路径预测用户可能访问的下一个应用
    const predictions = [];
    
    if (currentPath === '/') {
      predictions.push(
        { name: 'user-management', entry: '/apps/user-management/app.js' }
      );
    }
    
    return predictions;
  }
}

5.2 性能监控

class PerformanceMonitor {
  private metrics: Map<string, number> = new Map();
  
  startMeasure(appName: string, metric: string): void {
    const key = `${appName}:${metric}`;
    this.metrics.set(key, performance.now());
  }
  
  endMeasure(appName: string, metric: string): number {
    const key = `${appName}:${metric}`;
    const startTime = this.metrics.get(key);
    
    if (startTime) {
      const duration = performance.now() - startTime;
      this.metrics.delete(key);
      
      // 上报性能数据
      this.reportMetric(appName, metric, duration);
      
      return duration;
    }
    
    return 0;
  }
  
  private reportMetric(appName: string, metric: string, value: number): void {
    // 上报到监控系统
    console.log(`[Performance] ${appName} - ${metric}: ${value.toFixed(2)}ms`);
    
    // 可以发送到 APM 系统
    if (navigator.sendBeacon) {
      const data = {
        app: appName,
        metric,
        value,
        timestamp: Date.now()
      };
      navigator.sendBeacon('/api/performance', JSON.stringify(data));
    }
  }
  
  // 监控应用加载性能
  monitorAppLoad(appName: string, loadPromise: Promise<any>): void {
    this.startMeasure(appName, 'app-load');
    
    loadPromise
      .then(() => {
        const loadTime = this.endMeasure(appName, 'app-load');
        console.log(`App ${appName} loaded in ${loadTime}ms`);
      })
      .catch(error => {
        console.error(`App ${appName} failed to load:`, error);
      });
  }
}

这种基于 Web Components 的微前端架构提供了:

  1. 技术栈无关性:每个微应用可以使用不同的技术栈
  2. 样式隔离:通过 Shadow DOM 实现样式隔离
  3. 独立部署:每个微应用可以独立开发、测试和部署
  4. 运行时集成:动态加载和卸载应用
  5. 通信机制:通过事件总线和全局状态实现应用间通信
  6. 性能优化:支持预加载和懒加载

这种架构特别适合大型复杂应用的模块化开发和维护。

posted @ 2025-10-16 00:24  阿木隆1237  阅读(23)  评论(0)    收藏  举报