微前端MFE: 通过共享模块通信(模块联邦Module Federation)

背景

传统微前端中,不同子应用彼此完全独立,通信主要通过事件、消息总线等方式。随着模块联邦(Module Federation)技术的发展,可以在多个微前端应用间动态共享模块,实现模块级别的调用和通信,打破边界。


核心思想

  • 利用 Webpack Module Federation 机制,多个应用间共享和暴露模块(函数、组件、状态管理实例等)。

  • 通过导入其他应用暴露的模块,直接调用对方的功能或状态,达成通信目的。


具体实现步骤

1. 应用间暴露接口模块

remote 应用通过 Module Federation 配置,暴露一个模块,比如状态管理实例、工具函数或事件总线。

// webpack.config.js (remote 应用)
module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './EventBus': './src/eventBus.js', // 暴露一个事件总线模块
        './sharedState': './src/sharedState.js' // 也可以暴露状态实例
      },
      shared: ['react', 'react-dom'], // 共享依赖
    }),
  ],
};
2. host 应用动态加载并调用 remote 模块

在主应用或其他子应用里,动态加载这个暴露的模块,并使用它完成通信。

// host 应用中,动态加载 remote 应用暴露的模块
import('remoteApp/EventBus').then(eventBusModule => {
  const eventBus = eventBusModule.default;

  // 订阅事件
  eventBus.on('some-event', (data) => {
    console.log('收到 remote 应用消息:', data);
  });

  // 触发事件,通知 remote 应用
  eventBus.emit('host-event', { msg: 'Hello from host' });
});

共享状态的例子

比如两个应用共享同一个 sharedState 实例(可以是 Redux store、MobX store 或自定义状态管理)。

// remote 应用 sharedState.js
import { makeAutoObservable } from 'mobx';

class SharedState {
  data = 0;
  constructor() {
    makeAutoObservable(this);
  }
  setData(value) {
    this.data = value;
  }
}

const sharedState = new SharedState();
export default sharedState;

host 应用调用:

import('remoteApp/sharedState').then(sharedState => {
  console.log(sharedState.data);
  sharedState.setData(42);
});

完整示例

基于 Module Federation 共享事件总线 ,演示 remote-app 如何在组件内监听 host-to-remote 事件,并展示消息。

1. remote-app 结构和代码

假设前端框架用的是 React,remote-app 目录结构:

remote-app/
  src/
    eventBus.js
    App.jsx
    index.js
  webpack.config.js
  package.json

1.1 webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    port: 3001,
    historyApiFallback: true,
  },
  output: {
    publicPath: 'auto',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './eventBus': './src/eventBus.js',
      },
      shared: ['react', 'react-dom', 'mitt'],
    }),
  ],
};

1.2 src/eventBus.js
import mitt from 'mitt';

const eventBus = mitt();

export default eventBus;

1.3 src/App.jsx
import React, { useEffect, useState } from 'react';
import eventBus from './eventBus';

export default function App() {
  const [message, setMessage] = useState('');

  useEffect(() => {
    // 监听 host 应用发来的事件
    const handler = (data) => {
      setMessage(data.msg);
      console.log('remote-app 收到 host 发送的消息:', data);
    };
    eventBus.on('host-to-remote', handler);

    // 清理订阅
    return () => {
      eventBus.off('host-to-remote', handler);
    };
  }, []);

  // 远程主动发送消息给 host
  useEffect(() => {
    setTimeout(() => {
      eventBus.emit('remote-to-host', { msg: 'Hello from remote-app React component!' });
    }, 3000);
  }, []);

  return (
    

Remote App (React)

收到 host 的消息: {message || '暂无消息'}

); }

1.4 src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();

2. host-app 结构和代码

host-app 目录结构类似:

host-app/
  src/
    index.js
  webpack.config.js
  package.json

2.1 webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    port: 3000,
    historyApiFallback: true,
  },
  output: {
    publicPath: 'auto',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: ['mitt'],
    }),
  ],
};

2.2 src/index.js
// host 应用中动态加载 remote-app 暴露的事件总线
import('remoteApp/eventBus').then((module) => {
  const eventBus = module.default;

  // 监听 remote 发来的消息
  eventBus.on('remote-to-host', (data) => {
    console.log('host-app 收到 remote 发送的消息:', data);
  });

  // 发送消息给 remote 应用
  setTimeout(() => {
    eventBus.emit('host-to-remote', { msg: 'Hi from host-app after 5 seconds!' });
  }, 5000);
});

3. 启动顺序
  1. 启动 remote-app(端口3001)

  2. 启动 host-app(端口3000)

  3. host-app 会动态加载 remote-app 的事件总线模块

  4. remote-app React 组件会监听 host-to-remote 事件,5秒后 host-app 发送消息,remote-app 显示消息

  5. remote-app 3秒后发消息给 host-app,host-app 控制台打印消息


优点

  • 模块级别通信,直接调用函数、操作状态,不必通过事件转发或序列化。

  • 代码复用性强,减少重复实现。

  • 易于维护,接口清晰。

  • 支持多种通信模式(事件总线、状态管理、工具函数等)。


缺点/挑战

  • 依赖构建配置复杂,需统一 webpack Module Federation 配置。

  • 应用间耦合度稍高,不适合完全独立解耦的场景。

  • 需要解决共享依赖版本冲突问题(React、ReactDOM 等)。

  • 只能用于同一浏览器上下文内(单页面应用或 iframe 内嵌需额外配置)。


适用场景

  • 多个微前端应用间需要共享状态或业务逻辑。

  • 需要高度复用组件或工具库。

  • 使用 Module Federation 技术栈。

  • 微前端加载在同一页面,且能共享同一个 JS 运行时环境。

你可能感兴趣的:(前端)