Skip to content
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

[Bug]: MF breaks the asset path when more than one sub path #2482

Closed
XavierLeTohic opened this issue May 31, 2024 · 30 comments
Closed

[Bug]: MF breaks the asset path when more than one sub path #2482

XavierLeTohic opened this issue May 31, 2024 · 30 comments
Labels
bug Something isn't working

Comments

@XavierLeTohic
Copy link

XavierLeTohic commented May 31, 2024

Version

System:
    OS: macOS 14.4.1
    CPU: (8) arm64 Apple M1
    Memory: 41.70 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Chrome: 125.0.6422.141
    Safari: 17.4.1
  npmPackages:
    @rsbuild/core: ^0.7.1 => 0.7.1 
    @rsbuild/plugin-react: ^0.7.1 => 0.7.1

Details

When setting moduleFederation.options and reloading with more than one sub path (.i.e /sub/path) Rsbuild inject assets without the prefix / even setting output.assetPrefix: "/" resulting in a white page.

Commenting or removing the whole moduleFederation from rsbuild config and everything works well.

See the attached Codesandbox and repository to reproduce.

Reproduce link

https://codesandbox.io/p/github/XavierLeTohic/rsbuild-mf-asset-path/main

Reproduce Steps

The repo of the codesandbox is here:

https://github.com/XavierLeTohic/rsbuild-mf-asset-path

To reproduce:

  1. Clone
  2. npm install or yarn install
  3. npm run dev or yarn dev
  4. On http://localhost:5050/example everything will work as expected if you reload
  5. Go to http://localhost:5050/example/sub/path and reload it will be broken
  6. Comment the whole module federation in rsbuild.config.ts and try to reload it'll work
@XavierLeTohic XavierLeTohic added the bug Something isn't working label May 31, 2024
@chenjiahan
Copy link
Member

output.assetPrefix is for production build, can you try the dev.assetPrefix config?

Note that when you use moduleFederation.options, Rsbuild will set the default value of Rspack's output.publicPath configuration to 'auto', this is recommended by MF team.

@XavierLeTohic
Copy link
Author

dev.assetPrefix helped me discover that the reason why it's not working it's that it does not find assets using rsbuild dev and with module federation no matter the prefix I use, I updated the Codesandbox to reproduce, any idea?

Note that output.publicPath does not exists according to the types in 0.7.1 even tho it's mentioned in the documentation.

Screenshot 2024-06-03 at 10 14 07
Screenshot 2024-06-03 at 10 26 34

@chenjiahan
Copy link
Member

output.publicPath is a config of Rspack, you can use Rsbuild's tools.rspack to configure it.

@ScriptedAlchemy
Copy link
Contributor

Is public path set to auto?

@XavierLeTohic
Copy link
Author

Was fixed in dev by setting config.output.publicPath = '/' since config.output.publicPath = 'auto' still break the injection of assets when more than one sub path is used and the page is reloaded.

Updated the codesandbox to demo when it fails with auto -> https://codesandbox.io/p/github/XavierLeTohic/rsbuild-mf-asset-path/main

@imzisy
Copy link

imzisy commented Jun 5, 2024

I tried to change publicPath to / but it is not working for the host app.

@XavierLeTohic
Copy link
Author

XavierLeTohic commented Jun 5, 2024

I tried to change publicPath to / but it is not working for the host app.

Did you setup MF via appendPlugins like below?

tools: {
		rspack: (config, { appendPlugins }) => {
			config.output.publicPath = "/";

			appendPlugins([
				new ModuleFederationPlugin({ // ...my options })
			]);
		},
	},

@imzisy
Copy link

imzisy commented Jun 5, 2024

I tried to change publicPath to / but it is not working for the host app.

Did you setup MF via appendPlugins like below?

tools: {
		rspack: (config, { appendPlugins }) => {
			config.output.publicPath = "/";

			appendPlugins([
				new ModuleFederationPlugin({ // ...my options })
			]);
		},
	},

I'm using the v1 moduleFederation.options .

@XavierLeTohic
Copy link
Author

No certain but I think you can find the plugin of the 1.5 from @rspack/core's container

@nate-summercook
Copy link

maybe related to #2747 ?

@imzisy
Copy link

imzisy commented Jul 4, 2024

To resolve this issue, upgrade to the latest version of Module Federation.

@XavierLeTohic
Copy link
Author

XavierLeTohic commented Jul 4, 2024

@imzisy this issue also happen with the built-in module federation of rsbuild, not only using the external plugin @module-federation/enhanced/rspack

@XavierLeTohic
Copy link
Author

After upgrading to ^1.0.0-alpha.6 it fixed the issue using tools.rspack but the issue remain with the built-in moduleFederation.options

@qmakani
Copy link
Contributor

qmakani commented Sep 21, 2024

this doesn't seem like an issue anymore. should be safe to close it?

@XavierLeTohic
Copy link
Author

Fixed on the latest versions

@petrosD93
Copy link

@XavierLeTohic whats the resolution for this? It's not clear on how we can resolve this issue as I'm currently facing the same. By upgrading to @rsbuild/core latest, can we still use the built-in moduleFederation.options?

@XavierLeTohic
Copy link
Author

@petrosD93 we ended up setting MF options using:

import { defineConfig } from '@rsbuild/core';

export default defineConfig({
  tools: {
    rspack: (config, { appendPlugins, rspack }) => {
      appendPlugins(
        [
          new rspack.container.ModuleFederationPlugin({ 
            // mf config 
          }),
        ],
      );
    }
  }
});

I thought that issue would have been fixed using moduleFederation.options since then, never tried it again

@ScriptedAlchemy
Copy link
Contributor

ScriptedAlchemy commented Nov 13, 2024

https://module-federation.io/guide/basic/rsbuild.html

@petrosD93
Copy link

@ScriptedAlchemy thanks for pointing this out, I was following the documentation of RsBuild. My issue seems to be more complex though. I am trying to transform our projects from Webpack to RsBuild. Thing is I am using the API of MF to load remotes (that was done in order to be able to change MF Config of remotes at runtime, and to also ensure proper error handling of host application when one of the remotes are failing). I understand that the logic we used back then was depending on fetching remote js modules, where now we do fetch the mf-manifest.json. I wonder if there is any documentation on how to use the API of module federation with RsBuild and the module federation plugin

@ScriptedAlchemy
Copy link
Contributor

Federation runtime might be useful?

https://module-federation.io/guide/basic/runtime.html

Primarily the main thing is you should be using the package from us and not the builtin container object in the bundler. Since those just expose the base classes from rust side of rspack.

@VictorPulzz
Copy link

VictorPulzz commented Feb 4, 2025

@ScriptedAlchemy
I faced the same problem, when I used in rsbuild config this setting, publicPath : 'auto'

  tools: {
      ...config.tools,
      rspack: {
        ...config.tools?.rspack,
        output: {
          publicPath: 'auto',
          // clean: true,
        },
      },
    },

, everything worked, but then I started to configure react tanstack router and realized that internal routes do not work because the js script is inserted

<script defer src="static/js/vendor. js“></script><script defer src=”static/js/index.js"></script>, 

so all internal routes, say /dashboard/edit, don't work. When I removed publicPath: auto, the router started working and in dev mode mf works, but in production build, the microfrontend stopped working:

Image
Please help....

You can see that the paths referenced by module federation are wrong, it inserts relative paths localhost:3000, but the bindle files themselves are localhost:3051 (in remove app).

Image

I using: @module-federation/rsbuild-plugin

@VictorPulzz
Copy link

@XavierLeTohic Can you help please?

@XavierLeTohic
Copy link
Author

Have you tried using module federation directly getting it from the container like in my last comment above?

I haven't tried @module-federation/rsbuild-plugin yet.

@VictorPulzz
Copy link

VictorPulzz commented Feb 4, 2025

@XavierLeTohic Yes, this is my config:

import type { moduleFederationPlugin } from '@module-federation/sdk';
import { loadEnv, RsbuildConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginSass } from '@rsbuild/plugin-sass';
import path from 'path';

// eslint-disable-next-line import/no-relative-packages
import packagesVersion from '../../../package.json' assert { type: 'json' };

const { parsed } = loadEnv();

const defaultPublicEnvList: (keyof ImportMetaEnv)[] = ['API_GATEWAY'];
const typedParsed = parsed as Record<keyof ImportMetaEnv, string>;

export const rsbuildConfig = (
  config: RsbuildConfig = {},
  moduleFederation: moduleFederationPlugin.ModuleFederationPluginOptions = {},
  publicEnvList: (keyof ImportMetaEnv)[] = [],
): RsbuildConfig => {
  const plugins = [...(config.plugins ?? [])];

  plugins.push(
    pluginReact(),
    pluginSass(),
    // pluginModuleFederation({
    //
    // }),
  );

  const resultConfig: RsbuildConfig = {
    ...config,
    server: {
      ...config.server,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
        'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
      },
      publicDir: [
        {
          name: 'public',
          watch: true,
        },
        {
          name: path.resolve(__dirname, '../../../libs/ui/public'),
          watch: true,
        },
      ],
    },

    source: {
      ...config.source,
      define: Object.keys(typedParsed)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        .filter(item => publicEnvList.includes(item as any))
        .concat(defaultPublicEnvList as never)
        .reduce(
          (prev, key) => {
            const sanitizedKey = key.replace(/[^a-zA-Z0-9_]/g, '_');

            prev[`import.meta.env.${sanitizedKey}`] = JSON.stringify(parsed[key]);

            return prev;
          },
          {} as Record<string, string>,
        ),
    },

    output: {
      ...config.output,
      cleanDistPath: true,
    },

    tools: {
      ...config.tools,
      rspack: (config, { appendPlugins, rspack }) => {
        appendPlugins([
          new rspack.container.ModuleFederationPlugin({
            ...moduleFederation,
            dts: true,
            manifest: true,
            shared: {
              react: {
                singleton: true,
                requiredVersion: packagesVersion.dependencies.react,
              },
              'react-dom': {
                singleton: true,
                requiredVersion: packagesVersion.dependencies['react-dom'],
              },
              jotai: {
                singleton: true,
                requiredVersion: packagesVersion.dependencies['jotai'],
              },
              'jotai-effect': {
                singleton: true,
                requiredVersion: packagesVersion.dependencies['jotai-effect'],
              },
              clsx: {
                singleton: true,
                requiredVersion: packagesVersion.dependencies['clsx'],
              },
              zod: {
                singleton: true,
                requiredVersion: packagesVersion.dependencies['zod'],
              },
            },
            runtimePlugins: [
              path.resolve(__dirname, './module-federation-plugins/shared-strategy.ts'),
              path.resolve(__dirname, './module-federation-plugins/offline-remote.ts'),
            ],
          }),
        ]);
      },
    },

    html: {
      ...config.html,
      template: './template.html',
    },

    performance: {
      ...config.performance,
      chunkSplit: {
        ...config.performance?.chunkSplit,
        strategy: 'single-vendor',
      },
    },
  };

  return {
    ...resultConfig,
    plugins,
  };
};

Host app rsbuild.config.ts

import { rsbuildConfig } from '@platform/rsbuild-config';
import { defineConfig } from '@rsbuild/core';

// eslint-disable-next-line import/no-relative-packages
import mfConfigs from '../../.deploy/mf.json' assert { type: 'json' };

const env = process.env.ENV;
const appConfig = mfConfigs.mfs.app;

if (!appConfig) {
  throw new Error('You dont setup this mf in mf.json!');
}

export default defineConfig(
  rsbuildConfig(
    {
      server: {
        port: appConfig.ports[env],
      },
    },
    {
      name: appConfig.name,
      remotes: appConfig.mfs.reduce((acc, curr) => {
        return {
          ...acc,
          [curr.alias]: `${curr.name}@${curr.entry[process.env.NODE_ENV][env]}`,
        };
      }, {}),
    },
  ),
);

Remote app rsbuild.config.ts

import { rsbuildConfig } from '@platform/rsbuild-config';
import { defineConfig } from '@rsbuild/core';

// eslint-disable-next-line import/no-relative-packages
import mfConfigs from '../../.deploy/mf.json' assert { type: 'json' };

const env = process.env.ENV;
const appConfig = mfConfigs.mfs.tasks;

if (!appConfig) {
  throw new Error('You dont setup this mf in mf.json!');
}

export default defineConfig(
  rsbuildConfig(
    {
      server: {
        port: appConfig.ports[env],
      },
    },
    {
      name: appConfig.name,
      filename: 'remoteEntry.js',
      exposes: {
        './Tasks': './src/components/Tasks.tsx',
      },
    },
  ),
);

But, I got same issue with @module-federation/rsbuild-plugin

Image

@XavierLeTohic
Copy link
Author

What happens if you set the publicPath to "/" instead of auto?

I would recommend that you make either a reproducible example in a repository or a code sandbox

@VictorPulzz
Copy link

I wrote it like this:

    tools: {
      ...config.tools,
      rspack: {
        ...config.tools?.rspack,
        output: {
          publicPath: '/',
          // clean: true,
        },
      },
    },

Nothing has changed, I'm very tired:(

@XavierLeTohic
Copy link
Author

I will have a look tomorrow, also why are you spreading the default rsbuild config everywhere? It's not gonna erase the whole config at the end but apply some kind of patch

@VictorPulzz
Copy link

VictorPulzz commented Feb 4, 2025

Do you think it is necessary to write publicPath="auto" only for remote apps?
I have a very large project and I want to unify:)

@petrosD93
Copy link

petrosD93 commented Feb 5, 2025

@VictorPulzz have a look at this module-federation/core#3343

I ended up using the base meta tag

@VictorPulzz
Copy link

@XavierLeTohic @petrosD93
Thanks a lot guys for your help, I made it easier, I just passed publicPath: 'auto' only for remote project,

import { rsbuildConfig } from '@platform/rsbuild-config';
import { defineConfig } from '@rsbuild/core';

// eslint-disable-next-line import/no-relative-packages
import mfConfigs from '../../.deploy/mf.json' assert { type: 'json' };

const env = process.env.ENV;
const appConfig = mfConfigs.mfs.tasks;

if (!appConfig) {
  throw new Error('You dont setup this mf in mf.json!');
}

export default defineConfig(
  rsbuildConfig(
    {
      server: {
        port: appConfig.ports[env],
      },
      tools: {
        rspack: {
          output: {
            publicPath: 'auto',
          },
        },
      },
    },
    {
      name: appConfig.name,
      filename: 'remoteEntry.js',
      exposes: {
        './Tasks': './src/components/Tasks.tsx',
      },
    },
  ),
);

Everything seems to work, locally dev/prod, as well as on amazon, I want to put my setup rsbuild+eslint+prettier+tailwind+mf in a separate monorepo, maybe it will help someone, as I will write here:))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

8 participants