ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 @module-federation/vite 构建 Svelte 模块联邦:host 与 remote 实战指南

使用 @module-federation/vite 构建 Svelte 模块联邦:host 与 remote 实战指南
  • 示例工程

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

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

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

本指南以module-federation-vite-svelte示例为蓝本,讲解如何基于 Vite 与 Svelte(含 TypeScript)搭建模块联邦(Module Federation)架构下的宿主应用(host)与远程应用(remote),实现跨应用动态加载 Svelte 组件并共享第三方依赖。读完本文,你将掌握@module-federation/vite插件的核心配置项(name、remotes、exposes、shareScope)、pnpm workspace 下的启动流程,以及 host 侧通过动态import()实例化 remote 组件的完整方案。

一、示例概览:Svelte host + remote 架构

module-federation-vite-svelte示例在module-federation-examples仓库中演示了一个最基础的 Svelte 模块联邦场景:一个host(宿主)应用与一个remote(远程)应用各自独立开发、独立构建,运行时由 host 从 remote 拉取remoteEntry.js并加载其暴露的 Svelte 组件。整个目录结构如下:

module-federation-vite-svelte/ ├── package.json # workspace 脚本入口(install:deps / preview 等) ├── host/ # 宿主应用,运行于 4173 端口 │ ├── vite.config.ts # federation 插件 + svelte 插件 │ └── src/App.svelte # 动态 import remote 组件并实例化 └── remote/ # 远程应用,运行于 4174 端口 ├── vite.config.ts # exposes 暴露 './remote-app' └── src/App.svelte # 被 host 消费的 Svelte 组件

两个应用都是标准的 Vite + Svelte + TypeScript 模板:main.ts将App.svelte挂载到index.html的#app节点(见 host/src/main.ts 与 remote/src/main.ts),svelte.config.js统一启用vitePreprocess()预处理。差异全部集中在各自的vite.config.ts中,这正是模块联邦配置发挥作用的地方。

二、快速开始:两条命令跑通示例

原文档给出的启动步骤非常精简,从示例目录执行:

pnpm install:deps pnpm preview

随后在浏览器打开http://localhost:4173/即可看到 host 应用渲染,并在页面中加载出 remote 应用暴露的组件(如上方截图所示)。

这两条命令并非空壳,它们由 module-federation-vite-svelte/package.json 中的 workspace 脚本驱动:

脚本实际执行命令作用
install:depspnpm --filter module-federation-vite-svelte-* install只安装 host、remote 两个子包依赖
postinstall:depspnpm --filter module-federation-vite-svelte-* run build安装后自动构建两个应用
previewpnpm --filter module-federation-vite-svelte-* preview以生产构建产物启动预览服务
devpnpm --filter module-federation-vite-svelte-* dev开发模式(HMR)启动
buildpnpm --filter module-federation-vite-svelte-* build仅执行构建

仓库根目录的 pnpm-workspace.yaml 中已声明'module-federation-vite-svelte'与'module-federation-vite-svelte/*'为 workspace 包,因此pnpm --filter可以精确选中 host 与 remote。两个子包的package.json脚本也保持一致:dev、build、preview分别绑定各自端口——host 为4173(host/package.json),remote 为4174(remote/package.json)。

注意:原文档中的pnpm preview会同时启动 host(4173)与 remote(4174)两个服务。若你只希望单独调试 remote,可进入remote目录执行pnpm dev或pnpm preview;反之,host 单独启动时仍会尝试从http://localhost:4174/remoteEntry.js拉取 remote,因此联调时务必先保证 remote 已就绪。

三、remote 侧:如何暴露一个 Svelte 组件

remote 应用负责“生产”模块,核心配置集中在 remote/vite.config.ts:

import { federation } from '@module-federation/vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ svelte(), federation({ filename: 'remoteEntry.js', name: 'remote', exposes: { './remote-app': './src/App.svelte', }, remotes: {}, }), ], build: { target: 'chrome89', }, });

各配置项含义如下:

  • name: 'remote':联邦模块的唯一标识,host 通过该名称引用此 remote。
  • filename: 'remoteEntry.js':remote 的入口清单文件名,构建后由 host 的entry地址加载。
  • exposes:对外暴露的模块映射表。'./remote-app'是暴露的逻辑键名,'./src/App.svelte'是实际文件路径。host 侧动态import("remote/remote-app")中的remote-app即对应此键。
  • remotes: {}:remote 自身不消费任何其他联邦模块,因此留空。
  • build.target: 'chrome89':将构建产物目标定为 Chrome 89 及以上,保证现代浏览器语法兼容;在 Vite 的 module federation 场景下,该目标同时也约束了产物能被 host 端正常解析。

构建后,remote 会在根路径产出remoteEntry.js,即 host 需要拉取的入口文件。

四、host 侧:声明 remote 并配置共享作用域

host 应用负责“消费”模块,配置位于 host/vite.config.ts:

import { federation } from '@module-federation/vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ svelte(), federation({ name: 'host', remotes: { remote: { type: 'module', name: 'remote', entry: 'http://localhost:4174/remoteEntry.js', entryGlobalName: 'remote', shareScope: 'default', }, }, exposes: {}, filename: 'remoteEntry.js', }), ], build: { target: 'chrome89', }, });

host 端需要重点理解以下几个字段:

  • remotes.remote:声明一个名为remote的远程联邦模块,键名remote将作为 host 代码中import("remote/...")的命名空间前缀。
  • type: 'module':指定 remote 产物以 ESM 模块形式加载,这是 Vite 场景下@module-federation/vite推荐的加载类型。
  • entry: 'http://localhost:4174/remoteEntry.js':remote 入口文件的完整 URL。开发与预览模式下,remote 的 4174 端口必须与本地址一致。
  • entryGlobalName: 'remote':与 remote 的name对应,用于运行时全局标识。
  • shareScope: 'default':指定共享作用域名称。default是联邦运行时默认的共享 scope,host 与 remote 只有处于同一共享 scope 时,第三方依赖(如 rxjs)才能被正确去重与共享。
  • exposes: {}/filename:host 自身不暴露任何模块,但仍保留remoteEntry.js的默认输出名称(若 host 未来也要被其他应用消费,可在此补充exposes)。

由于 host 与 remote 均设置了build.target: 'chrome89',两者产物的语法基线保持一致,避免了跨应用加载时的兼容性断层。

五、跨应用组件加载:动态 import 与手动实例化

host 消费 remote 组件的方式极具 Svelte 特色——通过动态import()拿到组件类后,手动new实例化并指定挂载目标。核心代码位于 host/src/App.svelte:

<script lang="ts"> import { of, tap } from 'rxjs'; let remoteAppTarget; (async () => { //@ts-ignore const app = await import("remote/remote-app"); new app.default({ target: remoteAppTarget }); })(); of('emit') .pipe(tap(() => console.log("I'm RxJs from host"))) .subscribe(); </script> <div class="host"> <div class="card">…I'm the host app…</div> </div> <!-- 将 DOM 节点绑定给 remote 组件作为挂载目标 --> <div bind:this={remoteAppTarget} />

这里有两个关键机制:

  1. 动态命名空间导入:import("remote/remote-app")中的remote对应 host 配置里remotes的键名,remote-app对应 remote 配置里exposes的键名'./remote-app'。运行时该语句会被联邦运行时替换为对http://localhost:4174/remoteEntry.js的按需加载。
  2. bind:this+new app.default():Svelte 组件实例化时需要传入target(挂载 DOM 节点)。host 用bind:this={remoteAppTarget}把模板中的<div>节点引用交给变量,随后在异步回调中执行new app.default({ target: remoteAppTarget }),从而把 remote 的App.svelte渲染到 host 页面里。这也是 Svelte 生态中消费联邦组件的主流方式——不同于 React 的 JSX 直接渲染,Svelte 的编译器产物天然以“组件构造器 + target”的形式挂载。

remote 侧的 remote/src/App.svelte 则是一个自带样式的独立卡片组件,同时也在自身挂载时通过 rxjs 输出"I'm RxJs from remote"日志,用于验证依赖共享链路。

六、依赖共享实践:rxjs 的双端验证

该示例的妙处在于通过rxjs演示了第三方依赖如何在 host 与 remote 之间共享:

  • host 的 App.svelte 中import { of, tap } from 'rxjs'并订阅,控制台输出"I'm RxJs from host";
  • remote 的 App.svelte 同样引入 rxjs,控制台输出"I'm RxJs from remote";
  • 两个子包的 package.json 都将rxjs: ^7.6.0声明在dependencies中,且版本一致。

配合 host 配置中的shareScope: 'default',联邦运行时会在加载 remote 时优先复用 host 已实例化的 rxjs 单例,避免同一依赖在页面中被重复打包、重复执行。打开浏览器控制台即可同时看到两条日志,这是验证“依赖共享是否生效”最直观的检查点。

七、运行环境与版本基线

示例当前的依赖基线(以两个子包 package.json 为准):

  • @module-federation/vite1.11.0(联邦插件)
  • vite^5.4.8
  • svelte^4.2.19
  • @sveltejs/vite-plugin-svelte^3.1.2
  • typescript^5.5.3、svelte-check^4.0.4

TypeScript 配置统一继承@tsconfig/svelte,并开启allowJs/checkJs(见 host/tsconfig.json),使.svelte与.js文件都能获得类型检查。host 代码中import("remote/remote-app")前的//@ts-ignore注释,正是因为在静态类型层面 TypeScript 无法感知联邦运行时动态注册的模块命名空间——这是当前 Vite 联邦方案的常见类型局限,可通过自定义模块声明(如declare module 'remote/*')进一步缓解。

八、排错与调试建议

  1. 打开 4173 一片空白:检查 remote 是否已在 4174 端口正常提供remoteEntry.js。可先单独访问http://localhost:4174/remoteEntry.js确认文件可拉取。
  2. 控制台报模块加载失败:核对 host 配置中entry的端口号是否与 remote 实际预览端口一致(本示例固定为 4174)。
  3. 依赖重复加载:确认 host 与 remote 的shareScope名称一致(均为default),且 rxjs 版本声明兼容,否则联邦运行时不会走共享路径。
  4. 开发模式调试:在host/remote目录分别执行pnpm dev,可借助 Vite HMR 即时调试两端源码;pnpm preview则更贴近生产构建产物下的真实表现。

九、小结

module-federation-vite-svelte用最精简的双应用结构,完整覆盖了 Vite + Svelte 模块联邦的关键链路:remote 通过exposes暴露 Svelte 组件,host 通过remotes.entry声明远程入口,并以“动态import()+new Component({ target })”的方式完成跨应用渲染;同时以 rxjs 为例验证了shareScope: 'default'下的依赖共享机制。这套模式同样可参考仓库中module-federation-vite-react、module-federation-vite-vue3、module-federation-vite-solid等姊妹示例(配置结构高度一致,仅框架插件与 shared 声明不同),是理解模块联邦在 Vite 生态落地的理想起点。

  • 示例工程

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

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

项目地址:https://gitcode.com/gh_mirrors/mo/module-federation-examples
点击查看免费下载
上一篇:免费开源键鼠共享神器:3分钟搞定多设备无缝控制终极指南 🚀
下一篇:Simple Runtime Window Editor终极指南:打破游戏分辨率限制的免费解决方案

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进