一个用于运行时动态加载远程 React 组件的工具库,支持多版本共存、CDN 故障转移和完整的模块生命周期管理。
这是一个 pnpm monorepo,当前包含:
packages/remote-reload-utils:核心运行时加载工具库(已发布 npm)packages/vue-adapter:Vue 3 适配层(在 Vue 中加载 React 远程组件)packages/react-adapter:React 适配层(简化的 React 组件封装)apps/test-mf-unpkg:远程组件示例应用(React)apps/host-react18-remote:宿主示例应用(React)apps/host-vue3-remote:宿主示例应用(Vue 3)
- 🚀 运行时动态加载 - 无需重新构建即可加载远程组件
- 📦 多版本支持 - 支持同一包的多个版本同时运行
- 🔄 CDN 故障转移 - 自动在多个 CDN 之间切换,提高可用性
- 💾 智能缓存 - 内置版本缓存机制,减少网络请求
- 🎯 TypeScript 支持 - 完整的类型定义
- ⚛️ React 友好 - 专为 React 组件 Module Federation 设计
- 🔧 可扩展 - 插件系统支持自定义扩展
- 📊 性能优化 - 预加载、卸载、健康检查
- 🔗 事件总线 - 跨模块通信支持
- ✅ 质量保障 - 155+ 单元测试,高覆盖率
npm install remote-reload-utils # 或 pnpm add remote-reload-utils # 或 yarn add remote-reload-utilsimport { loadRemoteMultiVersion } from 'remote-reload-utils'; async function loadRemoteComponent() { const { scopeName, mf } = await loadRemoteMultiVersion({ name: 'my-remote-app', pkg: '@myorg/remote-app', version: '1.0.0', }); const mod = await mf.loadRemote(`${scopeName}/Button`); return mod.default; }import { lazyRemote } from 'remote-reload-utils'; import { Suspense } from 'react'; const RemoteDashboard = lazyRemote({ pkg: '@myorg/remote-app', version: '^1.0.0', moduleName: 'Dashboard', scopeName: 'myorg', maxRetries: 3, retryDelay: 1000, }); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <RemoteDashboard userId={123} /> </Suspense> ); }import { RemoteModuleProvider } from '@react-mf-lib/react-adapter'; function App() { return ( <RemoteModuleProvider pkg="@myorg/remote-app" version="^1.0.0" moduleName="Dashboard" scopeName="myorg" loadingFallback={<Spinner />} errorFallback={(error, reset) => ( <div> <p>加载失败:{error.message}</p> <button onClick={reset}>重试</button> </div> )} /> ); }动态加载远程模块,支持多版本和故障转移。
import { loadRemoteMultiVersion } from 'remote-reload-utils'; const { scopeName, mf } = await loadRemoteMultiVersion(options, plugins);参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
name | string | ✅ | - | Module Federation 的名称 |
pkg | string | ✅ | - | npm 包名 |
version | string | ❌ | 'latest' | 版本号或 'latest' |
retries | number | ❌ | 3 | 每个 CDN 的重试次数 |
delay | number | ❌ | 1000 | 重试间隔(毫秒) |
localFallback | string | ❌ | - | 本地兜底 URL |
cacheTTL | number | ❌ | 86400000 | 缓存时间(毫秒) |
revalidate | boolean | ❌ | true | 异步重新验证最新版本 |
shared | Record<string, any> | ❌ | - | 自定义共享模块配置 |
返回值: Promise<{ scopeName: string, mf: ModuleFederationInstance }>
MF 实例方法:
const { scopeName, mf } = await loadRemoteMultiVersion(options); // 加载暴露的模块 const module = await mf.loadRemote(`${scopeName}/Button`); const Button = module.default;import { preloadRemote, preloadRemoteList } from 'remote-reload-utils'; // 预加载单个模块 await preloadRemote({ pkg: '@myorg/remote-app', version: '1.0.0', name: 'myorg', priority: 'idle', // 'idle' | 'high' force: false, }); // 预加载多个模块 await preloadRemoteList([ { pkg: '@myorg/app1', version: '1.0.0', name: 'app1' }, { pkg: '@myorg/app2', version: '2.0.0', name: 'app2' }, ], (loaded, total) => { console.log(`Progress: ${loaded}/${total}`); });import { unloadRemote, unloadAll } from 'remote-reload-utils'; // 卸载特定模块 await unloadRemote({ name: 'myorg', pkg: '@myorg/remote-app', version: '1.0.0', clearCache: true, }); // 卸载所有模块 await unloadAll(true); // true = 清除所有缓存import { checkRemoteHealth, getRemoteHealthReport } from 'remote-reload-utils'; // 检查单个远程模块健康状态 const health = await checkRemoteHealth({ pkg: '@myorg/remote-app', version: '1.0.0', name: 'myorg', }); console.log(health.status); // 'healthy' | 'degraded' | 'unhealthy' console.log(health.latency); // 延迟(毫秒) // 生成健康报告 const report = await getRemoteHealthReport([ { pkg: '@myorg/app1', version: '1.0.0', name: 'app1' }, { pkg: '@myorg/app2', version: '2.0.0', name: 'app2' }, ]); console.log(report.overall); // 'healthy' | 'degraded' | 'unhealthy'import { preloadRemote, preloadRemoteList } from 'remote-reload-utils'; // 预加载单个模块 await preloadRemote({ pkg: '@myorg/remote-app', version: '1.0.0', name: 'myorg', priority: 'idle', // 'idle' | 'high' force: false, }); // 预加载多个模块 await preloadRemoteList([ { pkg: '@myorg/app1', version: '1.0.0', name: 'app1' }, { pkg: '@myorg/app2', version: '2.0.0', name: 'app2' }, ], (loaded, total) => { console.log(`Progress: ${loaded}/${total}`); });import { unloadRemote, unloadAll } from 'remote-reload-utils'; // 卸载特定模块 await unloadRemote({ name: 'myorg', pkg: '@myorg/remote-app', version: '1.0.0', clearCache: true, }); // 卸载所有模块 await unloadAll(true); // true = 清除所有缓存import { checkRemoteHealth, getRemoteHealthReport } from 'remote-reload-utils'; // 检查单个远程模块健康状态 const health = await checkRemoteHealth({ pkg: '@myorg/remote-app', version: '1.0.0', name: 'myorg', }); console.log(health.status); // 'healthy' | 'degraded' | 'unhealthy' console.log(health.latency); // 延迟(毫秒) // 生成健康报告 const report = await getRemoteHealthReport([ { pkg: '@myorg/app1', version: '1.0.0', name: 'app1' }, { pkg: '@myorg/app2', version: '2.0.0', name: 'app2' }, ]); console.log(report.overall); // 'healthy' | 'degraded' | 'unhealthy'import { eventBus } from 'remote-reload-utils'; // 订阅事件 const unsubscribe = eventBus.on('user-login', (user, meta) => { console.log('User logged in:', user); console.log('Event meta:', meta); // { timestamp, source, id } }); // 发送事件 eventBus.emit('user-login', { id: 1, name: 'John' }); // 只触发一次的订阅 eventBus.once('notification', (msg) => { console.log('Received once:', msg); }); // 带过滤器的订阅 eventBus.on( 'message', (data) => console.log('Received:', data), { filter: (data) => data.priority === 'high' } ); // 取消订阅 unsubscribe(); // 获取事件历史 const history = eventBus.getHistory('user-login'); // 获取所有事件 const events = eventBus.getEvents();import { checkVersionCompatibility, satisfiesVersion, parseVersion, compareVersions, getLatestVersion, getStableVersions, } from 'remote-reload-utils'; // 检查版本兼容性 const result = checkVersionCompatibility('18.2.0', '^18.0.0', 'react'); console.log(result.compatible); // true console.log(result.severity); // 'info' | 'warning' | 'error' // 版本范围匹配 satisfiesVersion('1.5.0', '^1.0.0'); // true satisfiesVersion('2.0.0', '~1.2.0'); // false satisfiesVersion('1.2.5', '>=1.2.0'); // true // 版本解析 const parsed = parseVersion('1.2.3-alpha.1'); // { major: 1, minor: 2, patch: 3, prerelease: 'alpha.1', raw: '1.2.3-alpha.1' } // 版本比较 compareVersions('2.0.0', '1.0.0'); // > 0 compareVersions('1.0.0', '1.0.0'); // 0 // 获取最新稳定版本 const versions = ['1.0.0', '2.0.0-alpha', '2.0.0', '3.0.0-beta']; getLatestVersion(versions); // '3.0.0-beta' getStableVersions(versions); // ['1.0.0', '2.0.0']import { ErrorBoundary } from '@react-mf-lib/react-adapter'; <ErrorBoundary fallback={(error, reset) => ( <div> <p>Error: {error.message}</p> <button onClick={reset}>Try again</button> </div> )} onError={(error, errorInfo) => { console.error('Caught error:', error, errorInfo); }} onReset={() => console.log('Reset clicked')} > <MyComponent /> </ErrorBoundary>import { lazyRemote } from '@react-mf-lib/react-adapter'; import { Suspense } from 'react'; const RemoteDashboard = lazyRemote({ pkg: '@myorg/remote-app', version: '^1.0.0', moduleName: 'Dashboard', scopeName: 'myorg', maxRetries: 3, retryDelay: 1000, }); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <RemoteDashboard userId={123} /> </Suspense> ); }import { SuspenseRemoteLoader } from '@react-mf-lib/react-adapter'; <SuspenseRemoteLoader pkg="@myorg/remote-app" version="^1.0.0" moduleName="Dashboard" scopeName="myorg" fallback={<Spinner />} errorFallback={(error) => <div>Error: {error.message}</div>} componentProps={{ userId: 123 }} />import { // 版本缓存 getVersionCache, setVersionCache, fetchLatestVersion, // URL 构建 buildCdnUrls, buildFinalUrls, // 共享配置 getFinalSharedConfig, // 卸载状态 getLoadedRemotes, isRemoteLoaded, registerRemoteInstance, registerLoadedModule, // 预加载状态 getPreloadStatus, clearPreloadCache, cancelPreload, // 格式化 formatHealthStatus, } from 'remote-reload-utils';- remote-reload-utils 详细文档 - 核心工具库完整 API
- vue-adapter 文档 - Vue 3 适配层使用指南
- react-adapter 文档 - React 适配层使用指南
react-mf-lib/ ├── packages/ │ ├── remote-reload-utils/ # 核心工具库 │ │ ├── src/ │ │ │ ├── index.ts # 主入口 │ │ │ ├── loader/ │ │ │ │ ├── index.ts # loadRemoteMultiVersion │ │ │ │ └── utils.ts # 加载工具函数 │ │ │ ├── preload/ # 预加载模块 │ │ │ ├── unload/ # 卸载管理 │ │ │ ├── health/ # 健康检查 │ │ │ ├── version/ # 版本工具 │ │ │ ├── event-bus/ # 事件总线 │ │ │ └── plugins/ # 插件系统 │ │ ├── __tests__/ # 单元测试 │ │ ├── package.json │ │ └── tsconfig.json │ ├── vue-adapter/ # Vue 3 适配层 │ │ ├── src/ │ │ │ ├── components/ # Vue 组件 │ │ │ ├── hooks/ # Vue Hooks │ │ │ ├── composables/ # Composables │ │ │ └── types/ # 类型定义 │ │ └── README.md │ └── react-adapter/ # React 适配层 │ ├── src/ │ │ ├── components/ # React 组件 │ │ └── hooks/ # React Hooks │ └── README.md └── apps/ ├── test-mf-unpkg/ # 远程组件示例(React) ├── host-react18-remote/ # 宿主应用示例(React) └── host-vue3-remote/ # 宿主应用示例(Vue 3) pnpm --filter test-mf-unpkg devpnpm --filter host-react18-remote devpnpm --filter @react-mf-lib/vue-adapter build pnpm --filter host-vue3-remote dev按各应用控制台输出的地址访问运行效果。
pnpm install# 构建工具库 pnpm --filter remote-reload-utils build # 构建 Vue 适配器 pnpm --filter @react-mf-lib/vue-adapter build # 监听模式 pnpm --filter remote-reload-utils dev# 运行所有测试 pnpm --filter remote-reload-utils test # 监听模式 pnpm --filter remote-reload-utils test:watch # 生成覆盖率报告 pnpm --filter remote-reload-utils test --coverage# 格式化代码 pnpm --filter remote-reload-utils format # 代码检查 pnpm --filter remote-reload-utils check # Vue 适配器格式化/检查 pnpm --filter @react-mf-lib/vue-adapter lint pnpm --filter @react-mf-lib/vue-adapter check- 构建工具: Rslib, Rsbuild, Rspack
- 运行时: @module-federation/enhanced
- 包管理: pnpm (workspace)
- 代码规范: Biome
- 测试框架: Vitest
- 类型检查: TypeScript
// ✅ 推荐:生产环境使用固定版本 await loadRemoteMultiVersion({ name: 'my-app', pkg: '@myorg/remote-app', version: '1.2.3', }); // ⚠️ 注意:使用 latest 时设置合理的 cacheTTL await loadRemoteMultiVersion({ name: 'my-app', pkg: '@myorg/remote-app', version: 'latest', cacheTTL: 3600000, // 1 小时 revalidate: true, });try { const { mf } = await loadRemoteMultiVersion(options); const mod = await mf.loadRemote(`${scopeName}/MyComponent`); } catch (error) { console.error('Failed to load remote module:', error); // 显示降级 UI }// 在应用空闲时预加载 preloadRemote({ pkg: '@myorg/remote-app', version: '1.0.0', priority: 'idle', }); // 高优先级立即加载 preloadRemote({ pkg: '@myorg/critical-module', priority: 'high', });// 组件卸载时清理 useEffect(() => { return () => { unloadRemote({ name: 'my-app', pkg: '@myorg/remote-app' }); }; }, []);- 检查 CDN 地址是否可访问
- 查看浏览器控制台的错误信息
- 验证远程组件是否正确构建
- 检查 Module Federation 配置是否匹配
- 确认共享模块的
singleton配置 - 检查 React 版本是否兼容
- 使用不同的
name避免命名冲突
- 确认远程组件已发布类型定义
- 检查 TypeScript 配置
- 使用
import type导入类型
- 重构:使用 remote-reload-utils 替换 RemoteModuleCard
- 新增:完整的单元测试覆盖(155+ 测试)
- 新增:健康检查模块
- 新增:事件总线模块
- 新增:版本兼容性检查
ISC
欢迎提交 Issue 和 Pull Request!
- Fork 项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request