vite icon indicating copy to clipboard operation
vite copied to clipboard

[Bug]: remoteEntryExports is undefined

Open StevenLangbroek opened this issue 9 months ago • 26 comments

Describe the bug

Hey folks! I haven't used Module Federation since experimenting with it in its early days, and have a use-case now, so I thought I'd start prototyping. I created 2 apps, both with the same stack (TanStack Query + Router, Vite):

  • host (offering an app shell + routing, host application, runs on port 5175)
  • hello-world (a simple hello-world component, a remote, runs on port 4173, exposes mf-manifest.json)

I'm not using runtime or dynamic stuff, just a straight up import HelloWorld from 'hello-world/HelloWorld';. When I try to vier my app in browser, it seems as though Module Federation is confused:

Image

It tries to load the assets/remoteEntry-CdYN5o5R.js from 5175 instead of 4173, so it 404s, which then leads to the remoteEntryExports being undefined.

If I change my host to load assets/remoteEntry-CdYN5o5R.js instead of mf-manifest.json instead, it kind of works, but I still get the following errors:

Image

This is also kind of undesirable, as I'd have to keep track of hashes somehow, rather than just pointing to mf-manifest & letting that figure it out for me...

Am I missing something? I've upgraded everything in host & hello-world to latest versions of all dependencies (with npx npm-check-updates).

Version

6.2.0

Reproduction

It's literally the most basic example I can think of...

Relevant log output


Remote Config:

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'hello-world',
      manifest: true,
      exposes: {
        './HelloWorld': './src/components/HelloWorld.tsx',
        './routes': './src/routes/index.tsx',
      },
      shared: ['react', 'react-dom', '@tanstack/react-router'],
    }),
  ],
  build: {
    target: 'esnext',
    minify: false,
  },
});

Host config:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { federation } from '@module-federation/vite';
import path from 'path';

export default defineConfig({
  plugins: [
    federation({
      name: 'host',
      remotes: {
        'hello-world': {
          name: 'hello-world',
          type: 'module',
          entry: 'http://localhost:4173/mf-manifest.json',
        }
      },
      shared: ['react', 'react-dom', '@tanstack/react-query']
    }),
    react()
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  build: {
    modulePreload: false,
    target: 'esnext',
    minify: false,
    cssCodeSplit: false
  }
});

The generated mf-manifest.json:

{
  "id": "hello-world",
  "name": "hello-world",
  "metaData": {
    "name": "hello-world",
    "type": "app",
    "buildInfo": { "buildVersion": "1.0.0", "buildName": "hello-world" },
    "remoteEntry": {
      "name": "assets/remoteEntry-D0hsAXHw.js",
      "path": "",
      "type": "module"
    },
    "ssrRemoteEntry": {
      "name": "assets/remoteEntry-D0hsAXHw.js",
      "path": "",
      "type": "module"
    },
    "types": { "path": "", "name": "" },
    "globalName": "hello-world",
    "pluginVersion": "0.2.5",
    "publicPath": "/"
  },
  "shared": [
    {
      "id": "hello-world:@tanstack/react-router",
      "name": "@tanstack/react-router",
      "version": "1.111.11",
      "requiredVersion": "^1.111.11",
      "assets": {
        "js": {
          "async": [],
          "sync": [
            "assets/index-Bk4i2veO.js",
            "assets/jsx-runtime-DtXR568w.js",
            "assets/hello_mf_2_world__loadShare__react__loadShare__-BU5ovZIB.js",
            "assets/hello_mf_2_world__mf_v__runtimeInit__mf_v__-Dfj3CfpL.js",
            "assets/hello_mf_2_world__loadShare__react_mf_2_dom__loadShare__-dlIcOtSS.js"
          ]
        },
        "css": { "async": [], "sync": [] }
      }
    },
    {
      "id": "hello-world:react",
      "name": "react",
      "version": "19.0.0",
      "requiredVersion": "^19.0.0",
      "assets": {
        "js": {
          "async": [],
          "sync": [
            "assets/index-CWTAqxGv.js",
            "assets/_commonjsHelpers-B85MJLTf.js"
          ]
        },
        "css": { "async": [], "sync": [] }
      }
    },
    {
      "id": "hello-world:react-dom",
      "name": "react-dom",
      "version": "19.0.0",
      "requiredVersion": "^19.0.0",
      "assets": {
        "js": {
          "async": [],
          "sync": [
            "assets/index-D4xJte0W.js",
            "assets/_commonjsHelpers-B85MJLTf.js",
            "assets/hello_mf_2_world__loadShare__react__loadShare__-BU5ovZIB.js",
            "assets/hello_mf_2_world__mf_v__runtimeInit__mf_v__-Dfj3CfpL.js"
          ]
        },
        "css": { "async": [], "sync": [] }
      }
    }
  ],
  "remotes": [],
  "exposes": [
    {
      "id": "hello-world:HelloWorld",
      "name": "HelloWorld",
      "assets": {
        "js": {
          "async": [],
          "sync": [
            "assets/HelloWorld-D5KLP__Z.js",
            "assets/jsx-runtime-DtXR568w.js"
          ]
        },
        "css": { "sync": [], "async": [] }
      },
      "path": "./HelloWorld"
    },
    {
      "id": "hello-world:routes",
      "name": "routes",
      "assets": {
        "js": {
          "async": [],
          "sync": [
            "assets/index-BiBtT30k.js",
            "assets/hello_mf_2_world__mf_v__runtimeInit__mf_v__-Dfj3CfpL.js",
            "assets/jsx-runtime-DtXR568w.js",
            "assets/HelloWorld-D5KLP__Z.js"
          ]
        },
        "css": { "sync": [], "async": [] }
      },
      "path": "./routes"
    }
  ]
}

Validations

StevenLangbroek avatar Feb 27 '25 08:02 StevenLangbroek

On a sidenote, I tried setting path in mf-manifest.json manually to http://localhost:4173, but that 404'ed as well to http://localhost:5135http://localhost:4173/asset/remoteEntry-[hash].js (so it seemed they were just naively joined, rather than parsed into a proper URL with a single protocol, host & port)

StevenLangbroek avatar Feb 27 '25 08:02 StevenLangbroek

Hi 👋 thanks for looking at this library. can you share your repo please?

gioboa avatar Feb 27 '25 08:02 gioboa

Hey @gioboa! Yep sorry, it's here: https://github.com/StevenLangbroek/mf-repro

StevenLangbroek avatar Feb 27 '25 09:02 StevenLangbroek

Run npm run build && npm run preview in remote, npm run dev in host

StevenLangbroek avatar Feb 27 '25 09:02 StevenLangbroek

Not sure it's important, but I'm running node 20.17.0 on Debian Linux.

StevenLangbroek avatar Feb 27 '25 09:02 StevenLangbroek

Thanks, I'll look at it

gioboa avatar Feb 27 '25 10:02 gioboa

Hello, you have to set base: 'http://localhost:4173 vite config in remote app. The base config will be used entry prefix like 'http://localhost:4173/assets/remoteEntry-CdYN5o5R.js'. If you set, you'll see publicPath in manifaset.json

"publicPath": "http://localhost:3000/"

rich-woowahan avatar Mar 04 '25 00:03 rich-woowahan

Hey @rich-woowahan! Ehmmm... We won't know the path at build time for our use-case. The host app can add remotes, and ideally, the paths are relative to the manifest... Is that not possible?

Our use-case is that in the host application, there's a configuration screen where admins can add remotes to the application's config, which should expose predetermined entrypoints for navigation, screens & configuration forms, which are dynamically loaded at runtime... We don't always know how our customers deploy stuff, so setting the base at build time isn't possible.

StevenLangbroek avatar Mar 04 '25 07:03 StevenLangbroek

@StevenLangbroek I understand. It seems that you cannot specify a specific domain to deploy at build time for the remote application. I haven’t tried it myself, but from the official documentation, I learned that using the GetPublicPath property allows the remote application to dynamically set the publicPath when it’s loaded. This makes me think that there might be a way for the host application to provide the publicPath for the remote application. I am not an expert yet, so I want to clarify that this is just a suggestion. By the way, the admin dashboard idea you mentioned sounds interesting.

rich-woowahan avatar Mar 06 '25 06:03 rich-woowahan

@StevenLangbroek I understand. It seems that you cannot specify a specific domain to deploy at build time for the remote application. I haven’t tried it myself, but from the official documentation, I learned that using the GetPublicPath property allows the remote application to dynamically set the publicPath when it’s loaded. This makes me think that there might be a way for the host application to provide the publicPath for the remote application. I am not an expert yet, so I want to clarify that this is just a suggestion. By the way, the admin dashboard idea you mentioned sounds interesting.

@gioboa i added a getpublicpath and hardcoded the URL for now just to see if it works, and while the tanstack router route works and the component renders when it's navigated to, I still see these 404s in the network tab... I'll push to the repro later in the morning and leave some more debugging info.

StevenLangbroek avatar Mar 06 '25 06:03 StevenLangbroek

@gioboa oh sorry there's some discussion on the PR (#267)

StevenLangbroek avatar Mar 06 '25 07:03 StevenLangbroek

Hello, you have to set base: 'http://localhost:4173 vite config in remote app. The base config will be used entry prefix like 'http://localhost:4173/assets/remoteEntry-CdYN5o5R.js'. If you set, you'll see publicPath in manifaset.json

"publicPath": "http://localhost:3000/"

I am running into the same issue, and although I have set the base configuration option, publicPath is "/":

Vite config modified from here:

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',
		},
    base: 'http://localhost:4174/',
		plugins: [
			{
				name: 'generate-environment',
				options: function () {
					console.info('selfEnv', selfEnv);
					writeFileSync('./src/environment.ts', `export default ${JSON.stringify(selfEnv, null, 2)};`);
				},
			},
			federation({
				name: 'remote',
        manifest: true,
				exposes: {
					'./remote-app': './src/App.tsx',
				},
				remotes: {},
				shared: {
					react: {
						requiredVersion: dependencies.react,
						singleton: true,
					},
				},
			}),
			react(),
		],
	};
});

mf-manifest.json:

{"id":"remote","name":"remote","metaData":{"name":"remote","type":"app","buildInfo":{"buildVersion":"1.0.0","buildName":"remote"},"remoteEntry":{"name":"remoteEntry-[hash]","path":"","type":"module"},"ssrRemoteEntry":{"name":"remoteEntry-[hash]","path":"","type":"module"},"types":{"path":"","name":""},"globalName":"remote","pluginVersion":"0.2.5","publicPath":"/"},"shared":[{"id":"remote:react","name":"react","version":"18.3.1","requiredVersion":"^18.3.1","assets":{"js":{"async":[],"sync":[]},"css":{"async":[],"sync":[]}}}],"remotes":[],"exposes":[{"id":"remote:remote-app","name":"remote-app","assets":{"js":{"async":[],"sync":[]},"css":{"sync":[],"async":[]}},"path":"./remote-app"}]}

hans-ts avatar Mar 06 '25 19:03 hans-ts

Note that the above only applies to dev npm script. If you run the build script publicPath is populated.

hans-ts avatar Mar 06 '25 20:03 hans-ts

I'm experiencing the same issue. The functionality only works when I build and preview at least the Provider. However, it fails when running both, the Consumer and Provider, in development mode (using the vite command with a port specified ).

I'm encountering the same errors mentioned in this post.

I'm using "vite": "^6.2.5", and "react": "^18.3.1"

larevalos avatar Apr 07 '25 00:04 larevalos

I am running into similar. the manifest output is remoteEntry":{"name":"remoteEntry-[hash].js","path":"","type":"module"},"ssrRemoteEntry":{"name":"remoteEntry-[hash].js". For some reason in dev mode it doesn't get handled correctly... i think this is in the addEntry plugin.

pcfreak30 avatar Apr 10 '25 04:04 pcfreak30

I just verified this with the vite-vite example https://github.com/module-federation/vite/tree/7114b3896da362488f9d3ffcba0c2091d618c893/examples/vite-vite

in dev mode the hash template var never gets replaced. I also think its an ugly hack to be trying to read a html file as well if im reading things right to get the hash (but i could be mis-reading the code).

overall right now running a dev server is kind of broken if you keep the hash template var, which also means cant use HMR.

pcfreak30 avatar Apr 10 '25 06:04 pcfreak30

same issue here

Image

my vite.config.ts host

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

export default defineConfig({
  plugins: [
      react(), 
      federation({
        exposes: {},
        name: 'host_shell',
        remotes: {
            remote_products: 'remote_products@http://localhost:5001/remoteEntry.js',
        },
        shared: {
          react: { singleton: true, requiredVersion: '^18.0.0' },
          'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        },
      })
  ],
  server: { port: 4173 },
  build: { target: 'chrome89' },
});

If you prefer, I can send the remote config too, but it is opening normally and providing remoteEntry.js as well.

I am using the build and preview command for both the remote and the host.

@pcfreak30 @StevenLangbroek Did you manage to solve your problems?

DevJoaoLopes avatar Sep 07 '25 08:09 DevJoaoLopes

same issue here

Image my vite.config.ts host

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

export default defineConfig({ plugins: [ react(), federation({ exposes: {}, name: 'host_shell', remotes: { remote_products: 'remote_products@http://localhost:5001/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }) ], server: { port: 4173 }, build: { target: 'chrome89' }, }); If you prefer, I can send the remote config too, but it is opening normally and providing remoteEntry.js as well.

I am using the build and preview command for both the remote and the host.

@pcfreak30 @StevenLangbroek Did you manage to solve your problems?

I have been doing a lot of r&d if you look at the PR's for the plugin. I have an approach now for dev and build mode, though a key finding is dev mode doesn't bundle anything and loads direct as ESM. That also means some CJS don't get processed and have to be fixed.

pcfreak30 avatar Sep 07 '25 09:09 pcfreak30

@pcfreak30 Do you have any examples of this approach?

DevJoaoLopes avatar Sep 08 '25 00:09 DevJoaoLopes

@pcfreak30 Do you have any examples of this approach?

No. my work is driven by my own needs. The best I can give you is my projects actual vite stuff which is an abstraction https://github.com/LumeWeb/web/blob/647bde87ec985ac7cf204311a94501e422a67178/libs/portal-framework-core/src/vite/plugin.ts.

pcfreak30 avatar Sep 08 '25 01:09 pcfreak30

Same issue, this is how I got around it:

Remote vite.config.js

Remove the hash from remoteEntry.js chunk so the name is predictable

build: {
	rollupOptions: {
		output: {
			chunkFileNames: (chunkInfo) => {
				if (/remoteEntry/.test(chunkInfo.name)){
					return `js/[name].js`;
				}
				return `js/[name]_[hash].js`;
			}
		}
	}
}

Host vite.config.js

Use remoteEntry.js instead of mf-manifest.json

I also disabled modulePreload because in my use-case it was trying to preload chunks from the local path instead of the full url

plugins: [
	react(),
	federation({
		name: "host",
		remotes: {
			helloWorld: `helloworld@http://localhost:3001/js/remoteEntry.js`
		}
	})
],
build: {
	modulePreload: false
}

koga73 avatar Sep 12 '25 14:09 koga73

As there's been no activity for 30 days, this issue has been flagged as stale. If you'd like it to remain open, please add a comment within the next 7 days. Thank you.

github-actions[bot] avatar Oct 29 '25 14:10 github-actions[bot]

This is still an issue.

hans-ts avatar Oct 29 '25 15:10 hans-ts

I am facing this issue as well. Loading a Vite module into an RsBuild host is not currently working

mattwmarra avatar Nov 10 '25 17:11 mattwmarra

I am facing this issue as well. Loading a Vite module into an RsBuild host is not currently working

Could you share the federation module configuration codes in both vite and rsbuild?

DevJoaoLopes avatar Nov 11 '25 16:11 DevJoaoLopes

@DevJoaoLopes Have exact same issue as @mattwmarra described.

Host: vite

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { federation } from "@module-federation/vite";

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(),
    federation({
      name: "uiAPP",
      filename: "remoteEntry.js",
      remotes: {
        automationMFE: {
          name: "automationMFE",
          type: "var",
          entry: "http://localhost:5001/remoteEntry.js",
        },
      },
      shared: ["react", "react-dom"],
    }),
  ],
  build: {
    target: "chrome89",
    minify: false,
  },
});

Remote: rsbuild

import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";

// Docs: https://rsbuild.rs/config/
export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: "automationMfe",
      filename: "remoteEntry.js",
      exposes: {
        "./App": "./src/App.tsx",
      },
      shared: ["react", "react-dom"],
    }),
  ],
  server: {
    port: 5001,
  },
  output: {
    minify: false,
  },
});

Receiving this error in both dev mode and preview

Uncaught Error: [ Federation Runtime ]: remoteEntryExports is undefined 
 {
  "entryGlobalName": "automationMFE",
  "name": "automationMFE",
  "type": "var",
  "entry": "http://localhost:5001/remoteEntry.js",
  "shareScope": "default"
}
    at error2 (chunk-LTCJ5FP2.js?v=9c972234:954:13)
    at assert2 (chunk-LTCJ5FP2.js?v=9c972234:944:9)
    at Module.getEntry (chunk-LTCJ5FP2.js?v=9c972234:2273:9)
    at async Module.get (chunk-LTCJ5FP2.js?v=9c972234:2283:36)
    at async RemoteHandler.loadRemote (chunk-LTCJ5FP2.js?v=9c972234:3403:35)
    at async automationMFE_App.js?v=9c972234:26:49

orkhanrustamli avatar Nov 24 '25 20:11 orkhanrustamli