I have setup a test project with dynamic module federation using Vite. You can find the test repo here: https://github.com/iconag-bbasmer/vite-module-federation-test
The issue I have though is that if I want to load multiple external modules into the container app it fails as soon as the sub-modules use React Hooks.
I have put the modules that should be loaded into a json file (see container/public/externalRemotes.json). If I only add one component there, it works without problem. But as soon as I add multiple components to load, it breaks because it seems that there is some kind of race condition and React functionality is not available.
In the test project about I have added a simple useState in vapp1/src/components/AppHeader.tsx like this:
const [check, setCheck] = React.useState<boolean>(false);
This simple Hook call causes the app to break. If I remove this, from the component, everything works fine.
The relevant part where I try to dynamically load the components is in container/src/components/RemoteComponent/index.tsx
import React, { FC } from "react";
import ErrorBoundary from "../ErrorBoundary";
type Props = {
fallback?: string | React.ReactNode;
modulesToLoad: any[];
scope?: string;
[key: string]: any;
};
const loadComponent = (remoteUrl: string, moduleName: string) => async () => {
const container = await import(remoteUrl);
const factory = await container.get(moduleName);
const Module = factory();
return Module;
};
const RemoteComponent: FC<Props> = ({ modulesToLoad, scope = "default", fallback = null, ...props }) => {
const [components, setCompoments] = React.useState<any>();
React.useEffect(() => {
if (modulesToLoad.length > 0) {
const promises = modulesToLoad.map(
async (module: any) => await React.lazy(loadComponent(module.remoteUrl, `./${module.module}`))
);
Promise.all(promises).then((results) => {
let tempComponents: any[] = [];
results.forEach((Component) => {
tempComponents.push(
<ErrorBoundary>
<React.Suspense fallback={fallback}>{<Component {...props} />}</React.Suspense>
</ErrorBoundary>
);
});
// debugger;
setCompoments(tempComponents);
});
}
}, [modulesToLoad]);
return <>{components && components.map((component: any) => component)}</>;
};
export default RemoteComponent;
And the loaded components get placed into the container app on a button click in container/src/App.tsx
import * as React from "react";
import "./App.css";
import useUserStore from "sbhContainer/UserStore";
import RemoteComponent from "./components/RemoteComponent";
function App() {
const [user] = useUserStore();
const [showInfo, setShowInfo] = React.useState<boolean>(false);
const [loadedModules, setLoadedModules] = React.useState<any[]>([]);
const loadModules = (e: any) => {
fetch("./externalRemotes.json").then((response) => {
response.json().then((obj) => {
setLoadedModules(obj);
});
});
};
React.useEffect(() => {
setShowInfo(true);
}, [loadedModules]);
return (
<div className="App">
<div>
<h1>Container</h1>
User in Container: {user}
</div>
<div style={{ marginTop: 10 }}>
<button onClick={loadModules} style={{ backgroundColor: "#444" }}>
Load Modules
</button>
{showInfo === true ? (
<>
<RemoteComponent modulesToLoad={loadedModules} fallback={<div>Loading...</div>} />
</>
) : null}
</div>
</div>
);
}
export default App;
As mentioned above, this is all just test code, so please be patient with me. :-)
Maybe the repo above helps people to understand dynamic module federation but the issue mentioned above unfortunately makes it all unusable. I suspect that the issue is some kind of race condition but I'm not sure how to fix it. So any help would be greatly appreciated.