core icon indicating copy to clipboard operation
core copied to clipboard

Sharescopes do not work

Open itd-cba opened this issue 10 months ago • 18 comments

Describe the bug

Issue: Module Federation shareScopes Not Working as Expected

Description

We are currently migrating our large application to Module Federation and need to utilize the shareScopes feature because we cannot upgrade all microfrontends to new React versions simultaneously. Our approach is to have a shareScope for each React version currently in use and include all additional dependencies in the same React scope.

However, we are encountering an issue where sharing a scope that is not the "default" is not working. We can see the different scopes in the window.__FEDERATION variable, but two microfrontends using the react19 scope are not sharing their dependencies correctly and end up loading their own React instances.

Current Setup

We have a main app that integrates all remotes. The remotes define their shared dependencies via the rsbuild plugin. The main app registers the remotes at runtime with the following code:

import { registerPlugins, registerRemotes, init } from '@module-federation/enhanced/runtime';

export const registerMicroFrontends = async () => {
  try {
    const microfrontendUrls = await getAllMicrofrontends();

    const remotes = Object.entries(microfrontendUrls).map(([key, value]) => ({
      name: key,
      entry: `${value.baseUrl}/mf-manifest.json`,
    }));

    registerRemotes(remotes);
    registerPlugins([errorHandlingPlugin()]);
  } catch (e) {
    logger.warn('Failed to register (some) microfrontends.', e);
  }
};

The main app defines its shared dependencies at build time using the following configuration:

pluginModuleFederation({
  name: 'appaxolotl',
  dts: false,
  shared: [
    {
      react: { singleton: true, requiredVersion: packageJson.dependencies.react },
      'react-dom': { singleton: true, requiredVersion: packageJson.dependencies['react-dom'] },
    },
  ],
});

For microfrontends that use React 19, we use the following configuration and attempt to leverage the shareScope feature:

pluginModuleFederation({
  name: 'portfoliopenguin',
  dts: false,
  runtimePlugins: [],
  exposes: {
    './bootstrap': './src/bootstrap.tsx',
  },
  shared: [
    {
      react: { singleton: true, requiredVersion: packageJson.dependencies.react, shareScope: 'react19' },
      'react-dom': { singleton: true, requiredVersion: packageJson.dependencies['react-dom'], shareScope: 'react19' },
      '@mui/material': { singleton: false, requiredVersion: packageJson.dependencies['@mui/material'], shareScope: 'react19' },
    },
  ],
});

After the microfrondends got registered by the main app they will be loaded via loadRemote when they are actually needed. The "filterfalcon" gets loaded right after the remotes got registered and the second microfrontend is loaded when its route gets called.

Observed Behavior

At runtime, both React 19 microfrontends have the react19 shareScope, but they do not share dependencies correctly. Here is a snapshot of the window__FEDERATION__SHARE__.

Image

And here the broken part: Here we can see that both have the same scope but still both load their own dependencies. Image

Expected Behavior

Microfrontends using the same react19 scope should share the React instance instead of loading separate copies.

Questions

  • Are we missing any additional runtime configuration required for shareScopes?

Any guidance or insights would be greatly appreciated!

Used Package Manager

pnpm

System Info

- Module Federation Version: 0.8.9
- React Versions: 18.3.1, 19.0.0
- Build Tool: `rsbuild`

Validations

itd-cba avatar Jan 20 '25 15:01 itd-cba

@ScriptedAlchemy can you have a look? the feature is not very documented in detail

itd-cba avatar Jan 21 '25 08:01 itd-cba

@itd-cba provide a repo

ScriptedAlchemy avatar Jan 21 '25 17:01 ScriptedAlchemy

I think I'm seeing this same issue. I'm using nx and we just bumped past 19.5.0 where they started consuming @module-federation's packages. I'm seeing separate scopes as well, and I'm trying to nail down the evidence. Will work towards providing a repo if I can. At a minimum will see if I can get the devtools screenshot like you have, @itd-cba

RichFinn-WTW avatar Feb 06 '25 16:02 RichFinn-WTW

@RichFinn-WTW if you do end up getting a repo together please feel free to tag me and @Coly010 to take a peek.

zackarychapple avatar Feb 06 '25 18:02 zackarychapple

@RichFinn-WTW if you do end up getting a repo together please feel free to tag me and @Coly010 to take a peek.

Thanks @zackarychapple - I'm getting closer. I'm able to prove that the issue was introduced in nx 19.5.0. It's behind the __webpack_share_scopes__, but I can't get a debugger to float the value to dig in to the specifics. I'm having to drill into code that's tough to read, even with sourcemaps.

RichFinn-WTW avatar Feb 06 '25 19:02 RichFinn-WTW

I think I've found it. Not exactly what @itd-cba is seeing, but can prove that remoteUrlDefinitions in nx-angular-mf.js is set in one scope when called via setRemoteDefinitions(definitions) and then is empty in a second scope when loadRemoteModule(remoteName, moduleName) is called later.

We were stuck after having upgraded to nx v20.4.0 (ng 19.1.3) from nx v18.1.2. To continue moving forward we downgraded @nx/angular and @nx/webpack to 19.4.4, leaving everything else at 20.4.0, and removed "@nx/module-federation": "20.4.0". Things are now working fine using the original nx module federation functionality.

I'll work on getting a repo out there that contains branches with the different configurations we have to demonstrate the path we took as well as the issue.

RichFinn-WTW avatar Feb 07 '25 00:02 RichFinn-WTW

I've created a repository that demonstrates the issue as I see it using nx 20.4.2:

https://github.com/RichFinn-WTW/nx-mfe-scope-issue

The readme sets the stage and describes what's happening. Thanks for taking a look.

@zackarychapple @Coly010

RichFinn-WTW avatar Feb 10 '25 18:02 RichFinn-WTW

@zackarychapple @Coly010 - were you able to take a look at the repo? Should I open a new issue in nx or leave this here?

RichFinn-WTW avatar Feb 24 '25 15:02 RichFinn-WTW

let me check this issue :D

2heal1 avatar Feb 26 '25 09:02 2heal1

@RichFinn-WTW I can not install the deps ... https://github.com/RichFinn-WTW/nx-mfe-scope-issue/tree/nx-20.4.2

if i run yarn install in root dir , it will not install the src/org deps , only empty

if i run yarn install in the src/org , it will throw error Cannot find module '/nx-mfe-scope-issue/src/org/.yarn/releases/yarn-1.22.22.cjs'

Any progress i miss ?

2heal1 avatar Feb 26 '25 09:02 2heal1

@2heal1 - sorry about that. Looks like the yarn .cjs file was being excluded by git. I've added the file and updated the branches. Should be working now.

You should run yarn from the src/org dir

RichFinn-WTW avatar Feb 26 '25 14:02 RichFinn-WTW

Okay here is the context:

  1. The @org/federated-component import @nx/angular/mf
  2. @org/federated-component is also be import by host.
  3. And the @org/federated-component is set shared in host.

The key point which cause the issue: The @nx/angular/mf package has been bundled twice.

Tthe setRemoteDefinitions function(export by @nx/angular/mf) will add remotes to its own map in memory . And now there are two modules, but the loadRemoteContainer use the one which not call setRemoteDefinitions, cause this error.

Workaround:

I add @nx/angular/mf to host additionalShared can solve this issue , but i'm not sure whether it's right solutions .

@ScriptedAlchemy Can you help check why @nx/angular/mf be bundled twice ?

2heal1 avatar Feb 27 '25 09:02 2heal1

I'm wonder if this because @nx/angular/mf content is too small , so not split new js asset ?

2heal1 avatar Feb 27 '25 09:02 2heal1

@2heal1 - by bundled twice, you mean that its showing up in 2 chunks?

From running the build - i see that that same package is in both vendor.js and node_modules_nx_angular_fesm2022_nx-angular-mf_mjs.js

which is most likely caused by some issue in the underlaying splitChunks configuration @Coly010 do you know anything about how chunking is working under the hood for this package?

ScriptedAlchemy avatar Feb 27 '25 20:02 ScriptedAlchemy

@ScriptedAlchemy It's handled by Angular so not entirely sure without some deeper investigations

Coly010 avatar Feb 27 '25 20:02 Coly010

Ahh likely a collision. Split chunks causes issues with shared modules. Vendor regex should be patched to exclude shared modules from going into other chunks again. Or set split chunk to false

ScriptedAlchemy avatar Feb 28 '25 01:02 ScriptedAlchemy

We are facing the same issue. Each shared dependency with shareScope is loaded again when navigating through the remote apps. We are using the Vite plugin (without nx). I created a minimal reproduction repository here: https://github.com/sodehl/scoped-dependencies-example

sodehl avatar Feb 28 '25 10:02 sodehl

Vite plugin is maintained by another group.

ScriptedAlchemy avatar Mar 15 '25 22:03 ScriptedAlchemy

Hey @ScriptedAlchemy, we are facing the same issue using the regular webpack plugin. I've created this repo https://github.com/KaiDoering/sharescope-singleton In our case we have a host, that is besides being the React entrypoint also exposing some API functios, so not sure if this general setup is already flawed...

With the share scope in place the page crashes, because of a hook issue (caused by duplicate Reacts) and FEDERATION contains

Image

and without

Image

KaiDoering avatar Apr 16 '25 12:04 KaiDoering

I dont think this as an issue with the way how nx or other plugins interact with module fedaration. This seems to stem from the fact that remote containers are always initialized with __webpack_share_scopes__.default, and there’s no standard mechanism or plugin support to initialize and pass a non-default shareScope during container setup.

As a result, modules declared under a custom shareScope remain isolated, and the intended sharing behavior doesn't occur unless we manually wire everything (e.g., call webpack_init_sharing('<custom>') and use webpack_share_scopes['<custom>']).

So as per here, we always initialize the default shareScope.

await __webpack_init_sharing__('default');

Also we mount the container with default shareScope as seen here.

await containerScope.init(webpackShareScopes.default as any);

There is no reference for the custom share scope though.

karthickshanmugam0689 avatar May 21 '25 10:05 karthickshanmugam0689

Stale issue message

github-actions[bot] avatar Jul 20 '25 15:07 github-actions[bot]

Note. Currently you cannot have parallel share scopes. If you use shareScope in the share object. Another runtime must exist where the top level shareScope on the plugin itself is set to the same.

1 runtime = 1 share scope. Youd need another runtime who doesn't use default for it to be provided.

ScriptedAlchemy avatar Jul 21 '25 08:07 ScriptedAlchemy

Stale issue message

github-actions[bot] avatar Sep 20 '25 15:09 github-actions[bot]