微前端架构实战:从模块联邦到沙箱隔离

0 阅读8分钟

微前端架构实战:从模块联邦到沙箱隔离

引言

当你的前端应用从一个单体巨石变成多个团队并行开发的复杂系统时,微前端架构成为解决技术栈异构、团队协作、独立部署等问题的关键方案。微前端不是银弹,但它在正确的场景下能带来显著的工程效能提升。

本文将从五个维度展开:

  • 微前端设计模式:从iframe到模块联邦的演进
  • 模块联邦实战:Webpack Module Federation深度应用
  • 沙箱隔离机制:JS沙箱与样式隔离
  • 应用通信机制:跨应用状态管理与事件总线
  • 部署与监控:微前端部署策略与可观测性

一、微前端设计模式:从iframe到模块联邦

1.1 微前端方案演进

// 方案对比
export enum MicroFrontendStrategy {
  IFRAME = 'iframe',           // 简单但隔离过强
  ROUTER = 'router',           // 路由分发,共享运行时
  MODULE_FEDERATION = 'module-federation', // 模块联邦,共享依赖
  WEB_COMPONENTS = 'web-components', // Web Components标准
  SINGLE_SPA = 'single-spa'    // 应用生命周期管理
}

// 各方案优缺点分析
export const strategyAnalysis = {
  [MicroFrontendStrategy.IFRAME]: {
    pros: ['完全隔离', '技术栈无关', '部署简单'],
    cons: ['通信复杂', '体验割裂', '性能开销大', 'SEO不友好']
  },
  [MicroFrontendStrategy.ROUTER]: {
    pros: ['体验一致', '通信简单', '性能较好'],
    cons: ['技术栈受限', '依赖共享', '耦合度高']
  },
  [MicroFrontendStrategy.MODULE_FEDERATION]: {
    pros: ['依赖共享', '独立部署', '运行时加载', '性能优秀'],
    cons: ['配置复杂', '版本管理难', '需要Webpack5+']
  },
  [MicroFrontendStrategy.WEB_COMPONENTS]: {
    pros: ['标准规范', '样式隔离', '技术栈无关'],
    cons: ['生态不成熟', '交互复杂', '性能开销']
  },
  [MicroFrontendStrategy.SINGLE_SPA]: {
    pros: ['生命周期管理', '灵活集成', '技术栈无关'],
    cons: ['需要统一规范', '样式隔离难', '学习成本高']
  }
};

1.2 微前端架构设计

// architecture/MicroFrontendArchitecture.ts - 微前端架构设计
export interface MicroFrontendConfig {
  name: string;
  entry: string;
  container: string;
  activeRule: string | ((location: Location) => boolean);
  props?: Record<string, unknown>;
}

export interface MicroFrontendApp {
  name: string;
  mount: (container: HTMLElement, props: AppProps) => void;
  unmount: (container: HTMLElement, props: AppProps) => void;
  bootstrap?: (props: AppProps) => Promise<void>;
}

// 主应用架构
export class MainApplication {
  private apps: Map<string, MicroFrontendApp> = new Map();
  private containers: Map<string, HTMLElement> = new Map();
  private currentApp: string | null = null;

  constructor(
    private readonly router: Router,
    private readonly eventBus: EventBus,
    private readonly registry: AppRegistry
  ) {
    this.initializeRouting();
  }

  // 注册子应用
  async registerApp(config: MicroFrontendConfig): Promise<void> {
    // 1. 加载子应用入口
    const app = await this.loadApp(config.entry);

    // 2. 注册到应用注册表
    this.registry.register({
      ...config,
      load: () => app
    });

    // 3. 存储应用实例
    this.apps.set(config.name, app);

    // 4. 创建容器
    const container = document.createElement('div');
    container.id = `micro-app-${config.name}`;
    document.body.appendChild(container);
    this.containers.set(config.name, container);
  }

  // 加载应用
  private async loadApp(entry: string): Promise<MicroFrontendApp> {
    // 动态导入子应用
    const module = await import(/* @vite-ignore */ entry);
    return module.default as MicroFrontendApp;
  }

  // 初始化路由
  private initializeRouting(): void {
    this.router.onRouteChange((location: Location) => {
      // 查找匹配的子应用
      const matchedApp = this.findMatchingApp(location);

      if (matchedApp) {
        this.switchToApp(matchedApp);
      }
    });
  }

  // 查找匹配的子应用
  private findMatchingApp(location: Location): MicroFrontendConfig | null {
    const apps = this.registry.getAll();

    for (const app of apps) {
      const activeRule = app.activeRule;

      if (typeof activeRule === 'string') {
        if (location.pathname.startsWith(activeRule)) {
          return app;
        }
      } else if (typeof activeRule === 'function') {
        if (activeRule(location)) {
          return app;
        }
      }
    }

    return null;
  }

  // 切换到指定应用
  private async switchToApp(config: MicroFrontendConfig): Promise<void> {
    // 1. 卸载当前应用
    if (this.currentApp) {
      await this.unmountApp(this.currentApp);
    }

    // 2. 挂载新应用
    const app = this.apps.get(config.name);
    const container = this.containers.get(config.name);

    if (app && container) {
      // 设置当前应用
      this.currentApp = config.name;

      // 挂载应用
      await app.mount(container, {
        router: this.router,
        eventBus: this.eventBus,
        globalState: this.getGlobalState()
      });

      // 发布应用切换事件
      this.eventBus.publish('app:switched', {
        from: this.currentApp,
        to: config.name
      });
    }
  }

  // 卸载应用
  private async unmountApp(appName: string): Promise<void> {
    const app = this.apps.get(appName);
    const container = this.containers.get(appName);

    if (app && container) {
      await app.unmount(container, {
        router: this.router,
        eventBus: this.eventBus
      });

      // 清空容器
      container.innerHTML = '';
    }
  }

  // 获取全局状态
  private getGlobalState(): GlobalState {
    return {
      user: this.getCurrentUser(),
      theme: this.getCurrentTheme(),
      locale: this.getCurrentLocale()
    };
  }
}

// 子应用架构
export class MicroApp {
  private container: HTMLElement | null = null;
  private props: AppProps | null = null;

  constructor(
    private readonly appName: string,
    private readonly mountFn: (container: HTMLElement, props: AppProps) => void,
    private readonly unmountFn: (container: HTMLElement, props: AppProps) => void
  ) {}

  // 挂载
  async mount(container: HTMLElement, props: AppProps): Promise<void> {
    this.container = container;
    this.props = props;

    // 1. 创建应用容器
    const appContainer = document.createElement('div');
    appContainer.id = `${this.appName}-root`;
    container.appendChild(appContainer);

    // 2. 执行挂载
    this.mountFn(appContainer, props);

    // 3. 发布挂载事件
    props.eventBus?.publish(`app:${this.appName}:mounted`);
  }

  // 卸载
  async unmount(container: HTMLElement, props: AppProps): Promise<void> {
    // 1. 执行卸载
    if (this.container) {
      this.unmountFn(this.container, props);
    }

    // 2. 清理容器
    if (this.container) {
      this.container.innerHTML = '';
      this.container.remove();
    }

    // 3. 发布卸载事件
    props.eventBus?.publish(`app:${this.appName}:unmounted`);

    this.container = null;
    this.props = null;
  }
}

二、模块联邦实战:Webpack Module Federation深度应用

2.1 主应用配置

// webpack.config.js - 主应用配置
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  // 主应用配置
  plugins: [
    new ModuleFederationPlugin({
      // 主应用名称
      name: 'main_app',

      // 主应用暴露的模块
      exposes: {
        './MainLayout': './src/layouts/MainLayout',
        './GlobalComponents': './src/components/GlobalComponents',
        './Utils': './src/utils'
      },

      // 共享依赖
      shared: {
        react: {
          singleton: true,
          requiredVersion: deps.react,
          eager: true
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom'],
          eager: true
        },
        'react-router-dom': {
          singleton: true,
          requiredVersion: deps['react-router-dom']
        },
        '@tanstack/react-query': {
          singleton: true,
          requiredVersion: deps['@tanstack/react-query']
        }
      }
    })
  ]
};

2.2 子应用配置

// webpack.config.js - 子应用配置
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 子应用名称
      name: 'user_app',

      // 子应用入口文件
      filename: 'remoteEntry.js',

      // 子应用暴露的模块
      exposes: {
        './UserList': './src/pages/UserList',
        './UserDetail': './src/pages/UserDetail',
        './UserForm': './src/components/UserForm',
        './UserStore': './src/store/userStore'
      },

      // 共享依赖
      shared: {
        react: {
          singleton: true,
          requiredVersion: deps.react
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom']
        },
        'react-router-dom': {
          singleton: true,
          requiredVersion: deps['react-router-dom']
        }
      }
    })
  ]
};

2.3 运行时加载远程模块

// module-federation/RemoteModuleLoader.ts - 远程模块加载器
export class RemoteModuleLoader {
  private static instance: RemoteModuleLoader;
  private loadedModules: Map<string, any> = new Map();
  private loadingPromises: Map<string, Promise<any>> = new Map();

  private constructor() {}

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

  // 加载远程模块
  async loadRemoteModule<T>(
    remoteName: string,
    moduleName: string
  ): Promise<T> {
    const key = `${remoteName}:${moduleName}`;

    // 1. 检查是否已加载
    if (this.loadedModules.has(key)) {
      return this.loadedModules.get(key) as T;
    }

    // 2. 检查是否正在加载
    if (this.loadingPromises.has(key)) {
      return this.loadingPromises.get(key) as Promise<T>;
    }

    // 3. 加载远程模块
    const loadPromise = this.performLoad<T>(remoteName, moduleName);
    this.loadingPromises.set(key, loadPromise);

    try {
      const module = await loadPromise;
      this.loadedModules.set(key, module);
      return module;
    } finally {
      this.loadingPromises.delete(key);
    }
  }

  // 执行加载
  private async performLoad<T>(
    remoteName: string,
    moduleName: string
  ): Promise<T> {
    // 1. 获取远程模块入口
    const remoteEntry = this.getRemoteEntry(remoteName);

    // 2. 加载远程模块
    const container = await this.loadContainer(remoteName, remoteEntry);

    // 3. 获取模块工厂
    const factory = await container.get(moduleName);

    // 4. 创建模块实例
    const module = factory();

    return module as T;
  }

  // 获取远程模块入口
  private getRemoteEntry(remoteName: string): string {
    // 从配置中获取远程模块入口
    const remotes = {
      user_app: 'http://localhost:3001/remoteEntry.js',
      order_app: 'http://localhost:3002/remoteEntry.js',
      product_app: 'http://localhost:3003/remoteEntry.js'
    };

    return remotes[remoteName];
  }

  // 加载容器
  private async loadContainer(
    remoteName: string,
    remoteEntry: string
  ): Promise<any> {
    // 1. 动态加载remoteEntry脚本
    await this.loadScript(remoteEntry);

    // 2. 获取容器
    const container = window[remoteName];

    if (!container) {
      throw new Error(`远程模块 ${remoteName} 加载失败`);
    }

    // 3. 初始化容器
    await container.init({
      react: {
        singleton: true,
        version: '18.2.0'
      },
      'react-dom': {
        singleton: true,
        version: '18.2.0'
      }
    });

    return container;
  }

  // 动态加载脚本
  private loadScript(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.type = 'text/javascript';
      script.async = true;

      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`脚本加载失败: ${url}`));

      document.head.appendChild(script);
    });
  }
}

// 使用示例
const remoteLoader = RemoteModuleLoader.getInstance();

// 加载用户列表组件
const UserList = await remoteLoader.loadRemoteModule<React.ComponentType>(
  'user_app',
  './UserList'
);

// 加载用户Store
const userStore = await remoteLoader.loadRemoteModule<UserStore>(
  'user_app',
  './UserStore'
);

2.4 共享依赖管理

// module-federation/SharedDependencyManager.ts - 共享依赖管理
export class SharedDependencyManager {
  private static instance: SharedDependencyManager;
  private sharedVersions: Map<string, string> = new Map();
  private conflictResolutions: Map<string, string> = new Map();

  private constructor() {
    this.initializeSharedDependencies();
  }

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

  // 初始化共享依赖
  private initializeSharedDependencies(): void {
    // 从package.json获取依赖版本
    const deps = require('./package.json').dependencies;

    Object.entries(deps).forEach(([name, version]) => {
      this.sharedVersions.set(name, version as string);
    });
  }

  // 注册共享依赖
  registerSharedDependency(name: string, version: string): void {
    // 检查版本冲突
    const existingVersion = this.sharedVersions.get(name);

    if (existingVersion && existingVersion !== version) {
      // 版本冲突,记录冲突
      this.conflictResolutions.set(name, this.resolveConflict(name, existingVersion, version));
    }

    this.sharedVersions.set(name, version);
  }

  // 解决版本冲突
  private resolveConflict(
    name: string,
    version1: string,
    version2: string
  ): string {
    // 策略1:使用更高版本
    const v1 = this.parseVersion(version1);
    const v2 = this.parseVersion(version2);

    if (v1 > v2) {
      console.warn(`依赖 ${name} 版本冲突,使用更高版本 ${version1}`);
      return version1;
    } else {
      console.warn(`依赖 ${name} 版本冲突,使用更高版本 ${version2}`);
      return version2;
    }
  }

  // 解析版本号
  private parseVersion(version: string): number {
    const match = version.match(/(\d+).(\d+).(\d+)/);
    if (!match) return 0;

    return parseInt(match[1]) * 10000 +
           parseInt(match[2]) * 100 +
           parseInt(match[3]);
  }

  // 获取共享依赖配置
  getSharedConfig(): Record<string, unknown> {
    const sharedConfig = {};

    this.sharedVersions.forEach((version, name) => {
      sharedConfig[name] = {
        singleton: true,
        requiredVersion: version,
        eager: false
      };
    });

    return sharedConfig;
  }

  // 获取版本冲突报告
  getConflictReport(): Map<string, string> {
    return this.conflictResolutions;
  }
}

三、沙箱隔离机制:JS沙箱与样式隔离

3.1 JavaScript沙箱

// sandbox/JavaScriptSandbox.ts - JavaScript沙箱
export class JavaScriptSandbox {
  private windowProxy: Window;
  private sandboxGlobal: Record<string, unknown> = {};
  private active = false;
  private listeners: Array<{
    target: EventTarget;
    type: string;
    listener: EventListener;
  }> = [];

  constructor(private readonly appName: string) {
    this.windowProxy = this.createWindowProxy();
  }

  // 激活沙箱
  activate(): void {
    this.active = true;
    this.patchWindow();
    this.patchDocument();
    this.patchStorage();
  }

  // 停用沙箱
  deactivate(): void {
    this.active = false;
    this.restoreWindow();
    this.restoreDocument();
    this.restoreStorage();
    this.clearListeners();
  }

  // 创建Window代理
  private createWindowProxy(): Window {
    const rawWindow = window;
    const sandbox = this;

    return new Proxy(rawWindow, {
      get(target, prop) {
        // 1. 检查沙箱全局变量
        if (prop in sandbox.sandboxGlobal) {
          return sandbox.sandboxGlobal[prop];
        }

        // 2. 检查内置属性
        if (typeof prop === 'string' && prop.startsWith('__')) {
          return undefined;
        }

        // 3. 返回原始值
        return Reflect.get(target, prop);
      },

      set(target, prop, value) {
        // 1. 拦截window属性设置
        if (typeof prop === 'string') {
          // 禁止修改关键属性
          if (['location', 'parent', 'top', 'self'].includes(prop)) {
            console.warn(`禁止修改 ${prop} 属性`);
            return true;
          }

          // 存入沙箱全局变量
          sandbox.sandboxGlobal[prop] = value;
          return true;
        }

        return Reflect.set(target, prop, value);
      },

      has(target, prop) {
        // 检查是否存在
        return prop in sandbox.sandboxGlobal || prop in target;
      },

      deleteProperty(target, prop) {
        // 删除沙箱变量
        if (prop in sandbox.sandboxGlobal) {
          delete sandbox.sandboxGlobal[prop];
          return true;
        }

        return Reflect.deleteProperty(target, prop);
      }
    });
  }

  // 修补Window
  private patchWindow(): void {
    // 1. 拦截定时器
    const originalSetTimeout = window.setTimeout;
    const originalSetInterval = window.setInterval;

    window.setTimeout = (fn: Function, delay: number, ...args: any[]) => {
      const wrappedFn = () => {
        if (this.active) {
          fn.apply(this.windowProxy, args);
        }
      };
      return originalSetTimeout.call(window, wrappedFn, delay);
    };

    window.setInterval = (fn: Function, delay: number, ...args: any[]) => {
      const wrappedFn = () => {
        if (this.active) {
          fn.apply(this.windowProxy, args);
        }
      };
      return originalSetInterval.call(window, wrappedFn, delay);
    };

    // 2. 拦截全局变量
    window.globalThis = this.windowProxy;
  }

  // 修补Document
  private patchDocument(): void {
    // 1. 拦截DOM操作
    const originalCreateElement = document.createElement;
    const originalAppendChild = document.body.appendChild.bind(document.body);

    document.createElement = (tagName: string) => {
      const element = originalCreateElement.call(document, tagName);
      return element;
    };

    // 2. 拦截事件监听
    const originalAddEventListener = EventTarget.prototype.addEventListener;

    EventTarget.prototype.addEventListener = function(
      type: string,
      listener: EventListener,
      options?: AddEventListenerOptions
    ) {
      const wrappedListener = (event: Event) => {
        if (this === document || this === window) {
          // 全局事件,检查是否来自当前应用
          const target = event.target as HTMLElement;
          if (target && target.dataset.app === this.appName) {
            listener.call(this, event);
          }
        } else {
          listener.call(this, event);
        }
      };

      this.listeners.push({
        target: this,
        type,
        listener: wrappedListener
      });

      originalAddEventListener.call(this, type, wrappedListener, options);
    };
  }

  // 修补Storage
  private patchStorage(): void {
    // 1. 为每个应用创建独立的存储前缀
    const storagePrefix = `app:${this.appName}:`;

    const originalGetItem = Storage.prototype.getItem;
    const originalSetItem = Storage.prototype.setItem;
    const originalRemoveItem = Storage.prototype.removeItem;

    Storage.prototype.getItem = function(key: string) {
      return originalGetItem.call(this, `${storagePrefix}${key}`);
    };

    Storage.prototype.setItem = function(key: string, value: string) {
      return originalSetItem.call(this, `${storagePrefix}${key}`, value);
    };

    Storage.prototype.removeItem = function(key: string) {
      return originalRemoveItem.call(this, `${storagePrefix}${key}`);
    };
  }

  // 恢复Window
  private restoreWindow(): void {
    // 恢复原始方法
    // 这里需要保存原始引用
  }

  // 恢复Document
  private restoreDocument(): void {
    // 恢复原始方法
  }

  // 恢复Storage
  private restoreStorage(): void {
    // 恢复原始方法
  }

  // 清理事件监听器
  private clearListeners(): void {
    this.listeners.forEach(({ target, type, listener }) => {
      target.removeEventListener(type, listener);
    });
    this.listeners = [];
  }

  // 获取沙箱Window
  getWindow(): Window {
    return this.windowProxy;
  }

  // 执行代码
  executeCode(code: string): void {
    // 在沙箱中执行代码
    const sandboxFn = new Function('window', 'document', code);
    sandboxFn(this.windowProxy, document);
  }
}

3.2 样式隔离

// sandbox/StyleIsolation.ts - 样式隔离
export class StyleIsolation {
  private static instance: StyleIsolation;
  private styleSheets: Map<string, CSSStyleSheet> = new Map();
  private styleElements: Map<string, HTMLStyleElement> = new Map();

  private constructor() {}

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

  // 注入样式(带作用域)
  injectStyle(appName: string, cssText: string): void {
    // 1. 生成作用域前缀
    const scopePrefix = `[data-app="${appName}"]`;

    // 2. 处理CSS文本
    const scopedCSS = this.scopeCSS(cssText, scopePrefix);

    // 3. 创建样式元素
    const styleElement = document.createElement('style');
    styleElement.textContent = scopedCSS;
    styleElement.setAttribute('data-app', appName);

    // 4. 插入到文档
    document.head.appendChild(styleElement);

    // 5. 存储引用
    this.styleElements.set(appName, styleElement);
  }

  // 作用域CSS
  private scopeCSS(cssText: string, scopePrefix: string): string {
    // 1. 移除注释
    cssText = cssText.replace(//*[\s\S]*?*//g, '');

    // 2. 分割规则
    const rules = cssText.split('}');

    const scopedRules = rules.map(rule => {
      // 跳过空规则
      if (!rule.trim()) return '';

      // 提取选择器和声明
      const [selector, declarations] = rule.split('{');
      if (!selector || !declarations) return '';

      // 处理选择器
      const scopedSelector = this.scopeSelector(selector.trim(), scopePrefix);

      return `${scopedSelector} { ${declarations} }`;
    });

    return scopedRules.join('\n');
  }

  // 作用域选择器
  private scopeSelector(selector: string, scopePrefix: string): string {
    // 处理复杂选择器
    const selectors = selector.split(',');

    const scopedSelectors = selectors.map(sel => {
      const trimmed = sel.trim();

      // 跳过伪类和特殊选择器
      if (trimmed.startsWith('@')) return trimmed;
      if (trimmed.startsWith(':')) return trimmed;
      if (trimmed.startsWith('&')) return trimmed;

      // 处理后代选择器
      if (trimmed.includes(' ')) {
        const parts = trimmed.split(' ');
        const scopedParts = parts.map((part, index) => {
          if (index === 0) {
            return `${scopePrefix} ${part}`;
          }
          return part;
        });
        return scopedParts.join(' ');
      }

      // 处理子选择器
      if (trimmed.includes('>')) {
        const parts = trimmed.split('>');
        const scopedParts = parts.map((part, index) => {
          if (index === 0) {
            return `${scopePrefix} > ${part.trim()}`;
          }
          return part.trim();
        });
        return scopedParts.join(' > ');
      }

      // 简单选择器
      return `${scopePrefix} ${trimmed}`;
    });

    return selectors.join(', ');
  }

  // 移除样式
  removeStyle(appName: string): void {
    const styleElement = this.styleElements.get(appName);
    if (styleElement) {
      styleElement.remove();
      this.styleElements.delete(appName);
    }
  }

  // 样式隔离验证
  validateIsolation(): void {
    // 检查是否有样式泄漏
    const allStyles = document.querySelectorAll('style[data-app]');
    const appNames = new Set(
      Array.from(allStyles).map(el => el.getAttribute('data-app'))
    );

    // 检查样式冲突
    appNames.forEach(appName => {
      const appStyles = Array.from(allStyles).filter(
        el => el.getAttribute('data-app') === appName
      );

      // 检查选择器冲突
      appStyles.forEach(style => {
        const cssText = style.textContent || '';
        const selectors = this.extractSelectors(cssText);

        // 检查是否与其他应用冲突
        appNames.forEach(otherApp => {
          if (otherApp === appName) return;

          const otherStyles = Array.from(allStyles).filter(
            el => el.getAttribute('data-app') === otherApp
          );

          otherStyles.forEach(otherStyle => {
            const otherSelectors = this.extractSelectors(
              otherStyle.textContent || ''
            );

            // 查找冲突
            const conflicts = selectors.filter(sel =>
              otherSelectors.includes(sel)
            );

            if (conflicts.length > 0) {
              console.warn(`样式冲突: ${appName} 和 ${otherApp} 存在选择器冲突`);
            }
          });
        });
      });
    });
  }

  // 提取选择器
  private extractSelectors(cssText: string): string[] {
    const selectors: string[] = [];
    const rules = cssText.split('}');

    rules.forEach(rule => {
      const [selector] = rule.split('{');
      if (selector) {
        selectors.push(selector.trim());
      }
    });

    return selectors;
  }
}

四、应用通信机制:跨应用状态管理与事件总线

4.1 全局事件总线

// communication/EventBus.ts - 全局事件总线
export class EventBus {
  private static instance: EventBus;
  private listeners: Map<string, Set<EventListener>> = new Map();
  private history: Array<EventRecord> = [];
  private maxHistorySize = 100;

  private constructor() {}

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

  // 订阅事件
  subscribe<T>(
    eventName: string,
    listener: (data: T, context: EventContext) => void
  ): () => void {
    if (!this.listeners.has(eventName)) {
      this.listeners.set(eventName, new Set());
    }

    const wrappedListener = (data: T, context: EventContext) => {
      listener(data, context);
    };

    this.listeners.get(eventName)!.add(wrappedListener);

    // 返回取消订阅函数
    return () => {
      this.listeners.get(eventName)?.delete(wrappedListener);
    };
  }

  // 发布事件
  publish<T>(
    eventName: string,
    data: T,
    options?: PublishOptions
  ): void {
    const context: EventContext = {
      timestamp: Date.now(),
      source: options?.source || 'unknown',
      target: options?.target
    };

    // 1. 记录历史
    this.recordHistory(eventName, data, context);

    // 2. 获取监听器
    const eventListeners = this.listeners.get(eventName);
    if (!eventListeners) return;

    // 3. 分发事件
    eventListeners.forEach(listener => {
      // 检查目标过滤
      if (context.target && context.target !== listener.name) {
        return;
      }

      try {
        listener(data, context);
      } catch (error) {
        console.error(`事件处理错误: ${eventName}`, error);
      }
    });
  }

  // 记录历史
  private recordHistory<T>(
    eventName: string,
    data: T,
    context: EventContext
  ): void {
    this.history.push({
      eventName,
      data,
      context,
      timestamp: Date.now()
    });

    // 限制历史大小
    if (this.history.length > this.maxHistorySize) {
      this.history = this.history.slice(-this.maxHistorySize);
    }
  }

  // 获取事件历史
  getHistory(eventName?: string): EventRecord[] {
    if (eventName) {
      return this.history.filter(h => h.eventName === eventName);
    }
    return this.history;
  }

  // 清除历史
  clearHistory(): void {
    this.history = [];
  }

  // 获取所有事件名称
  getEventNames(): string[] {
    return Array.from(this.listeners.keys());
  }
}

// 使用示例
const eventBus = EventBus.getInstance();

// 订阅用户登录事件
const unsubscribe = eventBus.subscribe<UserInfo>(
  'user:login',
  (user, context) => {
    console.log(`用户登录: ${user.name}`, context);
  }
);

// 发布用户登录事件
eventBus.publish('user:login', {
  id: '123',
  name: '张三',
  email: 'zhangsan@example.com'
}, {
  source: 'auth_app',
  target: 'user_app'
});

// 取消订阅
unsubscribe();

4.2 跨应用状态管理

// communication/GlobalState.ts - 全局状态管理
export class GlobalState {
  private static instance: GlobalState;
  private state: Map<string, unknown> = new Map();
  private subscribers: Map<string, Set<StateListener>> = new Map();
  private storage: StorageAdapter;

  private constructor() {
    this.storage = new StorageAdapter('global-state');
    this.initializeFromStorage();
  }

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

  // 初始化存储
  private initializeFromStorage(): void {
    const storedState = this.storage.getAll();
    Object.entries(storedState).forEach(([key, value]) => {
      this.state.set(key, value);
    });
  }

  // 获取状态
  get<T>(key: string): T | undefined {
    return this.state.get(key) as T;
  }

  // 设置状态
  set<T>(key: string, value: T, options?: SetOptions): void {
    // 1. 检查是否变化
    const oldValue = this.state.get(key);
    if (oldValue === value && !options?.force) {
      return;
    }

    // 2. 更新状态
    this.state.set(key, value);

    // 3. 持久化
    if (options?.persist) {
      this.storage.set(key, value);
    }

    // 4. 通知订阅者
    this.notifySubscribers(key, value, oldValue);
  }

  // 订阅状态变化
  subscribe<T>(
    key: string,
    listener: (newValue: T, oldValue: T) => void
  ): () => void {
    if (!this.subscribers.has(key)) {
      this.subscribers.set(key, new Set());
    }

    this.subscribers.get(key)!.add(listener);

    return () => {
      this.subscribers.get(key)?.delete(listener);
    };
  }

  // 通知订阅者
  private notifySubscribers<T>(
    key: string,
    newValue: T,
    oldValue: T
  ): void {
    const subscribers = this.subscribers.get(key);
    if (!subscribers) return;

    subscribers.forEach(listener => {
      try {
        listener(newValue, oldValue);
      } catch (error) {
        console.error(`状态订阅者错误: ${key}`, error);
      }
    });
  }

  // 批量更新
  batchUpdate(updates: Record<string, unknown>): void {
    Object.entries(updates).forEach(([key, value]) => {
      this.set(key, value);
    });
  }

  // 获取所有状态
  getAll(): Record<string, unknown> {
    return Object.fromEntries(this.state);
  }

  // 清除状态
  clear(key?: string): void {
    if (key) {
      this.state.delete(key);
      this.storage.remove(key);
    } else {
      this.state.clear();
      this.storage.clear();
    }
  }
}

// 存储适配器
class StorageAdapter {
  constructor(private readonly prefix: string) {}

  get(key: string): unknown {
    const value = localStorage.getItem(`${this.prefix}:${key}`);
    return value ? JSON.parse(value) : undefined;
  }

  set(key: string, value: unknown): void {
    localStorage.setItem(`${this.prefix}:${key}`, JSON.stringify(value));
  }

  getAll(): Record<string, unknown> {
    const result = {};
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key?.startsWith(this.prefix)) {
        const actualKey = key.slice(this.prefix.length + 1);
        result[actualKey] = this.get(actualKey);
      }
    }
    return result;
  }

  remove(key: string): void {
    localStorage.removeItem(`${this.prefix}:${key}`);
  }

  clear(): void {
    const keysToRemove: string[] = [];
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key?.startsWith(this.prefix)) {
        keysToRemove.push(key);
      }
    }
    keysToRemove.forEach(key => localStorage.removeItem(key));
  }
}

// 使用示例
const globalState = GlobalState.getInstance();

// 设置全局状态
globalState.set('user', {
  id: '123',
  name: '张三',
  role: 'admin'
}, { persist: true });

// 订阅状态变化
const unsubscribe = globalState.subscribe<UserInfo>(
  'user',
  (newUser, oldUser) => {
    console.log('用户状态变化:', oldUser, '->', newUser);
  }
);

// 在React中使用
export const useGlobalState = <T>(key: string): [T | undefined, (value: T) => void] => {
  const [value, setValue] = useState<T | undefined>(
    () => globalState.get<T>(key)
  );

  useEffect(() => {
    const unsubscribe = globalState.subscribe<T>(
      key,
      (newValue) => setValue(newValue)
    );

    return unsubscribe;
  }, [key]);

  const setGlobalValue = useCallback((newValue: T) => {
    globalState.set(key, newValue);
  }, [key]);

  return [value, setGlobalValue];
};

五、部署与监控:微前端部署策略与可观测性

5.1 部署策略

// deployment/DeploymentStrategy.ts - 微前端部署策略
export class DeploymentStrategy {
  // 独立部署
  static independentDeployment(): void {
    // 每个子应用独立部署到CDN
    // 通过版本号管理
    const deploymentConfig = {
      user_app: {
        version: '1.2.3',
        entry: 'https://cdn.example.com/user-app/1.2.3/remoteEntry.js',
        assets: 'https://cdn.example.com/user-app/1.2.3/assets/'
      },
      order_app: {
        version: '2.0.1',
        entry: 'https://cdn.example.com/order-app/2.0.1/remoteEntry.js',
        assets: 'https://cdn.example.com/order-app/2.0.1/assets/'
      }
    };

    // 生成版本清单
    this.generateVersionManifest(deploymentConfig);
  }

  // 统一部署
  static unifiedDeployment(): void {
    // 所有应用打包到一起部署
    // 简化运维,但失去独立部署的优势
  }

  // 混合部署
  static hybridDeployment(): void {
    // 核心应用统一部署,扩展应用独立部署
  }

  // 生成版本清单
  private static generateVersionManifest(
    config: Record<string, DeploymentConfig>
  ): void {
    const manifest = {
      apps: config,
      timestamp: Date.now(),
      version: '1.0.0'
    };

    // 保存到配置文件
    localStorage.setItem('app-manifest', JSON.stringify(manifest));
  }

  // 版本回滚
  static rollback(appName: string, version: string): void {
    // 1. 获取版本清单
    const manifest = JSON.parse(
      localStorage.getItem('app-manifest') || '{}'
    );

    // 2. 更新版本
    if (manifest.apps && manifest.apps[appName]) {
      manifest.apps[appName].version = version;
      manifest.apps[appName].entry =
        `https://cdn.example.com/${appName}/${version}/remoteEntry.js`;

      // 3. 保存更新后的清单
      localStorage.setItem('app-manifest', JSON.stringify(manifest));

      // 4. 通知主应用重新加载
      window.dispatchEvent(new CustomEvent('app:version-changed', {
        detail: { appName, version }
      }));
    }
  }

  // 灰度发布
  static canaryDeployment(
    appName: string,
    canaryVersion: string,
    percentage: number
  ): void {
    // 1. 获取用户标识
    const userId = this.getUserId();

    // 2. 计算哈希决定是否走灰度
    const hash = this.hashString(userId);
    const shouldUseCanary = (hash % 100) < percentage;

    // 3. 根据结果选择版本
    const version = shouldUseCanary ? canaryVersion : 'stable';

    // 4. 加载对应版本
    this.loadVersion(appName, version);
  }

  private static getUserId(): string {
    return localStorage.getItem('user-id') || 'anonymous';
  }

  private static hashString(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;
    }
    return Math.abs(hash);
  }

  private static loadVersion(appName: string, version: string): void {
    // 动态加载指定版本
    const script = document.createElement('script');
    script.src = `https://cdn.example.com/${appName}/${version}/remoteEntry.js`;
    document.head.appendChild(script);
  }
}

5.2 可观测性

// observability/Observability.ts - 微前端可观测性
export class MicroFrontendObservability {
  private static instance: MicroFrontendObservability;
  private metrics: Map<string, MetricValue> = new Map();
  private traces: Array<Trace> = [];
  private logs: Array<LogEntry> = [];

  private constructor() {
    this.initializeCollectors();
  }

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

  // 初始化收集器
  private initializeCollectors(): void {
    // 1. 性能收集
    this.collectPerformanceMetrics();

    // 2. 错误收集
    this.collectErrors();

    // 3. 用户行为收集
    this.collectUserBehavior();

    // 4. 资源加载收集
    this.collectResourceLoading();
  }

  // 收集性能指标
  private collectPerformanceMetrics(): void {
    // 收集各子应用的性能指标
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach(entry => {
        // 识别来自哪个子应用
        const appName = this.identifyApp(entry.name);

        this.metrics.set(`${appName}:${entry.entryType}`, {
          appName,
          metric: entry.entryType,
          value: entry.startTime,
          timestamp: Date.now()
        });
      });
    });

    observer.observe({ entryTypes: ['navigation', 'resource', 'paint'] });
  }

  // 收集错误
  private collectErrors(): void {
    // 1. 全局错误
    window.addEventListener('error', (event) => {
      const appName = this.identifyApp(event.filename);

      this.logs.push({
        appName,
        level: 'error',
        message: event.message,
        stack: event.error?.stack,
        timestamp: Date.now()
      });
    });

    // 2. Promise错误
    window.addEventListener('unhandledrejection', (event) => {
      this.logs.push({
        appName: 'unknown',
        level: 'error',
        message: event.reason?.message || 'Unhandled Promise Rejection',
        stack: event.reason?.stack,
        timestamp: Date.now()
      });
    });

    // 3. 资源加载错误
    document.addEventListener('error', (event) => {
      const target = event.target as HTMLElement;
      if (target && target.src) {
        const appName = this.identifyApp(target.src);

        this.logs.push({
          appName,
          level: 'error',
          message: `资源加载失败: ${target.src}`,
          timestamp: Date.now()
        });
      }
    }, true);
  }

  // 收集用户行为
  private collectUserBehavior(): void {