- 示例工程
【免费下载链接】module-federation-examples
Implementation examples of module federation , by the creators of module federation
module-federation-vite-react是 module-federation-examples 仓库中演示Vite 与 React 组合下的模块联邦(Module Federation)的最小可运行示例:一个 Vite 构建的 host 应用在 4173 端口运行,通过运行时远程加载另一个 Vite 构建的 remote 应用(4174 端口),二者共享同一份 React 实例。读完本文,你将掌握在 Vite 项目中配置@module-federation/vite插件的完整参数、理解 host 端remotes与 remote 端exposes的对应关系,并能在本地一键启动并验证跨应用组件加载。
项目概览:Vite 场景下的 host/remote 双应用架构
官方 README 对该示例的定位非常简洁——"React host and remote"(README.md)。这个目录展示的是模块联邦经典的双应用拓扑在 Vite 生态中的落地方式:
- host(宿主):运行在
http://localhost:4173/,负责页面外壳,并在运行时按需拉取远端组件; - remote(远端):运行在
http://localhost:4174/,通过exposes对外暴露自己的 React 组件; - shared(共享目录):位于
module-federation-vite-react/shared/,是宿主与远端共用的代码与配置层面共享空间。
整套结构由根目录的 package.json 统一编排,通过 pnpm workspace 过滤(pnpm --filter module-federation-vite-react-*)同时管理 host 与 remote 两个子包。之所以强调 "Vite + React is now possible"(项目描述语),是因为模块联邦最初是 webpack 的能力,本示例证明了借助@module-federation/vite插件,Vite 构建的 React 应用同样可以成为模块联邦中的 host 与 remote。
快速开始:两条命令跑通整个示例
按照 README.md 的 Getting started 一节,在module-federation-vite-react目录下依次执行:
npm run install:deps npm run preview然后打开浏览器访问http://localhost:4173/,即可看到 host 应用与远端加载的 remote 应用同时渲染在同一页面。
两条命令背后的实际动作值得拆解(见 package.json):
| 命令 | 实际执行内容 | 作用 |
|---|---|---|
install:deps | pnpm --filter module-federation-vite-react-* install | 只为 host、remote 两个子包安装依赖 |
postinstall:deps | pnpm --filter module-federation-vite-react-* run build | 依赖安装完成后自动触发一次构建,保证后续 preview 可用 |
preview | pnpm --filter module-federation-vite-react-* preview | 分别构建并启动 host(4173)与 remote(4174)的静态预览服务 |
dev | pnpm --filter module-federation-vite-react-* dev | 以开发模式同时启动两个应用的 Vite dev server |
predev | node ../scripts/track-demo-run.js | 开发模式启动前的仓库级演示埋点脚本(scripts/track-demo-run.js) |
其中 host 与 remote 各自的preview脚本(见 host/package.json 与 remote/package.json)均为npm run build && vite preview --port 417x,即先执行tsc && vite build完成 TypeScript 检查与产物构建,再用vite preview提供静态服务。因此npm run preview一次即可让两个端口同时就绪。
目录结构:三个子目录的分工
module-federation-vite-react/ ├── docs/screenshot.png # README 中的运行效果截图 ├── host/ # 宿主应用(端口 4173) │ ├── src/ │ │ ├── components/Counter.tsx │ │ ├── App.tsx # 通过 lazy 动态加载远端组件 │ │ ├── main.tsx # ReactDOM.createRoot 入口 │ │ └── vite-env.d.ts │ ├── index.html │ ├── package.json │ └── vite.config.ts # host 端联邦配置 ├── remote/ # 远端应用(端口 4174) │ ├── src/ │ │ ├── components/Counter.tsx │ │ ├── App.tsx # 被 exposes 暴露的组件 │ │ ├── environment.ts # 构建期由插件动态生成的产物 │ │ └── main.tsx │ ├── index.html │ ├── package.json │ └── vite.config.ts # remote 端联邦配置 └── shared/ └── shared.ts # 两应用共享的类型/状态模块两侧源码几乎对称,差异集中在联邦配置与各自承载的职责上。
host 端配置详解:remotes声明远程来源
host 的联邦配置位于 host/vite.config.ts:
import { federation } from '@module-federation/vite'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import { dependencies } from './package.json'; export default defineConfig(() => ({ server: { fs: { allow: ['.', '../shared'] } }, build: { target: 'chrome89', }, plugins: [ federation({ name: 'host', remotes: { remote: { type: 'module', name: 'remote', entry: 'http://localhost:4174/remoteEntry.js', entryGlobalName: 'remote', shareScope: 'default', }, }, exposes: {}, filename: 'remoteEntry.js', shared: { react: { requiredVersion: dependencies.react, singleton: true, }, }, }), react(), ], }));关键配置项逐一说明:
name: 'host':当前应用在联邦体系中的唯一标识,同时会作为构建产物中运行时模块的命名空间前缀;remotes.remote:声明一个名为remote的远端。对象内部各字段:type: 'module':远端产物以原生 ES Module 形式加载(Vite 场景的默认形态,区别于 webpack 常用的var/UMD 形式);name: 'remote':远端应用自身声明的联邦名,必须与 remote 端配置中的name一致;entry: 'http://localhost:4174/remoteEntry.js':远端入口文件的完整 URL,host 运行时通过加载该入口获取远端模块清单;entryGlobalName: 'remote':远端入口在全局作用域暴露的变量名(与name对应,供运行时定位模块容器);shareScope: 'default':参与共享的依赖作用域名称,两侧需保持一致;
exposes: {}:host 端不对外暴露任何模块,因此为空对象;filename: 'remoteEntry.js':本应用生成联邦入口文件的文件名(host 自身也会生成,便于其他应用反过来引用它);shared:声明共享依赖,此处将react设为singleton: true,并指定requiredVersion: dependencies.react(即从 host/package.json 读取的^18.3.1)。这样 host 与 remote 加载的是同一份 React 运行时,避免出现双 React 实例导致的 hooks 状态错乱问题。
除联邦配置外,server.fs.allow: ['.', '../shared']允许 Vite dev server 访问应用根目录之外的shared目录;build.target: 'chrome89'则把产物目标锁定到 Chrome 89 及以上的现代浏览器,保证 ES Module 等特性可用。
remote 端配置详解:exposes暴露组件
remote 的联邦配置位于 remote/vite.config.ts:
import { federation } from '@module-federation/vite'; import react from '@vitejs/plugin-react'; import { writeFileSync } from 'fs'; import { defineConfig, loadEnv } from 'vite'; import { dependencies } from './package.json'; export default defineConfig(({ mode }) => { const selfEnv = loadEnv(mode, process.cwd()); return { server: { fs: { allow: ['.', '../shared'] } }, build: { target: 'chrome89' }, plugins: [ { name: 'generate-environment', options: function () { console.info('selfEnv', selfEnv); writeFileSync( './src/environment.ts', `export default ${JSON.stringify(selfEnv, null, 2)};`, ); }, }, federation({ filename: 'remoteEntry.js', name: 'remote', exposes: { './remote-app': './src/App.tsx', }, remotes: {}, shared: { react: { requiredVersion: dependencies.react, singleton: true, }, }, }), react(), ], }; });与 host 端形成对照的核心差异:
exposes: { './remote-app': './src/App.tsx' }:将src/App.tsx以remote/remote-app的模块标识对外发布。host 端import('remote/remote-app')中的remote对应remotes中声明的远端名,remote-app对应这里exposes的键;remotes: {}:remote 自身不再依赖其他远端,拓扑到此为止;name: 'remote'与filename: 'remoteEntry.js':与 host 端remotes声明中的name、entry文件名精确匹配,这正是两侧配置能够握手的前提。
此外 remote 端额外内嵌了一个名为generate-environment的自定义 Vite 插件:它在options钩子中通过loadEnv(mode, process.cwd())读取当前模式(development/production)下的环境变量,并把结果序列化写入src/environment.ts(仓库中的 environment.ts 即为该构建过程的生成产物)。这意味着 remote 可以按构建模式注入运行时环境信息,体现了本示例在演示联邦之外附带的环境变量处理手法。
运行时消费:lazy + Suspense 动态加载远端组件
host 在 host/src/App.tsx 中消费远端组件,使用的正是 React 标准的代码分割 API:
import { lazy, Suspense, useEffect } from 'react'; import { of, tap } from 'rxjs'; import './App.css'; import Counter from './components/Counter'; const Remote = lazy( // @ts-ignore async () => import('remote/remote-app'), ); export default () => { useEffect(() => { of('emit') .pipe(tap(() => console.log("I'm RxJs from host"))) .subscribe(); }, []); return ( <> <div className="host">...</div> <Suspense fallback="loading..."> <Remote /> </Suspense> </> ); };要点:
lazy(() => import('remote/remote-app')):remote是 host 配置里声明的远端别名,remote-app是 remote 暴露的模块键。模块联邦运行时会在首次渲染时去加载http://localhost:4174/remoteEntry.js,再解析出对应组件;Suspense fallback="loading...":远端模块未加载完成前显示占位文本,加载完成后渲染 remote 组件;- 行内的
@ts-ignore是因为 Vite 场景下尚未为该虚拟模块生成类型声明; - host 自身还引入了
rxjs(host/package.json 中rxjs: ^7.8.1),并在useEffect中执行了一次订阅打印,用于演示 host 独立依赖正常工作的同时不干扰联邦加载。
remote 端 remote/src/App.tsx 是一个自包含的组件:渲染 "I'm the remote app" 标题和远程版 Counter,并在挂载时打印Remote useEffect。host 与 remote 各自拥有独立的Counter组件实现(host/src/components/Counter.tsx 与 remote/src/components/Counter.tsx),点击后分别累加各自的count状态,互不干扰——这也是验证两套 React 组件在同一页面共存的最直观方式。
共享目录与 TypeScript 路径别名
示例在应用目录之外维护了一个 shared/shared.ts:
export const state = { message: '<no message>', };它被两侧的 TypeScript 配置通过paths统一映射为shared别名,例如 host/tsconfig.json 与 remote/tsconfig.json 中均有:
"baseUrl": ".", "paths": { "shared": ["../shared/shared.ts"] }为了让 Vite 在 dev 模式下也能解析到该目录,host 与 remote 的vite.config.ts都配置了server.fs.allow: ['.', '../shared']。这一组合体现了模块联邦项目中"跨应用共享代码"的两种手段:既可以在运行时通过联邦共享依赖,也可以在构建期通过共享目录共享源码模块。
验证运行结果与排查要点
启动npm run preview后,可以在浏览器中进行如下验证:
- 访问
http://localhost:4173/,页面应同时渲染 host 卡片("I'm the host app")与远端卡片("I'm the remote app"),对应 README.md 中附图的效果; - 打开开发者工具 Network 面板,应能看到对
http://localhost:4174/remoteEntry.js的请求,以及随后按需拉取的 remote 模块分块; - 点击两个 Counter 按钮,数字各自累加,证明两份组件实例与状态彼此独立;
- 在 Console 面板应能看到 host 打印的
I'm RxJs from host与 remote 打印的Remote useEffect。
常见排查点:若远端组件持续显示 "loading...",优先确认 remote 的 preview 服务确实运行在 4174 端口,且 host 配置中entry的地址、name/entryGlobalName与 remote 端配置一致;若出现双 React 实例警告,检查两侧shared.react是否都设置了singleton: true且requiredVersion相互兼容。
小结:从本示例迁移到自有项目的三个步骤
基于 host/vite.config.ts 与 remote/vite.config.ts 的配置范式,可以总结出在 Vite 中搭建模块联邦应用的最小清单:
- remote 端:安装
@module-federation/vite,在plugins中加入federation({ name, exposes, filename, shared }),把要共享的组件写进exposes; - host 端:在
federation({ name, remotes })中声明远端,remotes.<别名>的entry指向 remote 的remoteEntry.jsURL,name与entryGlobalName与 remote 的name保持一致; - 运行时消费:在 host 组件中用
lazy(() => import('别名/暴露键'))+Suspense包装远端组件,并通过shared.singleton确保 React 等关键依赖只存在一份。
示例本身固定使用localhost:4174作为远端入口地址,适用于本地演示与开发环境;若要在生产环境部署,需将remotes.remote.entry替换为实际托管的remoteEntry.js地址,并配合对应的跨域与缓存策略。更多 Vite 模块联邦的变体(Angular、Vue3、Solid、Svelte 等框架)可在仓库的module-federation-vite-*系列目录中横向对照参考。
- 示例工程
【免费下载链接】module-federation-examples
Implementation examples of module federation , by the creators of module federation
相关推荐
module-federation-examples basic-host-remote:用 Modern.js 搭建最小 Host-Remote 模块联邦示例
module federation examples basic host remote:用 Modern.js 搭建最小 Host Remote 模块联邦示例
示例工程Vite + Vue 3 模块联邦主机应用实战:以 module-federation-vite-vue3 的 host 应用为例
Vite + Vue 3 模块联邦主机应用实战:以 module federation vite vue3 的 host 应用为例 导读 本文围绕开源仓库 mo
示例工程module-federation-examples 双向联邦实战:基于 Modern.js 的 Host/Remote 应用(App1)深度解析
module federation examples 双向联邦实战:基于 Modern.js 的 Host/Remote 应用(App1)深度解析 本文以 bi
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考