Module Federation

下面,我们来系统的梳理关于 微前端:Module Federation 的基本知识点:


一、微前端与 Module Federation 概述

1.1 什么是微前端?

微前端是一种将前端应用分解为更小、更简单、可以独立开发、测试和部署的架构模式。

1.2 Module Federation 的核心价值

特性 传统微前端 Module Federation
部署独立性 ✅ ✅
技术栈无关 ✅ ✅
运行时集成 ❌ ✅
共享依赖 困难 内置支持
开发体验 复杂 优秀

1.3 架构对比

传统微前端 构建时集成 iframe 隔离 重复依赖 Module Federation 运行时集成 依赖共享 动态加载

二、Module Federation 核心概念

2.1 基本术语

  • Host(主机):消费其他应用模块的应用
  • Remote(远程):被其他应用消费的应用
  • Exposes(暴露):Remote 应用对外提供的模块
  • Remotes(远程模块):Host 应用从 Remote 消费的模块
  • Shared(共享):应用间共享的依赖

2.2 工作原理

Host 应用 Remote 应用 浏览器 加载 Host 应用 执行 Host 代码 动态请求 Remote 模块 返回 Remote 模块 集成 Remote 模块 Host 应用 Remote 应用 浏览器

三、Webpack 5 Module Federation 配置

3.1 基础配置结构

javascript 复制代码
// webpack.config.js (Host 应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, eager: true },
        'react-dom': { singleton: true, eager: true },
      },
    }),
  ],
};
javascript 复制代码
// webpack.config.js (Remote 应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './Header': './src/components/Header',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

3.2 完整配置示例

javascript 复制代码
// host-app/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        remoteApp: `remoteApp@${process.env.REMOTE_APP_URL || 'http://localhost:3001'}/remoteEntry.js`,
        authApp: `authApp@${process.env.AUTH_APP_URL || 'http://localhost:3002'}/remoteEntry.js`,
      },
      shared: {
        ...deps,
        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'],
        },
      },
    }),
  ],
};

四、React 应用集成实战

4.1 Host 应用配置

javascript 复制代码
// src/bootstrap.js (Host 入口)
import('./bootstrap');

// src/App.js
import React, { Suspense } from 'react';
import ErrorBoundary from './components/ErrorBoundary';

// 动态导入 Remote 组件
const RemoteButton = React.lazy(() => import('remoteApp/Button'));
const RemoteHeader = React.lazy(() => import('remoteApp/Header'));

function App() {
  return (
    <div>
      <ErrorBoundary>
        <Suspense fallback={<div>Loading Remote Components...</div>}>
          <RemoteHeader title="Host Application" />
        </Suspense>
      </ErrorBoundary>
      
      <main>
        <h1>Host App Content</h1>
        <ErrorBoundary>
          <Suspense fallback={<div>Loading Button...</div>}>
            <RemoteButton onClick={() => console.log('Clicked from remote')}>
              Remote Button
            </RemoteButton>
          </Suspense>
        </ErrorBoundary>
      </main>
    </div>
  );
}

export default App;

4.2 Remote 应用组件

javascript 复制代码
// remote-app/src/components/Button.js
import React from 'react';
import './Button.css';

const Button = ({ children, onClick, variant = 'primary' }) => {
  return (
    <button 
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

export default Button;

// remote-app/src/components/Header.js
import React from 'react';

const Header = ({ title }) => {
  return (
    <header style={{ padding: '1rem', background: '#f0f0f0' }}>
      <h1>{title}</h1>
      <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
      </nav>
    </header>
  );
};

export default Header;

4.3 错误边界组件

javascript 复制代码
// src/components/ErrorBoundary.js
import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Remote component error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '1rem', border: '1px solid #ff6b6b', color: '#ff6b6b' }}>
          <h3>Remote Component Failed to Load</h3>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Retry
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

五、路由集成与状态管理

5.1 跨应用路由配置

javascript 复制代码
// host-app/src/App.js
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

const RemoteHome = React.lazy(() => import('remoteApp/HomePage'));
const RemoteAbout = React.lazy(() => import('remoteApp/AboutPage'));

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> | 
        <Link to="/about">About</Link> |
        <Link to="/remote">Remote Home</Link> |
        <Link to="/remote/about">Remote About</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route 
          path="/remote/*" 
          element={
            <Suspense fallback={<div>Loading Remote App...</div>}>
              <RemoteAppRouter />
            </Suspense>
          } 
        />
      </Routes>
    </Router>
  );
}

// Remote 应用的路由包装器
function RemoteAppRouter() {
  return (
    <Routes>
      <Route path="/" element={<RemoteHome />} />
      <Route path="/about" element={<RemoteAbout />} />
    </Routes>
  );
}

5.2 状态管理集成

javascript 复制代码
// shared-state/src/store.js (共享状态库)
import { createStore } from 'redux';

const initialState = {
  user: null,
  cart: [],
  theme: 'light'
};

function rootReducer(state = initialState, action) {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload };
    case 'ADD_TO_CART':
      return { ...state, cart: [...state.cart, action.payload] };
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    default:
      return state;
  }
}

export const store = createStore(rootReducer);

// 在各个应用的 webpack 配置中共享
shared: {
  'shared-state': {
    singleton: true,
    requiredVersion: require('../shared-state/package.json').version
  }
}

六、高级配置与优化

6.1 动态 Remote 配置

javascript 复制代码
// src/utils/dynamicRemotes.js
class DynamicRemoteLoader {
  constructor() {
    this.remotes = new Map();
  }

  async loadRemote(remoteName, remoteUrl) {
    if (this.remotes.has(remoteName)) {
      return this.remotes.get(remoteName);
    }

    try {
      await this.__loadRemoteEntry(remoteUrl);
      const container = window[remoteName];
      await container.init(__webpack_share_scopes__.default);
      this.remotes.set(remoteName, container);
      return container;
    } catch (error) {
      console.error(`Failed to load remote ${remoteName}:`, error);
      throw error;
    }
  }

  async __loadRemoteEntry(url) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }
}

export const remoteLoader = new DynamicRemoteLoader();

// 使用动态 Remote
const loadRemoteComponent = async (remoteName, modulePath) => {
  const remoteConfig = await fetch('/api/remotes-config').then(r => r.json());
  const remoteUrl = remoteConfig[remoteName];
  
  await remoteLoader.loadRemote(remoteName, remoteUrl);
  const container = window[remoteName];
  const factory = await container.get(modulePath);
  return factory();
};

6.2 性能优化配置

javascript 复制代码
// webpack.config.js 优化配置
new ModuleFederationPlugin({
  name: 'hostApp',
  remotes: {
    remoteApp: `promise new Promise(resolve => {
      const remoteUrl = new URL('./remoteEntry.js', import.meta.url);
      const script = document.createElement('script');
      script.src = remoteUrl;
      script.onload = () => {
        const proxy = {
          get: (request) => window.remoteApp.get(request),
          init: (arg) => {
            try {
              return window.remoteApp.init(arg);
            } catch (e) {
              console.log('Remote container already initialized');
            }
          }
        };
        resolve(proxy);
      };
      document.head.appendChild(script);
    })`,
  },
  shared: {
    react: {
      singleton: true,
      requiredVersion: deps.react,
      eager: false, // 延迟加载
    },
    'react-dom': {
      singleton: true,
      requiredVersion: deps['react-dom'],
      eager: false,
    },
  },
}),

七、开发环境与工具链

7.1 开发服务器配置

javascript 复制代码
// host-app/webpack.config.js
devServer: {
  port: 3000,
  historyApiFallback: true,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
    'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
  },
  allowedHosts: 'all',
},

// remote-app/webpack.config.js
devServer: {
  port: 3001,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
    'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
  },
},

7.2 TypeScript 支持

typescript 复制代码
// types/federation.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.css';

declare module 'remoteApp/Button' {
  import { ComponentType } from 'react';
  interface ButtonProps {
    children: React.ReactNode;
    onClick?: () => void;
    variant?: 'primary' | 'secondary';
  }
  const Button: ComponentType<ButtonProps>;
  export default Button;
}

declare module 'remoteApp/Header' {
  import { ComponentType } from 'react';
  interface HeaderProps {
    title: string;
  }
  const Header: ComponentType<HeaderProps>;
  export default Header;
}

八、测试策略

8.1 单元测试配置

javascript 复制代码
// jest.config.js
module.exports = {
  moduleNameMapper: {
    '^remoteApp/(.*)$': '<rootDir>/__mocks__/remoteApp/$1',
  },
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};

// __mocks__/remoteApp/Button.js
import React from 'react';
const MockButton = ({ children, onClick }) => (
  <button onClick={onClick} data-testid="mock-button">
    {children}
  </button>
);
export default MockButton;

8.2 集成测试

javascript 复制代码
// src/__tests__/App.integration.js
import { render, screen, waitFor } from '@testing-library/react';
import * as RemoteApp from 'remoteApp/Button';

jest.mock('remoteApp/Button', () => ({
  __esModule: true,
  default: jest.fn(() => <button>Mock Remote Button</button>)
}));

describe('App Integration', () => {
  it('should load remote component', async () => {
    render(<App />);
    
    await waitFor(() => {
      expect(screen.getByText('Mock Remote Button')).toBeInTheDocument();
    });
    
    expect(RemoteApp.default).toHaveBeenCalled();
  });
});

九、部署与生产环境

9.1 生产环境配置

javascript 复制代码
// 环境特定的配置
const getRemoteUrl = (appName) => {
  const baseUrl = process.env.NODE_ENV === 'production' 
    ? 'https://cdn.yourcompany.com'
    : 'http://localhost';
  
  const ports = {
    remoteApp: process.env.NODE_ENV === 'production' ? '' : ':3001',
    authApp: process.env.NODE_ENV === 'production' ? '' : ':3002',
  };
  
  return `${baseUrl}${ports[appName]}/${appName}/remoteEntry.js`;
};

new ModuleFederationPlugin({
  remotes: {
    remoteApp: `remoteApp@${getRemoteUrl('remoteApp')}`,
    authApp: `authApp@${getRemoteUrl('authApp')}`,
  },
}),

9.2 CDN 部署策略

bash 复制代码
# 部署脚本示例
#!/bin/bash

# 构建各个应用
cd remote-app && npm run build && cd ..
cd host-app && npm run build && cd ..

# 上传到 CDN
aws s3 sync remote-app/dist/ s3://your-bucket/remote-app/ --delete
aws s3 sync host-app/dist/ s3://your-bucket/host-app/ --delete

# 刷新 CDN 缓存
aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/remote-app/*" "/host-app/*"

十、监控与错误处理

10.1 性能监控

javascript 复制代码
// src/utils/performanceMonitor.js
export const monitorRemoteLoading = () => {
  const originalGet = window.webpackChunkhostApp.get;
  
  window.webpackChunkhostApp.get = function(remoteName) {
    const startTime = performance.now();
    
    return originalGet.apply(this, arguments).then(container => {
      const loadTime = performance.now() - startTime;
      console.log(`Remote ${remoteName} loaded in ${loadTime}ms`);
      
      // 发送到监控系统
      sendMetricsToMonitoring({
        event: 'remote_loaded',
        remote: remoteName,
        duration: loadTime,
        timestamp: Date.now()
      });
      
      return container;
    });
  };
};

// 在应用启动时调用
monitorRemoteLoading();

10.2 错误追踪

javascript 复制代码
// src/utils/errorHandler.js
export const setupErrorHandling = () => {
  // 捕获远程模块加载错误
  window.addEventListener('error', (event) => {
    if (event.filename && event.filename.includes('remoteEntry.js')) {
      const remoteName = event.filename.split('/')[2];
      logError({
        type: 'REMOTE_LOAD_ERROR',
        remote: remoteName,
        message: event.error?.message,
        stack: event.error?.stack
      });
    }
  });

  // 捕获未处理的 Promise rejection
  window.addEventListener('unhandledrejection', (event) => {
    if (event.reason && event.reason.message.includes('Remote')) {
      logError({
        type: 'REMOTE_MODULE_ERROR',
        message: event.reason.message,
        stack: event.reason.stack
      });
    }
  });
};

// 初始化错误处理
setupErrorHandling();

十一、安全最佳实践

11.1 CSP 配置

html 复制代码
<!-- public/index.html -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' http://localhost:3001 http://localhost:3002;
               style-src 'self' 'unsafe-inline';
               connect-src 'self' http://localhost:3001 http://localhost:3002;">

11.2 安全头配置

javascript 复制代码
// Express 服务器配置
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "trusted-cdn.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      connectSrc: ["'self'", "api.yourdomain.com"],
    },
  },
  crossOriginEmbedderPolicy: false,
}));

十二、常见问题与解决方案

12.1 版本冲突解决

javascript 复制代码
// webpack.config.js
shared: {
  react: {
    singleton: true,
    requiredVersion: '^18.0.0',
    strictVersion: true,
    eager: true,
  },
  'react-dom': {
    singleton: true,
    requiredVersion: '^18.0.0',
    strictVersion: true,
    eager: true,
  },
}

12.2 循环依赖处理

javascript 复制代码
// 使用异步初始化避免循环依赖
new ModuleFederationPlugin({
  name: 'app1',
  exposes: { './Component': './src/Component' },
  shared: {
    react: { singleton: true, eager: false },
  },
}),

十三、总结

Module Federation 为微前端架构提供了强大的运行时模块共享能力,具有以下优势:

核心价值

  1. 真正的独立部署:各个应用可以独立开发、测试和部署
  2. 运行时集成:不需要重新构建整个应用
  3. 依赖共享:避免重复加载相同的依赖
  4. 技术栈无关:支持不同框架的应用集成
  5. 渐进式采用:可以逐步迁移到微前端架构

适用场景

  • 大型企业应用:多个团队协作开发的大型项目
  • 遗留系统迁移:逐步替换老旧系统
  • 多框架共存:React、Vue、Angular 混合开发
  • 独立部署需求:不同功能模块需要独立发布

决策考虑

项目需求 评估因素 团队结构 技术栈多样性 部署独立性需求 性能要求 多个独立团队 多种技术栈 独立部署需求 高性能要求 适合 Module Federation

相关推荐
晓得迷路了30 分钟前
栗子前端技术周刊第 149 期 - Vite+ 1.0、Preact 11、Next.js 16.4...
前端·javascript·react.js
song50142 分钟前
React Native for OpenHarmony 实战:三方库 react-native-popup-menu 的鸿蒙化适配指南
css·深度学习·react native·react.js·transformer·harmonyos
haluhalu.1 小时前
Agent = LLM + 上下文 + 工具(含观察 / 动作空间、ReAct 循环)
前端·javascript·人工智能·python·react.js·机器学习·个人开发
weixin_4481199410 小时前
Datawhale Easy Data × AI:构建知识与记忆驱动的 Agent笔记8
人工智能·笔记·react.js
To_OC11 小时前
同样是让 AI 写全栈,为什么别人一句话就够了
前端·前端框架·next.js
EatFan14 小时前
React 项目踩坑实录:useEffect 闭包陷阱、Context 性能陷阱与 React 18 升级避坑排查手册
前端·javascript·react.js·react·react hooks·useeffect·闭包陷阱
梦想的颜色18 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
知野小兔18 小时前
对比react钩子函数
react.js
星恒随风1 天前
Linux基础IO万字详解:从文件描述符fd到重定向、FILE缓冲机制及MiniShell升级
linux·运维·笔记·学习·状态模式
WayneX2 天前
开源 Vue 3 组件库 Morya UI:把组件、文档、AI 工具链一起做进一个包
前端·vue.js·前端框架