微前端-Module Federation运行时工具

fan-mf-lib --- Yunfan Module Federation 工具集

背景

fan-mf-lib 是一套基于 Module Federation 的前端微模块解决方案。

核心包 @fan-scripts/fan-mf-runtime 负责在运行时按版本加载远程 React 组件,并内置预加载、卸载、健康检查与事件总线;

@fan-scripts/react-adapter@fan-scripts/vue-adapter 分别面向 React 与 Vue 3 宿主。本仓库为 pnpm monorepo,附带 Bridge、React 18 / Vue 3 宿主等多套可运行示例,适合作为微前端基建学习与工程参考。

app/demo-app-bridge-host 宿主应用示例、 demo-app-bridge-provider 远程应用示例,本地开发可以启动展示, 线上可以将远程应用发到CDN/npm仓库的方式进行加载

标签 : module-federation · micro-frontend · react · vue3 · typescript · pnpm · rsbuild · remote-components

仓库 : BlueOrgreen/fan-mf-lib

业务已经有Module Federation微前端方案,为什么还要做 @fan-scripts/fan-mf-runtime 这一层

我们业务里已经在用 Module Federation,但构建时配置和运行时治理往往是两套事。MF 官方更擅长在构建阶段把 Host/Remote 接好;真正上线后,还会遇到:远程包版本怎么选、CDN 挂了怎么办、同一页面要不要跑两个版本的 Remote、实例怎么卸载、怎么预加载------这些如果每个业务自己写,会重复造轮子,而且容易和 MF Runtime 的细节缠在一起。

所以我做了 fan-mf-lib,核心是 @fan-scripts/fan-mf-runtime:在 MF Enhanced Runtime 之上,把「按 npm 包名 + 版本动态拉 remoteEntry」做成统一 API,比如 loadRemoteMultiVersion

不用这层库,团队常见坑有:

  1. 版本:写死 remoteEntry URL,发版要改 Host 配置;多团队并行时很难让「同一页面共存 v1/v2」。
  2. 可用性:只配一个 CDN,源站或 unpkg 抖动就直接白屏,没有自动换源。
  3. React 共享:Vue 宿主里加载 React Remote,容易出现双 React 或 shared 配错。
  4. 生命周期:只加载不卸载,路由切换或 Tab 关闭后内存、全局单例、缓存越积越多。
  5. 体验:没有统一的预加载、健康检查、跨模块事件总线,业务各自实现,难维护。

我的库把这些收成运行时工具链:多版本解析与缓存、多 URL 故障转移、shared 合并、预加载/卸载/健康检查、Bridge 懒加载;再提供 React/Vue 适配层,业务用 Provider、Hooks 接入,而不是直接摸 MF 底层 API

仓库概览

这是一个 pnpm monorepo,包含:

已发布包

包名 版本 说明
@fan-scripts/fan-mf-runtime v1.0.4 核心运行时加载工具库
@fan-scripts/react-adapter v1.0.1 React 适配层
@fan-scripts/vue-adapter v1.0.1 Vue 3 适配层(在 Vue 中加载 React 组件)

示例应用

应用 说明
demo-bridge-host Bridge 模块宿主应用 demo
demo-bridge-provider Bridge 模块远程组件提供者 demo
test-mf-unpkg 远程组件示例应用(React)
host-react18-remote React 18 宿主应用示例
host-vue3-remote Vue 3 宿主应用示例(消费 React 组件)

特性

  • 🚀 运行时动态加载 - 无需重新构建即可加载远程组件
  • 📦 多版本支持 - 支持同一包的多个版本同时运行
  • 🔄 CDN 故障转移 - 自动在多个 CDN 之间切换,提高可用性
  • 💾 智能缓存 - 内置版本缓存机制,减少网络请求
  • 🎯 TypeScript 支持 - 完整的类型定义
  • ⚛️ React 友好 - 专为 React 组件 Module Federation 设计
  • 🔧 可扩展 - 插件系统支持自定义扩展
  • 📊 性能优化 - 预加载、卸载、健康检查
  • 🔗 事件总线 - 跨模块通信支持
  • 质量保障 - 155+ 单元测试,高覆盖率
  • 🌉 Bridge 模块 - 支持懒加载远程组件和预加载

安装

bash 复制代码
npm install @fan-scripts/fan-mf-runtime
# 或
pnpm add @fan-scripts/fan-mf-runtime
# 或
yarn add @fan-scripts/fan-mf-runtime

快速开始

基本使用

typescript 复制代码
import { loadRemoteMultiVersion } from '@fan-scripts/fan-mf-runtime';

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;
}

本地调试

本地开发时,使用 localDebug 配置直接加载本地运行的远程组件服务:

typescript 复制代码
import { loadRemoteMultiVersion } from '@fan-scripts/fan-mf-runtime';

const { scopeName, mf } = await loadRemoteMultiVersion({
  name: 'my-remote-app',
  pkg: '@myorg/remote-app',
  version: '1.0.0',
  localDebug: {
    enabled: true,
    entry: 'http://localhost:3001/remoteEntry.js',
  },
});

const mod = await mf.loadRemote(`${scopeName}/Button`);

Bridge 模块 - 懒加载远程组件

typescript 复制代码
import { createLazyComponent, loadRemoteMultiVersion } from '@fan-scripts/fan-mf-runtime';

const RemoteButton = createLazyComponent({
  loader: () => loadRemoteMultiVersion({
    name: 'remote',
    pkg: '@org/remote-pkg',
    version: '1.0.0',
  }).then(({ mf }) => mf.loadRemote('remote/Button')),
  loading: <div>Loading...</div>,
  fallback: ({ error }) => <div>Error: {error.message}</div>,
});

function App() {
  return <RemoteButton variant="primary" />;
}

预加载组件

typescript 复制代码
import { prefetchComponent } from '@fan-scripts/fan-mf-runtime';

// 预加载远程组件资源
prefetchComponent({
  id: 'remote/Button',
  preloadComponentResource: true,
});

使用 useLazyComponent Hook

typescript 复制代码
import { useLazyComponent, loadRemoteMultiVersion } from '@fan-scripts/fan-mf-runtime';

function MyComponent() {
  const { loading, error, Component } = useLazyComponent({
    loader: () => loadRemoteMultiVersion({
      name: 'remote',
      pkg: '@org/remote-pkg',
      version: '1.0.0',
    }),
    loading: <div>Loading...</div>,
    fallback: ({ error }) => <div>Error: {error.message}</div>,
  });

  if (loading) return null;
  if (error) return <div>Error: {error.message}</div>;
  if (Component) return <Component />;
  return null;
}

React Adapter 方式

typescript 复制代码
import { lazyRemote, RemoteModuleProvider } from '@fan-scripts/react-adapter';
import { Suspense } from 'react';

// 方式 1: lazyRemote
const RemoteDashboard = lazyRemote({
  pkg: '@myorg/remote-app',
  version: '^1.0.0',
  moduleName: 'Dashboard',
  scopeName: 'myorg',
});

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <RemoteDashboard userId={123} />
    </Suspense>
  );
}

// 方式 2: RemoteModuleProvider
function App() {
  return (
    <RemoteModuleProvider
      pkg="@myorg/remote-app"
      version="^1.0.0"
      moduleName="Dashboard"
      scopeName="myorg"
      loadingFallback={<Spinner />}
      errorFallback={(error, reset) => (
        <div>
          <p>Error: {error.message}</p>
          <button onClick={reset}>Retry</button>
        </div>
      )}
    />
  );
}

Vue Adapter 方式

typescript 复制代码
import { VueRemoteModuleProvider } from '@fan-scripts/vue-adapter';

export default {
  template: `
    <VueRemoteModuleProvider
      pkg="@myorg/remote-app"
      version="^1.0.0"
      moduleName="Dashboard"
      scopeName="myorg"
    />
  `,
};

Bridge 模块 API

createLazyComponent

创建懒加载远程组件的工厂函数。

typescript 复制代码
import { createLazyComponent } from '@fan-scripts/fan-mf-runtime';

const LazyComponent = createLazyComponent<T>(options);

参数:

参数 类型 必填 说明
loader () => Promise<T> 加载远程模块的函数
loading ReactNode 加载中的 UI
fallback (errorInfo) => ReactNode 错误兜底 UI
export string 导出名称,默认 'default'
delayLoading number 延迟显示 loading 的毫秒数
dataFetchParams unknown 数据获取参数
noSSR boolean 是否禁用服务端渲染

useLazyComponent

用于懒加载远程组件的 React Hook。

typescript 复制代码
import { useLazyComponent } from '@fan-scripts/fan-mf-runtime';

const { loading, error, Component } = useLazyComponent({
  loader: () => loadRemoteMultiVersion(options),
  loading: <div>Loading...</div>,
  fallback: ({ error }) => <div>Error: {error.message}</div>,
});

返回值:

typescript 复制代码
{
  loading: boolean,      // 是否正在加载
  error: ErrorInfo | null,  // 错误信息
  Component: ComponentType<T> | null  // 加载完成的组件
}

prefetchComponent

预加载远程组件资源。

typescript 复制代码
import { prefetchComponent } from '@fan-scripts/fan-mf-runtime';

prefetchComponent({
  id: 'remote/Component',
  preloadComponentResource: true,
  dataFetchParams: { userId: 123 },
});

核心 API 文档

loadRemoteMultiVersion

动态加载远程模块,支持多版本和故障转移。

typescript 复制代码
import { loadRemoteMultiVersion } from '@fan-scripts/fan-mf-runtime';

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
localDebug LocalDebugOptions - 本地调试配置,启用后跳过 CDN 直接使用本地地址
cacheTTL number 86400000 缓存时间(毫秒)
revalidate boolean true 异步重新验证最新版本
shared Record<string, any> - 自定义共享模块配置

返回值 : Promise<{ scopeName: string, mf: ModuleFederationInstance }>

MF 实例方法:

typescript 复制代码
const { scopeName, mf } = await loadRemoteMultiVersion(options);

// 加载暴露的模块
const module = await mf.loadRemote(`${scopeName}/Button`);
const Button = module.default;

事件总线

typescript 复制代码
import { eventBus } from '@fan-scripts/fan-mf-runtime';

// 订阅事件
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);
});

// 取消订阅
unsubscribe();

// 获取事件历史
const history = eventBus.getHistory('user-login');

版本工具

typescript 复制代码
import {
  checkVersionCompatibility,
  satisfiesVersion,
  parseVersion,
  compareVersions,
  getLatestVersion,
  getStableVersions,
} from '@fan-scripts/fan-mf-runtime';

// 检查版本兼容性
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

// 版本比较
compareVersions('2.0.0', '1.0.0'); // > 0
compareVersions('1.0.0', '1.0.0'); // 0

运行示例

Bridge Demo

bash 复制代码
# 同时启动 Provider 和 Host(推荐)
cd apps/demo-bridge-host
pnpm dev:all

# 或分别启动
# 1. 启动 Provider (端口 3001)
cd apps/demo-bridge-provider
pnpm dev

# 2. 启动 Host (端口 3002)
cd apps/demo-bridge-host
pnpm dev

访问 http://localhost:3002 查看效果

技术栈

  • 构建工具: Rslib, Rsbuild, Rspack
  • 运行时: @module-federation/enhanced, @module-federation/bridge-react
  • 包管理: pnpm (workspace)
  • 代码规范: Biome
  • 测试框架: Vitest + happy-dom
  • 类型检查: TypeScript
  • 版本管理: Changesets

最佳实践

1. 版本管理

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,
});

2. 预加载优化

typescript 复制代码
// 在用户可能访问的路由预加载
useEffect(() => {
  prefetchComponent({
    id: 'remote/Dashboard',
    preloadComponentResource: true,
  });
}, []);

// 高优先级立即加载
prefetchComponent({
  id: 'remote/CriticalModule',
  priority: 'high',
});

3. 错误处理

typescript 复制代码
const MyLazyComponent = createLazyComponent({
  loader: () => loadRemoteMultiVersion(options),
  loading: <Spinner />,
  fallback: ({ error, errorType }) => (
    <ErrorFallback
      error={error}
      type={errorType} // 'LOAD_REMOTE' | 'DATA_FETCH' | 'RENDER'
      onRetry={() => window.location.reload()}
    />
  ),
});

4. 资源清理

typescript 复制代码
import { unloadRemote } from '@fan-scripts/fan-mf-runtime';

// 组件卸载时清理
useEffect(() => {
  return () => {
    unloadRemote({ name: 'my-app', pkg: '@myorg/remote-app' });
  };
}, []);

5. 本地调试

typescript 复制代码
// ✅ 推荐:本地开发使用 localDebug 配置
const isDev = process.env.NODE_ENV === 'development';

const { mf } = await loadRemoteMultiVersion({
  name: 'my-app',
  pkg: '@myorg/remote-app',
  version: '1.0.0',
  ...(isDev && {
    localDebug: {
      enabled: true,
      entry: 'http://localhost:3001/remoteEntry.js',
    },
  }),
});

// 或者使用 localFallback 作为兜底(会先尝试 CDN)
const { mf } = await loadRemoteMultiVersion({
  name: 'my-app',
  pkg: '@myorg/remote-app',
  version: '1.0.0',
  localFallback: 'http://localhost:3001/remoteEntry.js',
});

故障排查

加载失败

  1. 检查 CDN 地址是否可访问
  2. 查看浏览器控制台的错误信息
  3. 验证远程组件是否正确构建
  4. 检查 Module Federation 配置是否匹配
  5. 确认 remoteEntry.js 可访问
  6. 本地开发时,使用 localDebug 配置直接加载本地服务

"React is not defined" 错误

确保库的构建配置正确外部化 React:

typescript 复制代码
// rslib.config.ts
export default defineConfig({
  tools: {
    rspack: {
      externals: ['react', 'react-dom', 'react/jsx-runtime'],
    },
  },
});

版本冲突

  1. 确认共享模块的 singleton 配置
  2. 检查 React 版本是否兼容
  3. 使用不同的 name 避免命名冲突

类型错误

  1. 确认远程组件已发布类型定义
  2. 检查 TypeScript 配置
  3. 使用 import type 导入类型

相关文档

更新日志

v1.0.4 (fan-mf-runtime)

  • 新增 Bridge 模块:createLazyComponentuseLazyComponent
  • 新增 prefetchComponent 预加载功能
  • 新增 createLazyLoadComponentPlugin 导出
  • 完善错误处理和类型定义

v0.0.12

  • 重构:使用 fan-mf-runtime 替换 RemoteModuleCard
  • 新增:完整的单元测试覆盖(155+ 测试)
  • 新增:健康检查模块
  • 新增:事件总线模块
  • 新增:版本兼容性检查

查看详细更新日志

许可证

ISC

贡献

欢迎提交 Issue 和 Pull Request!

  1. Fork 项目
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

相关链接

相关推荐
小黑蛋9121 小时前
Nacos 集群部署方案
前端
PILIPALAPENG1 小时前
第4周 Day 4:Agent 工作流模式——编排复杂流程
前端·人工智能·python
KaMeidebaby1 小时前
卡梅德生物技术快报|蛋白的过表达质粒构建与生信分析实验全流程复盘
前端·数据库·其他·百度·新浪微博
ricardo19731 小时前
代码分割 + 路由懒加载 + 字体子集化:前端瘦身三板斧
前端·面试
dsyyyyy11011 小时前
CSS 2D 效果、3D 效果 与 Animation 总结
前端·css·3d
jerrywus1 小时前
Vibe Coding 实战:三天,一个人,一个 Claude Session Viewer——给三家 AI CLI 当统一会话浏览器
前端·claude·gemini
国科安芯2 小时前
ASM232S抗辐照RS-232收发器的技术架构与空间环境适应性研究
单片机·嵌入式硬件·安全·架构·安全性测试
心中有国也有家2 小时前
PaddlePaddle 适配 NPU 的技术全解析——从算子接入到端到端性能优化
人工智能·分布式·算法·性能优化·架构·paddlepaddle
GISer_Jing2 小时前
Three.js渲染架构:从WebGL到WebGPU的演进
javascript·架构·webgl