loading a remote wipes out the registered shared modules by the host even if they are declared singleton
Describe the bug
When a host is initialized with some shared modules say react and then host loads a remote which also shares react, the module registered by the host is removed from the shared scope (__FEDERATION__.__SHARE__) and is replaced with the one brought in by the remote.
This happens even if the host registers the module as "singleton".
The problem is that host doesn't "load" the shared module before the remote is loaded.
Expected behavior: Since the host registers a module in the shared scope first and marks it as singleton, subsequent remotes which depend on the same module should use the one provided by the host.
Reproduction
https://stackblitz.com/edit/stackblitz-starters-rsuexa?file=packages%2Fhost-shell%2Fhost-shell.js
Used Package Manager
npm
System Info
- NA
Validations
- [X] Read the docs.
- [X] Read the common issues list.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a Module federation issue and not a framework-specific issue.
- [X] The provided reproduction is a minimal reproducible example of the bug.
federation is assign based, it object assigns new over old, since the keys (version) is the same - it doesn't matter to init order. You can put it into loaded-first which may prevent this.
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
const sharedStrategy: () => FederationRuntimePlugin = () => ({
name: 'shared-strategy-plugin',
beforeInit(args) {
const { userOptions } = args;
const shared = userOptions.shared;
if (shared) {
Object.keys(shared).forEach((sharedKey) => {
const sharedConfigs = shared[sharedKey];
const arraySharedConfigs = Array.isArray(sharedConfigs)
? sharedConfigs
: [sharedConfigs];
arraySharedConfigs.forEach((s) => {
s.strategy = 'loaded-first';
});
});
}
return args;
},
});
export default sharedStrategy;
The solution works! but .....
It works when I use @module-federation/enhanced/webpack. But when I use webpack/lib/container/ModuleFederationPlugin.js, it doesn't work because there's no concept of runtime plugins in that.
I have updated my above mentioned stackblitz example to reflect it.
But interestingly, beforeInit is not even called for the MF 1.0 remote. Even if it is called, because there's no federation runtime, the only way a MF 1.0 remote will not wipe out the share scope is if we forcefully add loaded: true to the packages provided by the host.
Hence my proposal is to modify the SharedBaseArgs to add one additional param to mark any module as loaded. If loaded: true is an internal state, we can rename it to something else like preferred or whatever, but internally it will mark that module as loaded: true after which MF 1.0 container will start using it.
@ScriptedAlchemy @zackarychapple https://github.com/module-federation/core/issues/2739 also highlights the same issue. I think we should solve for this as this is a very common issue that enterprises have.
This issues happens even if singleton: true is set together import: false. As a result react is fetched and loaded multiple times from the host URL by all remotes.
Also it seems that the same issue happens with exposes, so If my host expose some module and the module has been already loaded by the host, all remotes will re-load it again.
Can you mutate __webpack_share_scopes__ and manually assign the loaded param
This issues happens even if
singleton: trueis set togetherimport: false. As a result react is fetched and loaded multiple times from the host URL by all remotes.Also it seems that the same issue happens with exposes, so If my host expose some module and the module has been already loaded by the host, all remotes will re-load it again.
It depends on how you are consuming things, do you have repo?
Can you mutate
__webpack_share_scopes__and manually assign the loaded param
The host is built using rollup. There's no such variable __webpack_share_scopes__ until we load a weback remote.
It would be great if we had a way of doing it directly using the runtime apis instead of hacking it through webpack internal variables.
Verified that it's working now with version 0.4.0
Updated the stackblitz example linked above to reflect that.