Visitar URL original
Remove Need for Async Import in Module Federation · Issue #18812 · webpack/webpack · GitHub
Skip to content

Remove Need for Async Import in Module Federation  #18812

Description

@ScriptedAlchemy

Feature request

Module Federation currently forces the user to manually implement a import() in their user code in order to avoid eager consumption errors.
Webpack should internalize the async boundary in the entrypoint startup or use require.X for active chunk startup, this ensures that chunk handlers are called for subsequent chunks and ideally for the entrypoint itself.

What is the expected behavior?

I should not need to import() in my codebase manually.
My entrypoint should behave like a async chunk load, and call ensureChunkHandlers for itself and any chunks it depends on before evaluating the EntryDependencies.

What is motivation or use case for adding/changing the behavior?
Most frameworks control how entrypoint is loaded, like next.js - there is no way for user to alter the startup of these apps from the outside. the eager consumption issue is a yearslong complaint from the community with most who just start getting stuck for hours wondering what is wrong, not realizing that the import() is critical to operations.

How should this be implemented in your opinion?

We should add a runtime requirement to any entrypoint whose tree contains module federation dependent modules, like shared module or remote module consumption that is not wrapped in AsyncDependencyBlock

In StartupChunkDependenciesPlugin or example.

        compilation.hooks.additionalChunkRuntimeRequirements.tap(
          'MfStartupChunkDependenciesPlugin',
          (chunk, set, { chunkGraph }) => {
            if (!isEnabledForChunk(chunk)) return;
            if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return;
            set.add(federationStartup);
          },
        );

in StartupHelpers + ESM startup / renderStartup hook.

   if (federation) {
        const chunkIds = Array.from(chunks, (c: Chunk) => c.id);

        const wrappedInit = (body: string) =>
          Template.asString([
            'Promise.all([',
            Template.indent([
              // may have other chunks who depend on federation, so best to just fallback
              // instead of try to figure out if consumes or remotes exists during build
              `${RuntimeGlobals.ensureChunkHandlers}.consumes || function(chunkId, promises) {},`,
              `${RuntimeGlobals.ensureChunkHandlers}.remotes || function(chunkId, promises) {},`,
            ]),
            `].reduce(${runtimeTemplate.returningFunction(`handler('${chunk.id}', p), p`, 'p, handler')}, promises)`,
            `).then(${runtimeTemplate.returningFunction(body)});`,
          ]);

        const wrap = wrappedInit(
          `${
            passive
              ? RuntimeGlobals.onChunksLoaded
              : RuntimeGlobals.startupEntrypoint
          }(0, ${JSON.stringify(chunkIds)}, ${fn})`,
        );

        runtime.push(`${final && !passive ? EXPORT_PREFIX : ''}${wrap}`);
      } else {
        const chunkIds = Array.from(chunks, (c: Chunk) => c.id);
        runtime.push(
          `${final && !passive ? EXPORT_PREFIX : ''}${
            passive
              ? RuntimeGlobals.onChunksLoaded
              : RuntimeGlobals.startupEntrypoint
          }(0, ${JSON.stringify(chunkIds)}, ${fn});`,
        );
        if (final && passive) {
          runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`);
        }
      }

The output of a entrypoint should look like this:

// load runtime
var __webpack_require__ = require("../webpack-runtime.js");
__webpack_require__.C(exports);
var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))



var promises = [];
var __webpack_exports__ = Promise.all([
	__webpack_require__.f.consumes || function(chunkId, promises) {},
	__webpack_require__.f.remotes || function(chunkId, promises) {},
// since call handler for this chunk
].reduce((p, handler) => (handler('pages/_document', p), p), promises)
// use require.x which already calls require.f for all other initialChunks
).then(() => (__webpack_require__.X(0, ["vendor-chunks/next"], () => (__webpack_exec__("./pages/_document.js")))));
module.exports = __webpack_exports__;

In RemoteRuntimeModule and ConsumeSharedRuntimeModule, we should getAllReferencedChunks - this ensures that initialChunks or custom splitChunks can be tracked by federation chunk handlers, this also allows splitChunks to work again on more than splitChunks: async

    const allChunks = [...(this.chunk?.getAllReferencedChunks() || [])];
    for (const chunk of allChunks) {

The remoteEntry should not be wrapped in require.X as this will convert it to promise when remote global should return get,init

Are you willing to work on this yourself?
yes

Activity

  1. Neerajpathak07 commented on Oct 1, 2024

    @Neerajpathak07

    would like to work on this ,
    Will be sending a pr by the weekend

  2. ScriptedAlchemy commented on Oct 1, 2024

    @ScriptedAlchemy
    ContributorAuthor

    @Neerajpathak07 you can look at how i implemented them in my repo.

    https://github.com/module-federation/core/tree/main/packages/enhanced/src/lib/startup

    See where these plugins are applied in my codebase

  3. Neerajpathak07 commented on Oct 2, 2024

    @Neerajpathak07

    Sure, I'll Look into it.

  4. Neerajpathak07 commented on Oct 3, 2024

    @Neerajpathak07

    @ScriptedAlchemy should I create a file and add the Plugin for mfStartupChunkDependenciesPlugin as well?

  5. ScriptedAlchemy commented on Oct 4, 2024

    @ScriptedAlchemy
    ContributorAuthor

    There was already a startup dependencies plugin in webpack. Ideally we just add some logic like mine into that. I think. You can always pr a new plugin and see what review says

  6. Neerajpathak07 commented on Oct 4, 2024

    @Neerajpathak07

    Ok Got it !

  7. ScriptedAlchemy commented on Oct 6, 2024

    @ScriptedAlchemy
    ContributorAuthor

    This depends on a PR if have open i think. The hoist container reference stuff. But that is to fix a problem with eager modules. The entry either way will need async wrapper

  8. Neerajpathak07 commented on Oct 8, 2024

    @Neerajpathak07

    Oh! I guess that's the reason The entry point is treated as an async chunk, and its dependencies are loaded before execution.

  9. ScriptedAlchemy commented on Oct 8, 2024

    @ScriptedAlchemy
    ContributorAuthor

    Yes. But hoist refs only fixes single runtime chunk issues. Async with main.js etc won't need that

  10. Neerajpathak07 commented on Oct 9, 2024

    @Neerajpathak07

    Oh! now I get it.

  11. ScriptedAlchemy commented on Oct 10, 2024

    @ScriptedAlchemy
    ContributorAuthor

    The idea is to move the async boundary out of user code and just change how an entrypoint requires its initial module. So it will only trigger after we check all requirements are met. Try applying this to a normal build with just a min entry. Then share react. Without these mods it will throw eager consumption error. With them it will not.

  12. ScriptedAlchemy commented on Oct 10, 2024

    @ScriptedAlchemy
    ContributorAuthor

    These changes make an asynchronous startup. So main.js exports a promise that resolves the inner require. Entry acts the same as if it were import().

    I changed from passive mode to active. Which usually is for ESM async entry. So I use startup entrypoint runtime requirement.

    That's require.x for its chunks and split chunks. Then the code I add to startup helpers checks if the chunk has federation-entry-startup or something as a runtime requirement. If so, I think I call the remotes and consumes handlers myself for the entry chunk ID, like main.

    Require.x will call the handler for all else like dependOn, etc. But there needs to be an extra wrap to also ensure the main chunk.

  13. Neerajpathak07 commented on Oct 11, 2024

    @Neerajpathak07

    Oh! alright then I'll add an extra wrap for the main chunk are send a pr again.

  14. ScriptedAlchemy commented on Oct 11, 2024

    @ScriptedAlchemy
    ContributorAuthor

    If you have any questions you can dm me on Twitter. Also my core repo under module federation has this fully implemented if you want to see the difference etc

  15. Neerajpathak07 commented on Oct 12, 2024

    @Neerajpathak07

    Yeah Sure I'll refer to that as well

  16. added a commit that references this issue on Oct 15, 2024
  17. Neerajpathak07 commented on Oct 15, 2024

    @Neerajpathak07

    @ScriptedAlchemy
    I have created a Pr.
    pls review it.

  18. added a commit that references this issue on Nov 29, 2024
  19. pranjalisr commented on Mar 22, 2025

    @pranjalisr

    Implement the below code in StartupChunkDependenciesPlugin or a similar location.

    compilation.hooks.additionalChunkRuntimeRequirements.tap(
    'MyStartupChunkDependenciesPlugin',
    (chunk, set, { chunkGraph }) => {
    if (!isEnabledForChunk(chunk)) return;
    if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return;
    set.add(federationStartup);
    }
    );

    It might help

  20. LeonxLJX commented on Sep 2, 2026

    @LeonxLJX

    I'd like to take this one.

    I've read through the report. Before changing anything I'd like to reproduce it against current main first — my reasoning is that a fix anchored to the actual observed behavior is much easier to review than one inferred from the report alone, especially if the surrounding code has moved since this was filed.

    Plan: reproduce and confirm the root cause, then make the smallest change that addresses it (plus a regression test if the repo has coverage for this area). If the root cause turns out to live somewhere other than where the report points, I'll report back here before forcing a change.

    Anything already known about this that I should read first?

    (claiming via @LeonxLJX)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedMaintainers welcome a community PR for this

    Fields

    Priority

    None yet

    Start date

    None yet

    Target date

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions