传统微前端中,不同子应用彼此完全独立,通信主要通过事件、消息总线等方式。随着模块联邦(Module Federation)技术的发展,可以在多个微前端应用间动态共享模块,实现模块级别的调用和通信,打破边界。
利用 Webpack Module Federation 机制,多个应用间共享和暴露模块(函数、组件、状态管理实例等)。
通过导入其他应用暴露的模块,直接调用对方的功能或状态,达成通信目的。
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'], // 共享依赖
}),
],
};
在主应用或其他子应用里,动态加载这个暴露的模块,并使用它完成通信。
// 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
事件,并展示消息。
假设前端框架用的是 React,remote-app 目录结构:
remote-app/
src/
eventBus.js
App.jsx
index.js
webpack.config.js
package.json
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'],
}),
],
};
import mitt from 'mitt';
const eventBus = mitt();
export default eventBus;
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 || '暂无消息'}
);
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( );
host-app 目录结构类似:
host-app/
src/
index.js
webpack.config.js
package.json
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'],
}),
],
};
// 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);
});
启动 remote-app(端口3001)
启动 host-app(端口3000)
host-app 会动态加载 remote-app 的事件总线模块
remote-app React 组件会监听 host-to-remote
事件,5秒后 host-app 发送消息,remote-app 显示消息
remote-app 3秒后发消息给 host-app,host-app 控制台打印消息
模块级别通信,直接调用函数、操作状态,不必通过事件转发或序列化。
代码复用性强,减少重复实现。
易于维护,接口清晰。
支持多种通信模式(事件总线、状态管理、工具函数等)。
依赖构建配置复杂,需统一 webpack Module Federation 配置。
应用间耦合度稍高,不适合完全独立解耦的场景。
需要解决共享依赖版本冲突问题(React、ReactDOM 等)。
只能用于同一浏览器上下文内(单页面应用或 iframe 内嵌需额外配置)。
多个微前端应用间需要共享状态或业务逻辑。
需要高度复用组件或工具库。
使用 Module Federation 技术栈。
微前端加载在同一页面,且能共享同一个 JS 运行时环境。