news 2026/10/15 6:31:41

跨应用 Tab 组件远程加载与路由同步:解析 comprehensive-demo-react18 的 Tabs 演示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
跨应用 Tab 组件远程加载与路由同步:解析 comprehensive-demo-react18 的 Tabs 演示
  • 示例工程

【免费下载链接】module-federation-examples

Implementation examples of module federation , by the creators of module federation

项目地址:https://gitcode.com/gh_mirrors/mo/module-federation-examples
点击查看免费下载

comprehensive-demo-react18 是 module-federation-examples 仓库中的一个多框架综合示例,其中位于 app-01/src/docs/Tabs.md 的文档,完整演示了「宿主应用(App #1)通过 Module Federation 远程加载 App #2 的 Tabs 组件,并让浏览器路由与 Tab 状态实时同步」的实战方案。本文将以该文档为主线,结合仓库内真实源码与 E2E 测试,逐层拆解远程 Tab 组件如何实现、如何与路由联动、如何按需懒加载第三个应用的样式化组件,帮助你掌握一套可复用的「远程组件 + 路由同步 + 懒加载」联邦化开发模式。

一、文档背后的演示场景:App #1 中的 Routing Demo

Tabs.md 位于宿主应用 App #1 的文档目录中,它描述了以下四个核心事实:

  1. 页面中的 Tab 组件是从bravo-app(即 App #2,运行在 3002 端口)远程导入的;
  2. 浏览器的路由会随激活的 Tab 在/routing/foo与/routing/bar之间切换;
  3. 直接打开 http://localhost:3002(App #2 的根页面)可以看到完全相同的 Tab 组件;
  4. "Bar" 这个 Tab 内还按需懒加载了来自 UI Library 演示(App #3,3003 端口)的 styled-componentsButton,且仅在 Bar 被渲染时才加载。

这正是 Module Federation 中「同一远程组件既可作为宿主页面内嵌模块、又可作为远程应用自身页面模块」的双重用法,也展示了跨应用按需加载的完整链路:App #1 → App #2(Tabs)→ App #3(Button)。

二、文档如何在页面中呈现:routing-page.jsx 与 Markdown 渲染链路

Tabs.md 之所以能成为路由演示页的说明文档,是因为 App #1 在构建时通过raw-loader将.md文件作为字符串导入(见 app-01/webpack.config.js),再由 app-01/src/Markdown.jsx 使用markdown-to-jsx渲染成 Material UI 风格的排版。

路由演示页 routing-page.jsx 把「文档」与「远程组件」放在同一页面:

import Markdown from 'markdown-to-jsx'; import Page from '../Page'; import React from 'react'; import Tabs from '../docs/Tabs.md'; const RoutedTabs = React.lazy(() => import('app_02/Tabs')); const RoutingPage = () => ( <Page title="Routing Demo"> <Markdown>{Tabs}</Markdown> <React.Suspense fallback="Loading Tabs..."> <RoutedTabs /> </React.Suspense> </Page> );

这里import('app_02/Tabs')是app_02这个远程容器(remote container)的异步导入调用,配合React.lazy与React.Suspense,实现了远程组件的懒加载:只有当用户进入/routing路由时,Tabs 组件才会被拉取并渲染。该页面被挂载在 app-01/src/Routes.jsx 的/routing路径下。

三、远程 Tab 组件的实现:路由驱动切换的 Tabs.jsx

文档所说的「路由随 Tab 变化」并非空话,而是由 App #2 中的 Tabs.jsx 通过react-router-dom的 Hook 实现的。它用useRouteMatch()取得当前页面的基础路径(在 App #1 内即/routing),用useHistory()在切换 Tab 时改写 URL,用useLocation()读取当前路径作为 Tab 的激活值:

const match = useRouteMatch(); const history = useHistory(); const location = useLocation(); const { path: rootPath } = match; const handleChange = (event, newValue) => { history.push(newValue); }; <Tabs value={location.pathname} onChange={handleChange}> <Tab label="Foo" value={`${rootPath}/foo`} /> <Tab label="Bar" value={`${rootPath}/bar`} /> </Tabs>

随后通过Switch/Route将路由与内容绑定:/routing精确匹配时重定向到/routing/foo,/routing/foo渲染 "Foo Content",/routing/bar渲染 "Bar Content"。

关键点在于:这套组件并不关心自己运行在哪个应用里。它从match.path推导基础路径,因此在 App #1 的/routing下表现为/routing/foo、/routing/bar;而当它作为 App #2 根页面的一部分渲染时(见 app-02/src/App.jsx),基础路径退化为空,路由就变成/foo、/bar。文档中「打开 3002 端口能看到同样的 Tab 组件」正是由此而来。

四、Bar Tab 的按需懒加载:跨应用加载 styled-components Button

文档还强调了一个容易被忽略的细节:Bar 内容里的Button来自 UI Library 演示(App #3),并且「仅在 Bar 被渲染时懒加载」。这在 Tabs.jsx 的顶部和 Bar 路由内均有体现:

const Button = React.lazy(() => import('app_03/Button'));

Bar 路由的内容中嵌套了React.Suspense:

<Route path={`${rootPath}/bar`}> <Typography component="div"> <Box p={3}> Bar Content <React.Suspense fallback={null}> <Button>Bar Button</Button> </React.Suspense> </Box> </Typography> </Route>

只有路由落在/routing/bar(或 App #2 根部的/bar)时,app_03/Button才会被真正加载。该按钮是 app-03/src/Button.jsx 中一个典型的 styled-components 样式化组件:

const Button = styled.button` display: inline-block; border-radius: 3px; padding: 0.5rem 0; margin: 0.5rem 1rem; width: 11rem; background: palevioletred; color: white; font-size: 1rem; font-family: sans-serif; `;

于是形成了「宿主 → 远程 Tab → 更远程的 Button」两级懒加载链条,验证了 Module Federation 支持嵌套联邦(remote 再消费 remote)的能力。

五、支撑这一切的联邦配置:exposes、remotes 与 shared

5.1 App #1:宿主兼聚合方

app-01/webpack.config.js 中的ModuleFederationPlugin声明了四个远程依赖,其中就包含 App #2 与 App #3:

remotes: { app_02: 'app_02@http://localhost:3002/remoteEntry.js', app_03: 'app_03@http://localhost:3003/remoteEntry.js', app_04: 'app_04@http://localhost:3004/remoteEntry.js', app_05: 'app_05@http://localhost:3005/remoteEntry.js', },

同时它以exposes对外提供./SideNav与./Page,供 App #2 反向消费(见下文 App #2 的 App.jsx)。

5.2 App #2:既是被消费方,又是消费方

app-02/webpack.config.js 中,name: 'app_02'与filename: 'remoteEntry.js'定义了远程容器的身份与入口文件;exposes声明了对外暴露的模块:

exposes: { './Dialog': './src/Dialog', './Tabs': './src/Tabs', },

remotes则声明了它自身的依赖——App #1 的Page组件与 App #3 的Button:

remotes: { app_01: 'app_01@http://localhost:3001/remoteEntry.js', app_03: 'app_03@http://localhost:3003/remoteEntry.js', },

App #2 的根应用 App.jsx 正是通过import('app_01/Page')复用 App #1 的Page外壳(顶栏 + 内容区),再把本地Dialog与Tabs渲染进去,从而与 App #1 的页面观感完全一致。

5.3 共享依赖:singleton 与 eager

为避免 React 双实例问题,两端配置都对核心依赖做了单例化处理。以 App #2 为例:

shared: { ...deps, '@material-ui/core': { singleton: true }, 'react-router-dom': { singleton: true }, 'react-dom': { singleton: true, requiredVersion: false, eager: true }, react: { singleton: true, requiredVersion: false, eager: true }, },
  • singleton: true:保证联邦各应用共用同一份react、react-dom、@material-ui/core与react-router-dom,避免 hooks 状态错乱;
  • requiredVersion: false:放宽版本校验,允许跨应用版本不一致时仍可共享;
  • eager: true:让核心依赖在容器启动时同步就绪,避免加载时序问题;
  • 此外experiments: { asyncStartup: true }开启了异步启动实验特性,shareStrategy: 'loaded-first'指定共享依赖采用「先加载者优先」策略。

值得说明的是,App #2 同时提供了 webpack 与 rspack 两套构建配置(webpack.config.js 与 rspack.config.js),二者在联邦配置上保持了一致,仅构建器与 loader 不同,便于对比两种工具链下的联邦实现。此外,两端 devServer 都开启了 CORS 头(Access-Control-Allow-Origin: *),这是浏览器端跨端口拉取remoteEntry.js与联邦模块的必要前提。

六、文档中「路由即 URL」的可验证性:Playwright E2E 测试

文档所描述的行为已被仓库内的 E2E 测试固化。在 comprehensive-demo-react18/e2e/checkApp1.spec.ts 中,routing page renders tabs用例完整验证了整条链路:

test('routing page renders tabs', async ({ page }) => { await page.goto(`${base}/#/routing/foo`); ... await expect(page.getByRole('tab', { name: 'Foo' })).toBeVisible(); await expect(page.getByText('Foo Content')).toBeVisible(); await page.getByRole('tab', { name: 'Bar' }).click(); await expect(page.getByText('Bar Content')).toBeVisible(); const barButton = page.getByRole('button', { name: 'Bar Button' }); await expect(barButton).toBeVisible(); await expect(barButton).toHaveCSS('background-color', 'rgb(219, 112, 147)'); });

该用例同时断言了 Tabs.md 中的全部四段文字(routingParagraphs,见 checkApp1.spec.ts),并验证了:

  • 路由进入#/routing/foo时 Foo Tab 激活、Foo 内容可见;
  • 点击 Bar Tab 后 Bar 内容可见,且 Bar Button 懒加载成功;
  • rgb(219, 112, 147)正是 App #3 中palevioletred的解析结果,证明远程样式化按钮确实渲染生效。

同一测试文件还以{ name: 'Routing', hash: '#/routing/foo' }断言了侧边栏中 Routing 入口的链接(checkApp1.spec.ts),并将#/routing/foo作为文档锚点链接的一部分(checkApp1.spec.ts),与文档「浏览器路由是 /routing/<foo|bar>」的描述相互印证。

七、如何运行与体验

在 comprehensive-demo-react18 目录下按序执行:

yarn install pnpm run start

随后打开 http://localhost:3001,从左侧导航进入Routing页面即可复现本文所述场景;再打开 http://localhost:3002 对比同一 Tabs 组件在远程应用根部的表现。整个 demo 由五个应用构成:App #1(3001,宿主外壳)→ App #2(3002,Material UI 组件提供方)→ App #3(3003,styled-components UI 库)→ App #4(3004,Svelte)→ App #5(3005,LitElement)。运行 E2E 测试可执行pnpm test:e2e,或使用pnpm test:e2e:ui打开交互式调试界面(见 comprehensive-demo-react18/package.json)。

八、小结:从本文可复用的三件事

通过 Tabs.md 与配套源码,可以提炼出三种可直接迁移到自有项目的实践:

  1. 远程组件的路由自适应:远程组件用useRouteMatch().path而非硬编码路径推导基础路由,就能在同一份代码下同时服务于宿主子路由(/routing/foo)与远程应用根路由(/foo);
  2. 两级懒加载:React.lazy+Suspense可以让「宿主 → 远程 → 再远程」的嵌套依赖按需加载,配合exposes/remotes的容器声明即可实现组件级代码拆分;
  3. 以文档驱动演示:将 Markdown 说明文件作为页面的一部分(raw-loader导入 +markdown-to-jsx渲染),再配合同页面的可交互远程组件与 E2E 断言,让「文档描述、代码实现、自动化验证」三者保持一致。

如需继续深入,可对照阅读同目录下的 Dialog.md(React Portal 场景的远程 Dialog)与 UiLibrary.md(独立 CSS 方案下的远程 Button),它们共同构成了 comprehensive-demo-react18 中「跨应用组件复用」的完整示例集。

  • 示例工程

【免费下载链接】module-federation-examples

Implementation examples of module federation , by the creators of module federation

项目地址:https://gitcode.com/gh_mirrors/mo/module-federation-examples
点击查看免费下载
上一篇:SubFinder跨平台兼容性终极指南:Windows/macOS/Linux环境全面对比
下一篇:Evergreen UI自定义组件开发:扩展现有组件的5种实用模式

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 6:30:46

TensorFlow深度学习实战:从自动微分到模型部署的避坑指南

做机器学习这几年&#xff0c;TensorFlow大概是我每天都会碰到的名字。不管你是刚入门的初学者&#xff0c;还是已经在生产环境里跑模型的工程师&#xff0c;这套框架几乎绕不开。它的价值不在于“模型写起来有多方便”&#xff0c;而在于从数据管道、模型训练、调优到服务部署…

作者头像 李华
网站建设 2026/10/15 6:30:20

TensorFlow实战指南:从数据管道到模型部署全流程解析

做深度学习相关的开发&#xff0c;TensorFlow基本是绕不开的名字。这个项目从最初一个内部使用的数据流图工具&#xff0c;一路发展成覆盖研究、工程、部署、硬件的完整生态&#xff0c;我自己从1.x时代开始踩坑&#xff0c;再到2.x重构后正式将它作为主力框架用在多个图像分类…

作者头像 李华
网站建设 2026/10/15 6:29:54

Python环境搭建指南:从安装PyCharm到配置虚拟环境

简介&#xff1a;面向Python初学者的环境搭建指南文档&#xff0c;以Word格式呈现&#xff0c;内容从Python解释器的官方下载与安装、PyCharm集成开发环境的获取与部署&#xff0c;到通过License Server方式激活PyCharm&#xff0c;再到新建项目时正确关联已安装的Python解释器…

作者头像 李华
网站建设 2026/10/15 6:29:32

H3 完全上手指南:极简 HTTP 框架如何内置安全边界

H3 完全上手指南&#xff1a;极简 HTTP 框架如何内置安全边界 【免费下载链接】h3 ⚡️ Minimal H(TTP) framework built for high performance and portability 项目地址: https://gitcode.com/GitHub_Trending/h31/h3 H3 是一个可组合、运行时无关的极简 HTTP 框架&a…

作者头像 李华
网站建设 2026/10/15 6:28:47

PyTorch核心机制与工程实践:从张量自动求导到模型部署

1. 为什么大家都在聊 PyTorch&#xff1a;它在解决什么问题这几年只要聊到深度学习&#xff0c;PyTorch 几乎是绕不开的名字。不管你是做计算机视觉、自然语言处理&#xff0c;还是搞语音识别、推荐系统&#xff0c;PyTorch 都像是一个默认选项。我自己是从静态图框架时代一路用…

作者头像 李华
网站建设 2026/10/15 6:28:31

TensorFlow工程化核心:计算图、SavedModel与全栈部署实践

1. 这不是“学个框架”那么简单&#xff1a;TensorFlow到底在解决什么问题&#xff1f;很多人第一次听说TensorFlow&#xff0c;是在某次技术分享会上听到“谷歌开源的深度学习框架”&#xff0c;或者在招聘JD里看到“熟悉TensorFlow者优先”。但如果你真去翻它的GitHub仓库首页…

作者头像 李华