vite icon indicating copy to clipboard operation
vite copied to clipboard

fix: #156 ssr: require is not defined

Open poenneby opened this issue 11 months ago • 14 comments

Further to this discussion: https://github.com/module-federation/vite/issues/156#issuecomment-2428952385

poenneby avatar Feb 06 '25 23:02 poenneby

@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?

poenneby avatar Feb 07 '25 17:02 poenneby

I see, I will check it. Thanks for the deep investigation

gioboa avatar Feb 07 '25 17:02 gioboa

Hey @gioboa any update on this?

rafal-akiro avatar Feb 10 '25 16:02 rafal-akiro

I did look at it yet. But eventually we can continually change the code if an SSR env is detected

gioboa avatar Feb 10 '25 17:02 gioboa

Hey @gioboa ! Are you looking at this. Would be happy to help but I am struggling to understand the inner workings. Thanks!

poenneby avatar Mar 05 '25 16:03 poenneby

Not yet but I will look at it. Can you share your repository to test this change please?

gioboa avatar Mar 05 '25 17:03 gioboa

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

poenneby avatar Mar 07 '25 12:03 poenneby

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.

tiagobnobrega avatar Mar 12 '25 12:03 tiagobnobrega

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.

tiagobnobrega avatar Mar 12 '25 12:03 tiagobnobrega

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.

tiagobnobrega avatar Mar 12 '25 13:03 tiagobnobrega

@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.

tiagobnobrega avatar Mar 13 '25 22:03 tiagobnobrega

Is there any update on this?

zd754 avatar Apr 07 '25 12:04 zd754

@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.

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.

pcfreak30 avatar Apr 21 '25 14:04 pcfreak30

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.

tiagobnobrega avatar May 05 '25 17:05 tiagobnobrega

Would love to see this fix land. I don't feel qualified to steward this though. Is someone working on it?

ecline123 avatar May 27 '25 16:05 ecline123

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: .

pcfreak30 avatar May 27 '25 16:05 pcfreak30

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?

vekunz avatar Jun 03 '25 10:06 vekunz

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 avatar Jun 03 '25 10:06 pcfreak30

@pcfreak30 is that fork something you would be ok to share?

raffaele-abramini avatar Jun 03 '25 10:06 raffaele-abramini

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.

vekunz avatar Jun 03 '25 10:06 vekunz

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

pcfreak30 avatar Jun 03 '25 12:06 pcfreak30

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.

vekunz avatar Jun 03 '25 12:06 vekunz

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...

pcfreak30 avatar Jun 03 '25 13:06 pcfreak30

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.

vekunz avatar Jun 03 '25 14:06 vekunz