Module Federation Support
See original GitHub issueFeature request
@Timer @rauchg @timneutkens In order to stop my “Twitter Driven Development” (still love that quote) I have updated the description of this issue to hopefully help move this challenge forwards.
Is your feature request related to a problem? Please describe.
Support Module Federation ability to share vendor code, like react
Describe the solution you’d like
- We should not use entrypoint based routing, each entrypoint would require react and react-dom to be async imported, or each entrypoint would need to use the
import(./bootstrap)
method in order to allow webpack to figure out if react exists already or if it needs to go and download the chunk. Ideally, there should be only one entrypoint, then whatever else needs to happen is done so via code splitting and async imports. - An alternative nice to have would be to make
shared: {import:'react',eager:true}
work - this would force react to be loaded upfront - however it seems that react is included multiple times in each entrypoint - causing various multiple copies of react issues. If eager worked, I could at least share react and force the remote to always use the hosts copy of react (I think) - this would / should be okay since next mostly requires us to be on a somewhat consistent version of react. Like next 11 will use v17, since all my apps use the latest nextjs, they will be compliment with a v17 version of react.
The bare minimum to get this working client or server-side would be to have some flag that changes how the client entrypoint gets started. If the client.js
looked like this https://github.com/module-federation/module-federation-examples/blob/master/bi-directional/app1/src/index.js then there’s a high likelihood we could hold up the application until whatever shared modules with static imports were ready before hydration begins.
Current workaround
My workaround no longer works for SSR (since 10.x), but works for CSR - is to alias react
as some external global https://github.com/module-federation/nextjs-mf/blob/main/react.js
~I then patchSharing
which is an ultra hack that literally inlines react into the head of _document
as a script
https://github.com/module-federation/nextjs-mf/blob/main/patchSharing.js~ I use resolve.alias
against react$
to point react imports to my custom react file https://github.com/module-federation/nextjs-mf/blob/main/withModuleFederation.js#L27
~This is not optimal for production-grade use but the limits of next give us little choice~ Seems okay for production grade apps
Even with Webpack 5, what we have done to get our federated AB testing, Tag manager, and AB testing engine to work with next is apply a shim that creates a fake sharing API that attaches react to share scope manually. This mechanism is the same one that I used a year ago when next was still webpack 4 based and I was using webpack 5 remotes against v4 hosts.
import logger from "../logger";
/* This logic is needed because we're not using webpack 5 yet */
const sharedExports = {};
const shareExports = (exports, remote = "") => {
/*
exports is expected to be an object
{
"react": ()=> Promise.resolve(()=>require("react"))
}
The value should be:
- a function that returns a promise
- that promise should resolve to a function
- that function should return the module
*/
// if no remote is given "" is used and that will apply to all remote entries
// This method is additive, there is no "unshare"
sharedExports[remote] = Object.assign(sharedExports[remote] || {}, exports);
};
const applySharedExports = (remote) => {
if (typeof window !== "undefined") {
// Given a remote entry, we gather all the modules that the host application has already
const mergedExports = {
...sharedExports?.[""], // apply globally shared modules
...sharedExports?.[remote] // modules shared only for this remote entry
};
// Logic for applying @ScriptedAlchemy
// check if using new webpack API
const override = window?.[remote]?.init
? window?.[remote]?.init
: window?.[remote]?.override;
if (window?.[remote]?.init) {
Object.assign(
mergedExports,
Object.entries(mergedExports).reduce((acc, [key, value]) => {
if (typeof value === "function") {
let version = "16.13.1";
try {
// eslint-disable-next-line
version = value.version;
} catch (error) {
logger.error("Error retrieving shared version", error);
}
Object.assign(acc, {
[key]: {
[version]: {
get: value,
loaded: true,
from: "dar"
}
}
});
}
return acc;
}, {})
);
}
if (override) {
try {
override(Object.assign(mergedExports, __webpack_require__.O));
} catch (error) {
logger.warn(`Unable to apply webpack 5 shim of shared exports`, error);
}
}
}
};
export { shareExports, applySharedExports };
Issue Analytics
- State:
- Created 3 years ago
- Reactions:216
- Comments:54 (20 by maintainers)
Top GitHub Comments
As of today, I was able to use just the Module Federation plugin with next js.
It appears that sharing works now, at least when it’s in eager mode.
~As of my testing today - nextjs supports federation without any workarounds~. 🎉
It’s almost too good to be true.
I’ll perform some deeper tests tomorrow to confirm.
Update on this.~I’ve found a way to move the async boundary into the webpack runtime. This circumvents limitations of next.js by changing how webpack startup runtime module works.~
~Replacing the startup module will effectively prevent next from initializing till webpack container negotiations take place.~
~I only uncovered this today, so it’ll take some time to modify and PR webpack.~
Too hard