fix: #156 ssr: require is not defined
Further to this discussion: https://github.com/module-federation/vite/issues/156#issuecomment-2428952385
@gioboa Running the multi-example with these changes correctly export default exportModule but at runtime I get "unexpected token: default"
Tracing this back in the code the export default gets incorrectly identified as a named export in src/plugins/pluginDevProxyModuleTopLevelAwait.ts
The transpiled code that causes the error:
// node_modules/__mf__virtual/host__loadRemote__remote_mf_1_Product__loadRemote__.js
var import_node_module = __toESM(require_node_module());
var { loadRemote } = require2("@module-federation/runtime");
var { initPromise } = require2("__mf__virtual/host__mf_v__runtimeInit__mf_v__.js");
var res = initPromise.then((_) => loadRemote("remote/Product"));
var exportModule = (
/*mf top-level-await placeholder replacement mf*/
initPromise.then((_) => res)
);
var require2 = (0, import_node_module.createRequire)(import.meta.url);
var host_loadRemote_remote_mf_1_Product_loadRemote_default = exportModule;
const __mfproxy__awaitdefault = await default(); <-- HERE
const __mfproxy__default = () => __mfproxy__awaitdefault;
export { __mfproxy__default as default };
Any ideas?
I see, I will check it. Thanks for the deep investigation
Hey @gioboa any update on this?
I did look at it yet. But eventually we can continually change the code if an SSR env is detected
Hey @gioboa ! Are you looking at this. Would be happy to help but I am struggling to understand the inner workings. Thanks!
Not yet but I will look at it. Can you share your repository to test this change please?
Not yet but I will look at it. Can you share your repository to test this change please?
Here you go! https://github.com/poenneby/mf-vanilla-react-router
Hi @poenneby. I'm trying to do the same as you. I fond this issue and started some investigation. I don't think you can rely on checking the remote type for esm. This would work if the exported code would only run on SSR. But that module can be requested by the browser directly, as in the failing test.
So to make it compatible with client-side rendering it needs to use syntax supported in CJS. Even if we fix the top-level await issue, the import from 'node:module' wouldn't work in this scenario.
I guess the ideal solution would be use a syntax supported both by CJS and ESM (which is easier said than done). I think we can try using dynamic imports and runtime check on the exports. something like this:
export function generateRemotes(id: string, command: string) {
return `
const runtimePromise = import('@module-federation/runtime');
const virtualRuntimePromise = import("${virtualRuntimeInitStatus.getImportId()}");
const res = Promise.all([runtimePromise, virtualRuntimePromise])
.then(([runtime, virtualRuntime]) => {
const initPromise = virtualRuntime.initPromise;
return initPromise.then(() => runtime.loadRemote(${JSON.stringify(id)}));
});
const exportModule = ${command !== 'build' ? '/*mf top-level-await placeholder replacement mf*/' : 'await '}res;
// Dual exports for CJS/ESM compatibility
if (typeof module !== 'undefined') {
module.exports = exportModule;
module.exports.default = exportModule;
}
// ESM export fallback
if (typeof exports !== 'undefined' && typeof define !== 'function') {
Object.defineProperty(exports, '__esModule', { value: true });
exports.default = exportModule;
}
`;
}
This is passing the current tests. Ideally we would need to also add a SSR test to validate this on a node/deno/bun environments. Haven't tested in SSR yet. Also, I don't love this solution, it's kind of a mess. We could try UMD, but that's just a different flavor of mess.
Other than that I think we would need to figure out a way to properly identify the runtime (CJS/ESM) instead of the relying on the remote type.
EDIT: This also seems to break the toplevel await plugin logic.
Another idea is to use a plugin (https://github.com/originjs/vite-plugins/tree/main/packages/vite-plugin-commonjs) to convert CJS to ESM on the host doing SSR and requesting the module. I'll try out a few more things today, but I'm not sure how long I can spend on this ATM.
ok... this is a slightly modified code, that doesn't seem to affect top level await logic:
export function generateRemotes(id: string, command: string) {
return `
const runtimePromise = import('@module-federation/runtime');
const virtualRuntimePromise = import("${virtualRuntimeInitStatus.getImportId()}");
const res = Promise.all([runtimePromise, virtualRuntimePromise])
.then(([runtime, virtualRuntime]) => {
const initPromise = virtualRuntime.initPromise;
return initPromise.then(() => runtime.loadRemote(${JSON.stringify(id)}));
});
const exportModule = ${command !== 'build' ? '/*mf top-level-await placeholder replacement mf*/' : 'await '}res;
// Dual exports for CJS/ESM compatibility
module.exports = exportModule;
module.exports.default = exportModule;
// ESM export fallback
const _exports = exports || {};
Object.defineProperty(_exports, '__esModule', { value: true });
exports.default = exportModule;
`;
}
but the weird thing is that now the build fails, from what it seems like we want the top level await transform to do.
error during build:
[commonjs--resolver] node_modules/__mf__virtual/host__loadRemote__remote_mf_1_Product__loadRemote__.js (9:31): await isn't allowed in non-async function
file: /Users/tiago/ws/other/vite/examples/vite-webpack-rspack/host/node_modules/__mf__virtual/host__loadRemote__remote_mf_1_Product__loadRemote__.js:9:31
7: return initPromise.then(() => runtime.loadRemote("remote/Product"));
8: });
9: const exportModule = await res;
^
10:
11: // Dual exports for CJS/ESM compatibility
RollupError: await isn't allowed in non-async function
Not sure what's happening and why we need the top level await logic exactly.
@poenneby I think I have a slightly better understanding of it now. We were making it harder than it had to be. We don't have to make the exports esm compatible. Rollup will process the file before writing it, and wrap the code into a __commonJS call. But we can't use the require calls as those are not available on esm mode.
So, I think we only need to get rid of the require calls.
I've got a fork for this here :https://github.com/tiagobnobrega/mf-vite/tree/interop It's on the interop branch.
Haven't tested on SSR yet. I'll try to make progress on that if I find the time.
Is there any update on this?
@poenneby I think I have a slightly better understanding of it now. We were making it harder than it had to be. We don't have to make the exports esm compatible. Rollup will process the file before writing it, and wrap the code into a
__commonJScall. But we can't use therequirecalls as those are not available on esm mode.So, I think we only need to get rid of the
requirecalls.I've got a fork for this here :https://github.com/tiagobnobrega/mf-vite/tree/interop It's on the interop branch.
Haven't tested on SSR yet. I'll try to make progress on that if I find the time.
im running into a related issue that may require the use of esm import() vs require. It seems that there is an edge case that can cause an import cycle, and as cjs gets rewritten to esm, the page hangs b/c a module waits on a promise that loads a chunk that goes and ends up requesting itself...
So far rewriting writeLoadShareModule may be needed.
Sorry. I had to move focus elsewhere. I'm still interested in this, but it is more complex than I anticipated. I found issues with top-level await in ESM modules. I was looking at the runtime packages, maybe this could leverage that ecosystem more.
Would love to see this fix land. I don't feel qualified to steward this though. Is someone working on it?
Would love to see this fix land. I don't feel qualified to steward this though. Is someone working on it?
IMO I think longer term this plugin will be superseded by the one rolldown is making. as for anyone being qualified, not really a thing, you go in the code and figure wtf is going on :upside_down_face: .
this plugin will be superseded by the one rolldown is making
What does this mean for this plugin? Currently, this plugin cannot be used. Do we have to wait for a new plugin to use Vite with Module Federation? Is there a timeline or other information about this new plugin?
this plugin will be superseded by the one rolldown is making
What does this mean for this plugin? Currently, this plugin cannot be used. Do we have to wait for a new plugin to use Vite with Module Federation? Is there a timeline or other information about this new plugin?
I use it but i made a fork and added a patch. But I will be going to rolldowns MF plugin as soon as im able to...
@pcfreak30 is that fork something you would be ok to share?
this plugin will be superseded by the one rolldown is making
What does this mean for this plugin? Currently, this plugin cannot be used. Do we have to wait for a new plugin to use Vite with Module Federation? Is there a timeline or other information about this new plugin?
I use it but i made a fork and added a patch. But I will be going to rolldowns MF plugin as soon as im able to...
I guess "rolldown" means the new bundler for Vite? I never heard of it before, I just read about it for the first time. And it seems that rolldown will have first class support for Module Federation.
this plugin will be superseded by the one rolldown is making
What does this mean for this plugin? Currently, this plugin cannot be used. Do we have to wait for a new plugin to use Vite with Module Federation? Is there a timeline or other information about this new plugin?
I use it but i made a fork and added a patch. But I will be going to rolldowns MF plugin as soon as im able to...
I guess "rolldown" means the new bundler for Vite? I never heard of it before, I just read about it for the first time. And it seems that rolldown will have first class support for Module Federation.
yes, its in rust. https://github.com/rolldown/rolldown/tree/main/crates/rolldown_plugin_module_federation
yes, its in rust. https://github.com/rolldown/rolldown/tree/main/crates/rolldown_plugin_module_federation
Currently, the Module Federation plugin for rolldown seems not to work with Vite (of course I used rolldown-vite). I tried it as remote in a vite-svelte project and as host in a vite-sveltekit project. In both constellations, the module federation plugin from rolldown just did nothing.
So a solution for this plugin here would be great.
yes, its in rust. https://github.com/rolldown/rolldown/tree/main/crates/rolldown_plugin_module_federation
Currently, the Module Federation plugin for rolldown seems not to work with Vite (of course I used rolldown-vite). I tried it as remote in a vite-svelte project and as host in a vite-sveltekit project. In both constellations, the module federation plugin from rolldown just did nothing. So a solution for this plugin here would be great.
rolldown is a long term solution. i didnt say it was ready yet...
rolldown is a long term solution. i didnt say it was ready yet...
Yes, that's true. I shared my experience just in case someone else thinks that rolldown is a solution at the current time.