TanStack

TanStack Start 和 TanStack Router 应用使用 Vite 构建,因此可以直接使用 @module-federation/vite。TanStack Start 宿主应用可以在服务端和浏览器中消费远程模块:远程组件会被渲染进 SSR 响应,然后进行水合。

纯客户端的 TanStack Router 应用就是一个普通的 Vite 应用,请参考 Vite 指南。本页其余部分介绍 TanStack Start 的配置。

支持

  • 通过 Nitro 进行服务端渲染的 TanStack Start 宿主应用
  • 使用 Vite 构建的远程应用,例如导出组件的 React 库
  • 以单例形式共享 react、react-dom 以及你自己的包

安装

npm
yarn
pnpm
bun
npm add @module-federation/vite --save-dev

配置远程应用

远程应用是一个导出组件的普通 Vite React 应用:

remote/vite.config.ts
import { federation } from '@module-federation/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    federation({
      name: 'remote',
      filename: 'remoteEntry.js',
      exposes: {
        './Widget': './src/components/Widget',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^19.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
      },
    }),
    react(),
  ],
  server: {
    port: 4174,
    cors: true,
    origin: 'http://localhost:4174',
  },
  preview: {
    port: 4174,
    cors: true,
  },
});

配置 TanStack Start 宿主应用

在 tanstackStart() 之前添加 federation():

host/vite.config.ts
import { federation } from '@module-federation/vite';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import react from '@vitejs/plugin-react';
import { nitro } from 'nitro/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  nitro: {
    // 将这些依赖保留为 Node externals,使宿主应用和远程应���
    // 在 SSR 期间共享同一个 React 实例。
    traceDeps: [
      'react',
      'react-dom',
      '@module-federation/runtime',
      '@module-federation/runtime-core',
      '@module-federation/sdk',
    ],
  },
  plugins: [
    federation({
      name: 'host',
      hostInitInjectLocation: 'entry',
      remotes: {
        remote: {
          type: 'module',
          name: 'remote',
          entry: 'http://localhost:4174/remoteEntry.js',
        },
      },
      shared: {
        react: { singleton: true, requiredVersion: '^19.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
      },
    }),
    tanstackStart(),
    react(),
    nitro(),
  ],
  ssr: {
    optimizeDeps: {
      include: ['react', 'react-dom'],
    },
  },
});

有两项配置对 SSR 至关重要:

  • hostInitInjectLocation: 'entry' 会将宿主应用的初始化代码注入入口脚本。TanStack Start 没有 index.html,这一配置可确保 federation 运行时在 hydrateRoot 执行之前就绪。
  • nitro.traceDeps 会在 Nitro 服务端产物中将 React 和 federation 运行时保留为 Node externals。如果不配置,Nitro 会将 React 内联打包,导致远程应用使用不同的 React 实例,从而破坏 hooks 和 context。

加载远程组件

使用 React.lazy 导入远程模块,并在路由的 loader 中预加载它们,以便 federation 运行时在渲染之前于服务端完成解析。如果不进行预加载,懒加载组件在 SSR 期间尚未解析完成,将不会渲染任何内容。

host/src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router';
import { lazy, Suspense } from 'react';

const RemoteWidget = lazy(() => import('remote/Widget'));

export const Route = createFileRoute('/')({
  loader: () => import('remote/Widget').then(() => null),
  component: IndexPage,
});

function IndexPage() {
  return (
    <Suspense fallback={<div>Loading remote widget...</div>}>
      <RemoteWidget />
    </Suspense>
  );
}

如果希望在远程模块不可用时页面仍能继续渲染,请使用错误边界(error boundary)包裹远程组件。

声明远程模块以便 TypeScript 能够解析它们,或者启用 dts 自动下载远程类型:

host/src/types/remote.d.ts
declare module 'remote/Widget' {
  import type { ComponentType } from 'react';
  const Widget: ComponentType;
  export default Widget;
}

示例