Skip to content

Setting up module federation in React Router (framework mode) using rsbuild without a custom server script #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
neetigyachahar opened this issue Apr 23, 2025 · 1 comment

Comments

@neetigyachahar
Copy link

I have been trying to run the experimental module federation support in this plugin without writing a custom server file because I feel I may break something as it will be a large scale project and want to rely on the full capabilities of the framework and the plugin. Configuring a React Router project to use this rsbuild plugin was smooth, but, I used the federation example in this repo and tried to first configure a remote app and faced issues.

So, if I create a build and then start the build with the following configuration:

"scripts": {
  "build": "rsbuild build",
  "dev": "NODE_OPTIONS=\"--experimental-vm-modules --experimental-global-webcrypto\" rsbuild dev",
  "start": "react-router-serve ./build/server/static/js/app.js",
  "typecheck": "react-router typegen && tsc"
},
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReactRouter } from 'rsbuild-plugin-react-router';
import { pluginReact } from '@rsbuild/plugin-react';
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';

// Shared dependencies
const sharedDependencies = {
    react: { singleton: true },
    'react-dom': { singleton: true },
    'react-router': { singleton: true },
};

// Exposed modules
const exposedComponents = {
    './button': './app/components/Button.tsx',
};

export default defineConfig({
    environments: {
        web: {
            tools: {
                rspack: {
                    plugins: [
                        new ModuleFederationPlugin({
                            name: 'remote',
                            filename: 'remoteEntry.js',
                            exposes: exposedComponents,
                            shared: sharedDependencies,
                            library: { type: 'module' },
                        }),
                    ],
                },
            },
        },
        node: {
            tools: {
                rspack: {
                    plugins: [
                        new ModuleFederationPlugin({
                            name: 'remote',
                            filename: 'remoteEntry.js',           
                            exposes: exposedComponents,
                            shared: sharedDependencies,
                            library: { type: 'commonjs-module' },  
                            runtimePlugins: ['@module-federation/node/runtimePlugin'],
                            manifest: {
                                additionalData: ({ stats }) => {
                                    stats.metaData.ssrRemoteEntry = stats.metaData.remoteEntry;
                                    return stats;
                                },
                            },
                        }),
                    ],
                },
            },
        },
    },
    plugins: [
        pluginReactRouter({
            // Optional: Enable custom server mode
            customServer: false,
            // Optional: Specify server output format
            serverOutput: "module",
            //Optional: enable experimental support for module federation
            federation: true
        }),
        pluginReact()
    ],
})

I get the below error:

Image

I'm just few days into bundlers, so I may be wrong somewhere silly, but, here what I have observed is, I've console.log the build app.js exports, created by the plugin, that react-router-serve receives in the remote-1/node_modules/@react-router/serve/dist/cli.js, and it is receiving functions with promises, but the actual resolved values are expected. So I wrote an adaptor server file which will import all the bundle exports and export them after executing the functions and it worked.

// Import for local reference
import * as originalBuild from './build/server/static/js/app.js';

console.log('Original build:', originalBuild);

// Resolve all promise-returning functions from app.js
const resolveExports = async () => {
  // Each of these functions returns a Promise that resolves to the actual value
  const assetsValue = await originalBuild.assets();
  const assetsBuildDirectoryValue = await originalBuild.assetsBuildDirectory();
  const basenameValue = await originalBuild.basename();
  const entryValue = await originalBuild.entry();
  const futureValue = await originalBuild.future();
  const isSpaValue = await originalBuild.isSpaMode();
  const prerenderValue = await originalBuild.prerender();
  const publicPathValue = await originalBuild.publicPath();
  const routesValue = await originalBuild.routes();
  const ssrValue = await originalBuild.ssr();

  console.log('Resolved exports successfully');

  return {
    assets: assetsValue,
    assetsBuildDirectory: assetsBuildDirectoryValue,
    basename: basenameValue,
    entry: entryValue,
    future: futureValue,
    isSpaMode: isSpaValue,
    prerender: prerenderValue,
    publicPath: publicPathValue,
    routes: routesValue,
    ssr: ssrValue
  };
};

// Resolve and export all values
const resolvedExports = await resolveExports();

// Export individual values (not re-exporting from app.js)
export const assets = resolvedExports.assets;
export const assetsBuildDirectory = resolvedExports.assetsBuildDirectory;
export const basename = resolvedExports.basename;
export const entry = resolvedExports.entry;
export const future = resolvedExports.future;
export const isSpaMode = resolvedExports.isSpaMode;
export const prerender = resolvedExports.prerender;
export const publicPath = resolvedExports.publicPath;
export const routes = resolvedExports.routes;
export const ssr = resolvedExports.ssr;

// Log for debugging
console.log('Original build:', originalBuild);
console.log('Resolved exports:', resolvedExports);
"scripts": {
  "build": "rsbuild build",
  "dev": "NODE_OPTIONS=\"--experimental-vm-modules --experimental-global-webcrypto\" rsbuild dev",
  "start": "react-router-serve server.ts",
  "typecheck": "react-router typegen && tsc"
},
Image

All good till here, but the issue is with npm run dev command. I get the same above error with rsbuild dev command because now I think it internally generates dev build and serves it through HMR. I cannot get it working without writing a custom server and then use tsx dev-server.ts command like how similarly it is done in federation example.

Will it be possible to not use a custom server script and run federation?

I have pushed the code here for reference: https://github.com/neetigyachahar/react-router-rsbuild-module-federation-demo

Thank you for the efforts.

@neetigyachahar neetigyachahar changed the title Setting up module federation in React Router using rsbuild without a custom server script Setting up module federation in React Router (framework mode) using rsbuild without a custom server script Apr 23, 2025
@neetigyachahar
Copy link
Author

Ideally a federation example without custom server will help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant