
示例工程【免费下载链接】module-federation-examplesImplementation examples of module federation , by the creators of module federation项目地址https://gitcode.com/gh_mirrors/mo/module-federation-examples点击查看免费下载本文基于 module-federation-examples 仓库中的 runtime-plugins/offline-remote 示例展开。该示例演示了当一个联邦 Remoteapp2完全离线不可用时宿主应用app1如何通过 Runtime Plugin 的errorLoadRemote钩子以 React 模块的形式优雅地渲染一段错误提示而不是让页面整体崩溃。读完本文你将掌握 Module Federation Runtime Plugin 的注册方式、errorLoadRemote钩子的完整签名与返回约定以及如何在 webpack / rspack 两套构建体系下复现并验证这一离线容错方案。一、背景联邦架构中的远端不可用问题在 Module Federation 的典型架构中宿主Host应用通过remotes声明静态或动态的远程模块地址并在运行时按需加载remoteEntry.js。网络抖动、服务重启、版本发布窗口、灰度流量切换任何一个环节都可能让远端容器暂时不可用。默认情况下如果加载 remote 失败运行时会把错误向上抛出页面往往表现为白屏或整块组件加载失败用户得不到任何可解释的反馈。module-federation-examples 仓库在 runtime-plugins/README.md 中把 Runtime Plugin 定义为「将产品策略从 Module Federation 核心中移出放进可组合的钩子」核心运行时负责稳定的加载、共享、快照与工厂执行语义而 Runtime Plugin 负责定制诸如请求路由、fallback 模块、共享选择、资源加载、预加载行为与遥测等策略。其中与远程策略相关的钩子族Remote policy包含beforeRequest、errorLoadRemote、afterLoadRemote而offline-remote正是errorLoadRemote这一钩子的最小可运行范例。二、示例架构双向联邦但只启动 app1offline-remote目录下包含两个相互引用的应用属于典型的双向联邦bi-directional结构应用目录端口暴露模块引用对方app1runtime-plugins/offline-remote/app13001./Buttonapp2http://localhost:3002/remoteEntry.jsapp2runtime-plugins/offline-remote/app23002./Buttonapp1http://localhost:3001/remoteEntry.js演示的关键前提是只启动 app1app2 保持离线。当浏览器访问http://localhost:3001时app1 尝试从 3002 端口加载 app2 的remoteEntry.js必然失败此时offline-remote插件捕获错误并以 React 模块形式渲染错误信息。这一结论在 runtime-plugins/offline-remote/playwright.config.ts 中可以得到印证——它的webServers只配置了{ directory: app1, port: 3001 }这一个服务export default createRuntimePluginPlaywrightConfig({ webServers: [{ directory: app1, port: 3001 }], });两个应用的package.json还刻意制造了版本差异app1 使用react^17.0.2与lodash^4.17.21app2 使用react^16.13.0与lodash^3.10.1并在各自的shared配置中把react、react-dom声明为singleton: true用于验证共享策略在不同版本依赖共存时的行为但这与离线容错主线无关本文不再展开。三、核心实现errorLoadRemote 钩子与 fallback 模块整个示例的灵魂是 app1 目录下的插件文件 runtime-plugins/offline-remote/app1/offline-remote.js。它导出一个工厂函数返回一个带name与errorLoadRemote钩子的插件对象export default function () { const getErrorMessage (id, error) remote ${id} is offline due to error: ${error}; const getModule (pg, from) { if (from build) { return () ({ __esModule: true, default: pg, }); } else { return { default: pg, }; } }; return { name: offline-remote-plugin, errorLoadRemote({ id, error, from, origin }) { console.error(id, offline); const pg function () { console.error(id, offline, error); return getErrorMessage(id, error); }; return getModule(pg, from); }, }; }3.1 钩子签名errorLoadRemote({ id, error, from, origin })errorLoadRemote是 Module Federation 运行时在加载远程模块失败时调用的策略钩子。从源码参数可以看出它暴露了四个上下文信息id失败的远程标识。在 e2e 测试注释中可以看到启用asyncStartup后错误钩子可能上报完整的模块 id例如app2/Button而不仅仅是容器名app2error底层加载错误对象被拼接到最终的错误文案中from加载来源标识offline-remote.js中区分了build与非build两种路径直接决定了 fallback 模块的包装形态详见 3.2origin触发错误的原始上下文。该钩子的返回值将作为加载失败的替代模块交付给宿主应用从而把「加载失败」转译为「一个可渲染的兜底模块」——这正是 Runtime Plugin 把策略从核心中剥离出来的典型体现。3.2from build分支两种模块包装形态getModule(pg, from)根据from参数决定如何包装兜底内容当from build时返回一个延迟工厂函数该函数每次被调用时生成{ __esModule: true, default: pg }形式的 ES Module 描述对象。这种形态用于构建期/模块描述阶段需要标记__esModule以兼容转译后的 ES Module 语义其他情况下直接返回{ default: pg }对象这是运行时常见的模块描述形态。3.3 兜底内容本身是一个渲染时再报错的函数pg被设计成一个函数调用时打印console.error(id, offline, error)并返回getErrorMessage(id, error)拼出的字符串即remote ${id} is offline due to error: ${error}。由于errorLoadRemote返回的模块会被宿主当作 React 组件来渲染这个函数在 React 中恰好是一个无状态函数组件——它在渲染时返回错误文本。于是整个链路变成了app1 渲染RemoteButton来自app2/Button时触发远程加载加载失败 → 运行时调用errorLoadRemote插件返回一个「渲染时才输出错误文案」的兜底组件该组件在页面上渲染出remote app2 is offline due to error: ...的提示。这样既绕过了「加载失败即抛错」的默认行为又保留了错误信息本身让用户看到的是可读的提示而非空白页面。3.4 TypeScript 版本类型化的插件工厂仓库还提供了同名的 TypeScript 版本 runtime-plugins/offline-remote/app1/offline-remote.ts内容非常精简但点明了插件类型体系import type { FederationRuntimePlugin } from module-federation/runtime/types; import offlineRemotePlugin from ./offline-remote.js; export default offlineRemotePlugin as () FederationRuntimePlugin;它从module-federation/runtime/types引入FederationRuntimePlugin类型并把 JS 版本的工厂函数断言为该类型的工厂。这说明在 TypeScript 工程中插件必须满足FederationRuntimePlugin的结构约束name 若干可选钩子rspack 配置正是通过require.resolve(./offline-remote.ts)直接引用这个 TS 文件作为 runtime 插件。四、插件接入webpack 与 rspack 双配置Runtime Plugin 的接入入口是 ModuleFederationPlugin 的runtimePlugins配置项。该选项接收一组模块解析路径构建时这些插件会被注入联邦运行时。4.1 webpack 版本legacywebpack 配置位于 runtime-plugins/offline-remote/app1/webpack.config.js核心片段如下const { ModuleFederationPlugin } require(module-federation/enhanced/webpack); new ModuleFederationPlugin({ experiments: { asyncStartup: true }, name: app1, remoteType: script, library: { type: var, name: app1 }, shareStrategy: loaded-first, filename: remoteEntry.js, remotes: { app2: app2http://localhost:3002/remoteEntry.js, }, runtimePlugins: [require.resolve(./offline-remote.js)], exposes: { ./Button: ./src/Button, }, shared: { ...deps, react: { singleton: true }, react-dom: { singleton: true }, }, }),这里有几个关键配置项值得展开runtimePlugins: [require.resolve(./offline-remote.js)]通过require.resolve把插件文件的绝对路径交给运行时是插件生效的唯一入口experiments: { asyncStartup: true }启用异步启动。e2e 测试注释专门提到「With async startup the runtime error hook may report the full module id」即异步启动模式下错误钩子上报的 id 粒度可能细化到app2/Button这样的模块级remoteType: script与library: { type: var, name: app1 }以全局变量方式暴露 remote 容器配合静态remotes声明确保两应用以经典 script 方式互相发现shareStrategy: loaded-first共享依赖采用「已加载优先」的解析策略。4.2 rspack 版本默认rspack 配置位于 runtime-plugins/offline-remote/app1/rspack.config.js与 webpack 版本的关键差异体现在三处插件类从module-federation/enhanced/rspack导入HTML 插件使用rspack/core的HtmlRspackPluginruntimePlugins引用的是 TypeScript 插件文件runtimePlugins: [require.resolve(./offline-remote.ts)]模块编译走builtin:swc-loader.js与.ts分别配置了 JSX 与 TypeScript 解析器React 采用runtime: automatic的自动 JSX 运行时。两个配置在name: app1、filename: remoteEntry.js、remotes.app2、exposes[./Button]、shared策略上保持一致说明同一份 Runtime Plugin 逻辑可以在两套构建体系下无差别工作。app2 的对应配置见 runtime-plugins/offline-remote/app2/webpack.config.js 与 runtime-plugins/offline-remote/app2/rspack.config.js其中shared.lodash被显式声明为{}不加 singleton 约束用于承载版本差异。4.3 插件钩子覆盖面速查根据 runtime-plugins/README.md 的钩子总览offline-remote属于「Remote policy」这一钩子族的示例与其他示例的覆盖关系如下Hook 族Hooks覆盖示例Remote policybeforeRequest、errorLoadRemote、afterLoadRemoteremote-router、offline-remoteBootstrapbeforeInit、init、applycontrol-sharing、remote-control等Share policybeforeLoadShare、resolveSharecontrol-sharing、multiple-react-versions等Loader policycreateScript、afterLoadEntryremote-routerFactory executiononLoadmultiple-react-versionsPreload policybeforePreloadRemote、generatePreloadAssetsremote-router五、宿主应用如何消费兜底模块5.1 组件树与 Suspenseapp1 的入口 runtime-plugins/offline-remote/app1/src/index.js 使用经典的ReactDOM.render挂载根组件。根组件 runtime-plugins/offline-remote/app1/src/App.js 中远程按钮的消费方式如下import RemoteButton from app2/Button; React.Suspense fallbackLoading Button RemoteButton / /React.Suspenseapp2/Button是声明在remotes中的静态远程模块加载被包在React.Suspense中。当远程加载失败时errorLoadRemote返回的兜底模块会替代真实的Button进入 Suspense 的渲染流程——它本身是一个同步函数组件因此会立即渲染出remote app2 is offline due to error: ...文本。5.2 使用__FEDERATION__.__INSTANCES__展示在线 RemoteApp.js 中还有一段值得注意的代码它遍历联邦运行时的实例列表把当前参与联邦的应用名以哈希颜色块展示出来{__FEDERATION__.__INSTANCES__.map(inst ( span style{{ padding: 10, color: #fff, background: getColorFromString(inst.name.split().reverse().join()), }} key{inst.name} {inst.name} /span ))}__FEDERATION__.__INSTANCES__是运行时暴露的全局联邦实例集合这一行代码展示了如何在宿主页面内直接观测运行时注册了哪些联邦实例可作为排查 remote 是否被正确发现的辅助手段。页面中的 Reset 按钮会清空localStorage中的button键并刷新页面用于复位 app2 按钮此前写入的持久化状态见 runtime-plugins/offline-remote/app2/src/Button.js 中的localStorage.setItem(button, green)。六、运行方式与端到端验证6.1 包管理与启动脚本整个示例通过 runtime-plugins/offline-remote/package.json 以 pnpm workspace 管理两个应用常用命令如下命令作用pnpm start仅启动 app1offline-remote_app1用于演示离线场景pnpm start:both并行启动 app1 与 app2pnpm build并行构建两个应用pnpm serve以静态服务器分别服务两应用构建产物pnpm legacy:start使用 webpack 配置而非默认 rspack启动pnpm e2e:ci构建后运行 Playwright 端到端测试默认启动命令为rspack serve -c rspack.config.js构建命令为rspack build --mode production -c rspack.config.js对应的 webpack 版本由legacy:start/legacy:build提供。演示离线场景只需执行pnpm start然后访问http://localhost:3001页面标题为 Offline Remote下方会展示当前联邦实例名与本地按钮而远程按钮区域渲染的是离线错误提示。6.2 e2e 测试断言测试文件 runtime-plugins/offline-remote/e2e/checkAutomaticVendorApps.spec.ts 把离线行为固化为可回归的断言断言 H1 标题 Offline Remote 与 H2 标题 Remotes currently in use 可见断言本地按钮App 1 Button可见且背景色为rgb(136, 0, 0)对应 runtime-plugins/offline-remote/app1/src/Button.js 中background: #800的样式断言页面出现匹配/remote app2(\/Button)? is offline/i的文本这正是errorLoadRemote返回的兜底组件渲染出的错误文案正则中的可选组(\/Button)?兼容了异步启动模式下模块级 idapp2/Button与容器级 idapp2两种上报粒度。Playwright 配置复用了 runtime-plugins/playwright.base.ts 提供的createRuntimePluginPlaywrightConfig工厂仅在webServers中声明 app1从而保证测试环境与「app2 离线」的演示前提一致。七、生产实践要点基于对离线容错插件的完整剖析可以沉淀出以下可直接迁移到生产项目的实践要点把「加载失败」转译为「可渲染兜底」errorLoadRemote的返回值是替代模块最小实现是返回一个函数组件渲染时输出可读文案同时用console.error保留错误现场复杂的生产实现可以在此返回更完整的占位组件、重试按钮甚至降级数据流。兼容from的两种加载来源插件代码中from build分支提醒我们运行时存在构建期与运行期两种模块获取路径兜底模块的包装形态需要分别处理否则可能出现__esModule语义或模块工厂形态不匹配的问题。区分容器级与模块级的错误 id启用experiments.asyncStartup后id可能携带app2/Button这样的完整模块路径错误文案与日志统计的解析逻辑应兼容两种粒度。插件与构建体系解耦同一个插件逻辑既可以在 webpack 的module-federation/enhanced/webpack下通过runtimePlugins: [require.resolve(./offline-remote.js)]接入也可以在 rspack 的module-federation/enhanced/rspack下通过runtimePlugins: [require.resolve(./offline-remote.ts)]接入且两者共享相同的运行时行为——这意味着插件代码本身与打包器无关只需满足FederationRuntimePlugin类型约束即可。用端到端测试固化离线行为把「远端离线时页面渲染特定错误文案」写成 Playwright 断言并让测试服务只启动宿主应用就能持续守护这一容错能力不被后续改动破坏。如需继续深入可对照阅读同目录下的其他 Runtime Plugin 示例runtime-plugins/control-sharing、runtime-plugins/remote-router、runtime-plugins/multiple-react-versions 等它们分别演示了共享解析、请求路由、脚本加载、预加载与多版本 React 共存等不同钩子族可帮助你拼出 Runtime Plugin 能力的完整版图。赞分享示例工程【免费下载链接】module-federation-examplesImplementation examples of module federation , by the creators of module federation项目地址https://gitcode.com/gh_mirrors/mo/module-federation-examples点击查看免费下载相关推荐LeetCode算法极限性能优化10个高效实现策略与多语言解决方案指南LeetCode算法极限性能优化10个高效实现策略与多语言解决方案指南 LeetCode算法刷题是程序员提升编程能力的必经之路但如何在有限时间内实现最优解示例工程Module Federation 运行时插件实战remote-control 示例中基于 Runtime Plugin 的动态远程控制 Remote 加载Module Federation 运行时插件实战remote control 示例中基于 Runtime Plugin 的动态远程控制 Remote 加载示例工程Module Federation 实战用 Vite Angular 构建 host/remote 微前端module-federation-vite-angular 示例解析Module Federation 实战用 Vite Angular 构建 host/remote 微前端module federation vite示例工程上一篇3个核心痛点为什么你的Mac多任务处理效率低下下一篇bup cat-file 深度指南从 bup 归档中低层提取文件内容、元数据与 .bupm 目录清单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考