Skip to content

Module federation prevents to use expose-shared modules as singletons #18556

Open

Description

@smelukov

a.js

import {c} from './c'

export const a = c;

b.js

import {c} from './c'

export const b = c;

c.js

export const c = 123;
new ModuleFederationPlugin({
  name: 'app2',
  exposes: {
    './App': './src/App',
    './a': './src/a',
    './b': './src/b',
  },
})

Here is an output after build
image

c.js is using by a and d and was duplicated that is not expected behaviour
For example c.js may be some singleton-class but bundler-behaviour prevents it.
Is it a bug?

Repo https://github.com/smelukov/mf-duplicates/tree/main

Activity

  1. alexander-akait commented on Jul 11, 2024

    @alexander-akait
    Member

    Yes, but you can move c.js forced into own chunk or share it if you want to reuse

    For example c.js may be some singleton-class but bundler-behaviour prevents it.

    https://webpack.js.org/plugins/module-federation-plugin/#singleton

  2. smelukov commented on Jul 13, 2024

    @smelukov
    ContributorAuthor

    A project may have many modules that should be used as singletons, and a developer never knows about all of them (e.g., packages in node_modules and their dependencies)
    I think that the solution is to try to use split chunk logic for exposed modules used in a federated project itself instead of managing them manually.
    Current behaviour just breaks some projects and leads to unexpected errors, and an investigation to find modules that should be used as singletons takes tons of time.

  3. alexander-akait commented on Jul 16, 2024

    @alexander-akait
    Member

    @smelukov using another logic can break it too, so without manual setup it is impossible to solve

  4. ScriptedAlchemy commented on Sep 27, 2024

    @ScriptedAlchemy
    Contributor

    Exposed modules dont know C needs to be anything special, you must set it as singleton otherwise it would bundle what that chunks needs like anything else.

  5. smelukov commented on Sep 28, 2024

    @smelukov
    ContributorAuthor

    @ScriptedAlchemy
    A really huge project may have a tons of cross-expose imports and these modules should not be duplicated (components, data stores, etc)
    Should I mark the entire project as a singleton or some directory?
    For example:

    new ModuleFederationPlugin({
      name: 'app2',
      exposes: {
        './components': './src/components',
        './pages': './src/pages',
      },
      shared: {
        './src/components/': { singleton: true }, // with trailing slash
        './src/pages/': { singleton: true }, // with trailing slash
        './src/utils/': { singleton: true }, // with trailing slash (utils used by components and pages and should not be duplicated)
      }
    })

    Will (should) it work that way?

  6. ScriptedAlchemy commented on Sep 28, 2024

    @ScriptedAlchemy
    Contributor

    Share the thing they both need as a singleton. Sharing exposed modules as singletons won't work since an exposed module is a singleton, but the sub-dependencies in there are not, unless you mark them as such.

    If you have a host where its own remote entry is consumed as well, then that's your actual issue. There are two module graphs on the page, and the instance in the host is a different runtime and module cache than the remote entry. In such a case, you need a single runtime chunk or can sometimes work around it by sharing and not exposing. But the reason it "works" is that the remote doesn't actually load a copy and the duplicate runtime happens to use one from the other.

    While the workaround works. If you do have host uses its own remote somehow in the tree. Then chunk loading will eventually break etc

  7. ScriptedAlchemy commented on Sep 28, 2024

    @ScriptedAlchemy
    Contributor

    But if you use single runtime chunk you should upgrade to federation v2, since webpack currently has issue with single runtime and mf

    https://module-federation.io/

  8. smelukov commented on Sep 28, 2024

    @smelukov
    ContributorAuthor

    @ScriptedAlchemy
    My case is:

    Host

    • has an entrypoint and one expose
    • uses own expose (lets name it as hostStore)

    Remotes

    • use hostStore-expose from host
    • contains some number of exposes and there is many cross-exposes imports in this remotes

    I use a plugin from https://module-federation.io/

  9. ScriptedAlchemy commented on Sep 28, 2024

    @ScriptedAlchemy
    Contributor

    Yeah then that's your problem. You have two runtimes from the same build.
    Set RuntimeChunk:single in optimization.

  10. smelukov commented on Sep 28, 2024

    @smelukov
    ContributorAuthor

    @ScriptedAlchemy

    1. changed import
      from name+expose:
      import { Store } from 'host/hostStore';
      to relative:
      import { Store } from './hostStore';

    2. changed config:

    optimization: {
        runtimeChunk: 'single',
    },
    

    Got error
    image

    image

    But moduleId is not hostStore, but webpack/sharing/consume/default/my-shared-library-name-here
    Yep, my project also has some shared libraries
    I don't understand how it relates.
    Plugin version is 0.6.6

    For now, to workaround this problem, all remotes use itself and import its own exposes by full name (name+expose)
    It would be great if bundler automatically reused expose-chunks if cross-exposed imports were detected.

  11. ScriptedAlchemy commented on Sep 30, 2024

    @ScriptedAlchemy
    Contributor

    The problem is that you have two runtime chunks. We cannot auto-fix that. (It should be a) single runtime chunk for the host to load its own remote. Tomorrow I will see if I can ship a slight update to how it works. I have something to handle making both single runtime and standalone runtime. Then you use the runtime plugin to switch between which remote URL to use depending on if the remote is also the host or not.

    Single runtime means the remote won't work off the host. Or without the host's parent runtime.
    If your host has other dynamic imports that load after its remote, you will get chunk load timeout or failed to load errors from webpack. Since the remote will overwrite the hosts chunk loading global.

  12. ScriptedAlchemy commented on Sep 30, 2024

    @ScriptedAlchemy
    Contributor

    Set experiments.federationRuntime = "hoisted"

    https://module-federation.io/configure/experiments.html

  13. webpack-bot commented on Dec 30, 2024

    @webpack-bot
    Contributor

    This issue had no activity for at least three months.

    It's subject to automatic issue closing if there is no activity in the next 15 days.

  14. added
    status: staleNo author response after a needs: request; close candidate
    on Dec 30, 2024
  15. webpack-bot commented on Jan 14, 2025

    @webpack-bot
    Contributor

    Issue was closed because of inactivity.

    If you think this is still a valid issue, please file a new issue with additional information.

  16. smelukov commented on Apr 24, 2025

    @smelukov
    ContributorAuthor

    @ScriptedAlchemy, do you have any plans or ideas for solving duplication cases? We have a really huge project with tons of MF bundles and exposes. If exposes use the same module, this module will be duplicated into two expose chunks.
    For example, we have two forms with a form component. These forms use the button component. The button component will be duplicated in the final bundle.

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

    status: staleNo author response after a needs: request; close candidate

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions