资讯动态

Module Federation + TypeScript Monorepo 实战:类型流式共享与 Jest 单元测试(typescript-react-monorepo-test 深度解析)

发布时间:2026/10/15 20:15:44 来源:尧图企业网站定制
示例工程【免费下载链接】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 仓库中的typescript-react-monorepo-test示例为蓝本系统讲解如何在 pnpm Monorepo 中搭建 TypeScript 版本的 Host/Remote 模块联邦应用如何通过FederatedTypesPlugin将远程组件的类型“流式”下发到宿主工程如何借助.env与REMOTE_URL动态指定远程地址以及如何在无需启动远程服务的前提下用 Jest 的moduleNameMapper对远程导入完成单元测试。读完本文你将掌握一套可复制的「TypeScript 模块联邦 Monorepo 单测」工程模板。一、示例定位与整体架构typescript-react-monorepo-test是一个双包Host/Remote的 React 18 TypeScript 示例核心目标是演示两件事一个基础的 Host/Remote 模块联邦应用通过module-federation/typescript的FederatedTypesPlugin实现远程模块类型的自动生成与共享streams types让宿主编译期就能拿到远程组件的完整 TypeScript 类型。示例采用 pnpm Monorepo 组织目录结构如下typescript-react-monorepo-test/ ├── package.json # 根脚本并发构建/启动 host 与 remote ├── packages/ │ ├── host/ # 宿主应用端口 3000 │ │ ├── configs/ # federationConfig.js webpack 多环境配置 │ │ ├── public/index.html │ │ └── src/ │ │ ├── __tests__/App.test.tsx # Jest 单元测试 │ │ ├── App.tsx # 懒加载远程 Button │ │ ├── ErrorBoundary.tsx │ │ ├── index.tsx │ │ └── remote.d.ts # 远程模块声明 │ └── remote/ # 远程应用端口 4000 │ ├── configs/ │ ├── public/index.html │ └── src/ │ ├── components/Button.tsx # 被暴露的远程组件 │ ├── App.tsx │ └── index.tsx两个包在 pnpm-workspace.yaml 中被注册为 workspace 成员typescript-react-monorepo-test与typescript-react-monorepo-test/*包名分别为typescript-react-monorepo-test_host与typescript-react-monorepo-test_remote见两个子包的 package.json。运行链路很简单远程应用在4000端口提供remoteEntry.js与暴露的Button组件宿主应用在3000端口运行通过模块联邦在运行时拉取远程模块并通过React.lazy实现按需加载。二、环境准备与安装运行按照 README 的说明安装依赖有两种路径# 方式一README 原始说明yarn npm install --global yarn yarn # 方式二本仓库实际采用 pnpm workspace 管理也可直接使用 pnpm 安装 pnpm install安装完成后在示例根目录即typescript-react-monorepo-test/执行启动命令REMOTE_URLhttp://localhost:4000 pnpm run start该命令会同时构建并启动 host 与 remote 两个应用分别运行在 3000 与 4000 端口宿主应用http://localhost:3000/远程应用http://localhost:4000/pnpm run start的并发编排定义在 typescript-react-monorepo-test/package.json 中{ scripts: { build: pnpm --parallel --filter typescript-react-monorepo-test_* run build, serve: wsrun --parallel serve, start: concurrently \wsrun --parallel start\ \wsrun --parallel serve\, prestart: node ../scripts/track-demo-run.js, preserve: node ../scripts/track-demo-run.js } }可以看到start由两部分并联组成wsrun --parallel start让各包进入webpack --watch监听构建wsrun --parallel serve让各包启动webpack serve开发服务器concurrently负责把两条命令捆绑执行。其中REMOTE_URL作为环境变量传入供宿主解析远程入口地址使用详见第四节。三、模块联邦核心配置FederationConfig示例把模块联邦配置独立成函数/对象文件便于复用与维护。3.1 宿主侧配置宿主侧配置位于 packages/host/configs/federationConfig.js它是一个接收REMOTE_URL参数的工厂函数const { dependencies } require(../package.json); const federationConfig REMOTE_URL { return { name: Host, filename: remoteEntry.js, remotes: { Remote: Remote${REMOTE_URL}/remoteEntry.js, }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies[react], }, react-dom: { singleton: true, requiredVersion: dependencies[react-dom], }, }, }; }; module.exports federationConfig;关键点解读remotes.Remote声明远程模块名为Remote入口地址为${REMOTE_URL}/remoteEntry.js。远程地址由环境变量注入这正是「运行时可切换远程地址」的入口。shared将package.json中的全部dependencies纳入共享范围并单独为react/react-dom配置singleton: true与requiredVersion。singleton保证整个应用中只存在一份 React 实例避免 Hook 状态错乱requiredVersion取自dependencies用于版本协商。3.2 远程侧配置远程侧配置位于 packages/remote/configs/federationConfig.js静态对象即可const { dependencies } require(../package.json); const federationConfig { name: Remote, filename: remoteEntry.js, exposes: { ./Button: ./src/components/Button, }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies[react], }, react-dom: { singleton: true, requiredVersion: dependencies[react-dom], }, }, }; module.exports federationConfig;远程侧通过exposes将./src/components/Button暴露为./Button宿主在业务代码中通过import(Remote/Button)即可引用Remote对应宿主remotes中声明的模块名./Button对应这里的暴露键。四、webpack 配置类型插件、环境变量与多环境拆分两个包的 webpack 配置均拆分为webpack.base.js公共部分、webpack.dev.js开发与webpack.prod.js生产使用webpack-merge合并。4.1 公共配置中的两个关键插件以宿主侧 packages/host/configs/webpack.base.js 为例const path require(path); const webpack require(webpack); const HtmlWebpackPlugin require(html-webpack-plugin); const { FederatedTypesPlugin } require(module-federation/typescript); const { ModuleFederationPlugin } require(module-federation/enhanced/webpack); const dotenv require(dotenv).config({ path: path.join(__dirname, ../.env), }); const federationConfig require(./federationConfig); module.exports { entry: { main: path.join(__dirname, ../src/index.tsx) }, output: { publicPath: auto }, module: { rules: [ { test: /\.(js|ts)x?$/, exclude: /node_modules/, use: { loader: babel-loader, options: { presets: [ babel/preset-typescript, [babel/preset-env, { useBuiltIns: usage, corejs: 3 }], babel/preset-react, ], }, }, }, ], }, plugins: [ new webpack.DefinePlugin({ process.env: dotenv.parsed }), new ModuleFederationPlugin(federationConfig(process.env.REMOTE_URL)), new FederatedTypesPlugin({ federationConfig: federationConfig(process.env.REMOTE_URL), }), new HtmlWebpackPlugin({ template: public/index.html, title: Host App, filename: index.html, chunks: [main], }), ], resolve: { extensions: [.tsx, .ts, .jsx, .js, .json] }, target: web, };这里有三个值得注意的实现细节dotenv加载.envwebpack 配置执行阶段会读取 host 包根目录下的.env文件并将其解析结果通过DefinePlugin注入为process.env。ModuleFederationPlugin与FederatedTypesPlugin使用同一份federationConfig(process.env.REMOTE_URL)前者负责运行时模块联邦后者负责类型流式生成——这也是本示例区别于普通联邦示例的核心亮点。TypeScript 编译交给 Babelbabel-loader依次应用babel/preset-typescript、babel/preset-env与babel/preset-react因此无需ts-loader参与构建期类型检查。远程侧 packages/remote/configs/webpack.base.js 结构基本一致只是直接使用静态的federationConfig远程侧不依赖REMOTE_URL。4.2 开发与生产环境差异开发环境host 的 webpack.dev.js特征module.exports merge(webpackBaseConfig, { mode: development, devtool: eval, cache: false, optimization: { minimize: false }, devServer: { static: { directory: path.join(__dirname, ../dist) }, port: 3000, hot: true, compress: false, historyApiFallback: false, }, });生产环境host 的 webpack.prod.js则开启压缩与缓存module.exports merge(webpackBaseConfig, { mode: production, devtool: cheap-source-map, cache: true, optimization: { minimize: true }, devServer: { static: { directory: path.join(__dirname, ../dist) }, port: 3000, compress: true, historyApiFallback: true, }, });远程侧对应配置webpack.dev.js、webpack.prod.js仅在 devServer 端口上不同4000其余开关策略一致。4.3 用.env与REMOTE_URL动态控制远程地址README 明确指出Use the .env file in the host project to change the remote address.宿主工程的.env文件用于设置远程地址的默认值该文件已在 host 的 .gitignore 中被忽略属于本地环境配置。结合代码调用链可以理清优先级启动时若显式传入REMOTE_URLhttp://localhost:4000该值直接作为 shell 环境变量存在webpack.base.js中dotenv.config()读取packages/host/.env但dotenv 默认不覆盖已存在的环境变量因此命令行传入的REMOTE_URL优先federationConfig(process.env.REMOTE_URL)将该值拼进Remote${REMOTE_URL}/remoteEntry.jsDefinePlugin将.env中的其余变量注入到前端代码的process.env中。这套设计让远程地址既可以在命令行临时指定REMOTE_URLhttp://localhost:4000 pnpm run start也可以沉淀到.env中作为团队默认值是模块联邦工程化中非常实用的「环境可配置」范式。五、类型流式共享从FederatedTypesPlugin到mfTypes5.1 远程组件导出真实类型远程暴露的 Button.tsx 除了组件本身还导出了ButtonProps接口import { FC, ReactNode } from react; import React from react; export interface ButtonProps { children: ReactNode; name: string; } const Button: FCButtonProps ({ children, name }) { return button name{name}{children}/button; }; export default Button;远程侧 App.tsx 直接消费该组件渲染自身页面。而宿主想要在编译期就拿到ButtonProps的类型定义靠的就是FederatedTypesPlugin。5.2 宿主的类型消费方式宿主业务代码位于 packages/host/src/App.tsximport React from react; import RemoteButtonProps from mfTypes/Button; const RemoteButton React.lazy(() import(Remote/Button)) as typeof RemoteButtonProps; import ErrorBoundary from ./ErrorBoundary; const App () { return ( div h1Host Website/h1 h2this button import from Remote project:/h2 ErrorBoundary RemoteButton namehost-buttonRemote button/RemoteButton /ErrorBoundary /div ); }; export default App;要点拆解import(Remote/Button)是模块联邦的运行时懒加载入口返回的是一个组件构造函数mfTypes/Button是类型流式生成的产物FederatedTypesPlugin在构建/监听时会把远程暴露模块的类型声明写入宿主的mf-types/Remote/_types目录该目录已被 gitignore属于生成物as typeof RemoteButtonProps将懒加载组件断言为远程 Button 的类型形状使宿主侧namehost-button、children 等 props 均获得类型检查远程组件被ErrorBoundaryErrorBoundary.tsx包裹远程加载失败时展示Something went wrong.兜底 UI。类型别名映射定义在 packages/host/tsconfig.json{ compilerOptions: { target: ESNext, module: umd, lib: [ESNext, dom], jsx: react, noEmit: true, strict: true, noImplicitAny: true, strictNullChecks: true, moduleResolution: node, forceConsistentCasingInFileNames: true, esModuleInterop: true, baseUrl: ., paths: { mfTypes/*: [./mf-types/Remote/_types/*] }, types: [testing-library/jest-dom] }, include: [src] }其中paths把mfTypes/*指向插件生成的mf-types/Remote/_types/*这就是「类型从远程流到宿主」的最后一环。此外宿主还提供了一份兜底声明文件 remote.d.ts///reference typesreact / declare module Remote/Button;当类型流尚未生成或缺失时它保证import(Remote/Button)至少能被 TypeScript 接受为合法模块声明。说明README 中还提到可借助 webpack 的resolve.alias让构建期解析远程导入就本仓库的实际配置而言运行时远程导入由ModuleFederationPlugin解析测试期远程导入由 Jest 的moduleNameMapper解析见下一节webpack 配置中并未出现resolve.alias。六、Jest 单元测试Monorepo 是前提示例最有工程价值的部分是在不启动远程服务的前提下为宿主编写并运行 Jest 单元测试。6.1 用moduleNameMapper解析远程导入Jest 配置位于 packages/host/jest.config.jsconst config { verbose: true, preset: ts-jest, testEnvironment: jsdom, moduleNameMapper: { Remote/Button: rootDir/../remote/src/components/Button, }, transform: { ^.\\.tsx?$: ts-jest, }, testMatch: [**/__tests__/**/*.[jt]s?(x)], }; module.exports config;关键机制moduleNameMapper将业务代码中的Remote/Button直接映射到 Monorepo 内远程包的本地源码文件rootDir/../remote/src/components/Button。测试时 Jest 不加载真实远程 bundle而是静态解析本地源码从而绕开网络与运行时容器preset: ts-jesttransform负责把.tsx/.ts编译为可执行 JStestEnvironment: jsdom提供 DOM 环境配合testing-library/react渲染组件testMatch限定测试文件位于__tests__目录。这里可以清晰看到Monorepo 是硬性前提README 的 Tip 也明确提示Monorepo is requirement for jest to run.因为moduleNameMapper中的目标路径../remote/src/components/Button依赖 host 与 remote 两个包共存于同一仓库根目录如果它们是独立仓库该相对路径将无法解析。6.2 测试用例实现测试用例位于 packages/host/src/tests/App.test.tsximport React from react; import { screen, render } from testing-library/react; import testing-library/jest-dom; import App from ../App; describe(App, () { it(should have remote button, async () { render(App /); expect(await screen.findByRole(button, { name: Remote button })).toBeInTheDocument(); }); });测试渲染整个App断言页面中出现了来自远程的按钮文本Remote button。由于App内部使用了React.lazy懒加载断言采用findByRole异步查询等待组件挂载完成——这与懒加载渲染的时序完全匹配。6.3 运行方式与脚本编排进入 host 包目录执行即可cd packages/host yarn test # 等价于包内脚本 test: jest 宿主 package.json 中与测试、类型检查相关的脚本如下{ scripts: { start: webpack --watch --config configs/webpack.dev.js, build: webpack --progress --config configs/webpack.prod.js, serve: webpack serve --config configs/webpack.dev.js, dev: npm-run-all --parallel start serve, tscheck: tsc, test: jest } }tscheck用于独立执行tsc类型检查宿主与远程包的tsconfig.json均开启strictnoImplicitAnystrictNullChecks与jest单测形成「类型 行为」双保险。宿主侧测试相关依赖Jest 29.7.0、ts-jest 29.1.5、testing-library/react 16.0.0、jest-environment-jsdom 等也都在该 package.json 的devDependencies中完整列出可直接复现。七、运行与测试的完整心智模型综合以上各节这个示例形成了清晰的「三阶段解析」分工阶段场景解析机制依据文件构建/监听webpack --watch、webpack serveModuleFederationPlugin解析Remote/ButtonFederatedTypesPlugin生成mf-types类型webpack.base.js类型检查tsc/ IDE 智能提示tsconfig.json的paths将mfTypes/*指向生成的类型目录tsconfig.json单元测试jestmoduleNameMapper将Remote/Button映射到远程包本地源码jest.config.js这套组合拳的收益在于开发期获得完整的远程组件类型提示杜绝 props 写错测试期不依赖网络、不依赖远程服务可用性单测稳定可复现生产期依旧享受模块联邦的运行时解耦与独立部署能力。八、小结typescript-react-monorepo-test虽然是一个“basic”示例但它同时踩通了模块联邦在 TypeScript Monorepo 场景下的三个关键工程点类型流式共享FederatedTypesPlugin把远程组件类型生成到宿主mf-types目录配合 tsconfigpaths别名实现编译期类型安全环境可配置远程地址REMOTE_URL环境变量 host 包.env文件让远程入口地址可随环境切换可测试的联邦代码JestmoduleNameMapper把远程导入静态映射到同仓库源码使React.lazy联邦组件也能被稳定单测而 Monorepo 布局正是这一方案得以成立的前提。如果你正在规划 TypeScript 化的微前端联邦工程建议直接以本示例为起点先跑通REMOTE_URLhttp://localhost:4000 pnpm run start再进入packages/host执行yarn test观察模块映射效果最后对照本文第五节修改远程组件 props 验证类型流是否实时同步到宿主。延伸阅读类型流方案更完整的姊妹示例typescript-react-monorepo含多页面路由与global.d.ts全局声明单包版类型共享示例typescript本项目相关的其他 React 联邦示例总览仓库根 README。赞分享示例工程【免费下载链接】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 实战用 module-federation/typescript 为 Module Federation 远程模块“流式”生成类型module federation examples 实战用 module federation/typescript 为 Module Federatio示例工程RK3568 刷 Armbian 家庭服务器完整指南3 步把安卓盒子改成家用小服务器RK3568 刷 Armbian 家庭服务器完整指南3 步把安卓盒子改成家用小服务器 amlogic s9xxx armbian 主打一件事给 RK3568示例工程Firebase Cloud Functions 单元测试快速上手Jest TypeScript 实战指南test-functions-jest-ts 示例深度解析Firebase Cloud Functions 单元测试快速上手Jest TypeScript 实战指南test functions jest ts示例工程上一篇Prusa-Firmware配置与定制化打造专属3D打印体验下一篇Mamba_State_Space_Model_Paper_List开发者指南如何贡献和维护SSM论文资源库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑