-
-
Notifications
You must be signed in to change notification settings - Fork 374
Description
Describe the bug
When a remote is offline, it takes multiple seconds on Windows before a remote is considered as unavailable, leaving the users with a blank page until the shared dependencies negotiation is done and the application is rendered.
According to my POC, a production build hosted on Netlify, when a remote is offline on Windows, it takes approximately 2500ms until the application is rendered.
On macOS, it takes about 20ms, which is a lot faster.
This delay doesn't seems to be related to any custom code but rather on how Windows behave when a connection is refused. It seems to retry 3 times before failing with ERR_CONNECTION_REFUSED.
Similar issues has been observed for other projects:
- https://stackoverflow.com/questions/19440364/why-do-failed-attempts-of-socket-connect-take-1-sec-on-windows
- Connect timeout on windows when the server isn't running instead of immediate Connection Refused warmcat/libwebsockets#570
@ryok90 and I have been extensively discussing about this issue in the past few days on the Module Federation Discord's server and came to the conclusion that there is currently nothing offered by Module Federation to actually help with this issue: https://discord.com/channels/1055442562959290389/1060923312043212920/threads/1232010715381108929
👉🏻 We believe that the solution would be for Module Federation to include a mechanism allowing the authors to specify a timeout delay to fetch a remote, rather the relying on the OS defaults.
When the host is configuring a remote with a mf-manifest.json file, it seems like the manifest is fetched with fetch, a timeout could be introduced with an AbortSignal.
When the host is configuring a remote with a remoteEntry.js file, it seems like the remote is fetched with a script element. Something similar to the following code could be added to eagerly reject a remote when it is offline. This is how we used to do it with Module Federation 1.0:
function loadRemoteScript(url: string, { timeoutDelay = 500 }: LoadRemoteScriptOptions = {}) {
return new Promise((resolve, reject) => {
const element = document.createElement("script");
// Adding a timestamp to make sure the remote entry points are never cached.
// View: https://github.com/module-federation/module-federation-examples/issues/566.
element.src = `${url}?t=${Date.now()}`;
element.type = "text/javascript";
element.async = true;
let timeoutId: number | undefined = undefined;
let hasCanceled = false;
function cancel(error: Error) {
hasCanceled = true;
element?.parentElement?.removeChild(element);
reject({
error,
hasCanceled: true
});
}
element.onload = () => {
window.clearTimeout(timeoutId);
element?.parentElement?.removeChild(element);
resolve({});
};
element.onerror = (error: unknown) => {
if (!hasCanceled) {
window.clearTimeout(timeoutId);
element?.parentElement?.removeChild(element);
reject({
error,
hasCanceled: false
});
}
};
document.head.appendChild(element);
// Eagerly reject the loading of a script, it's too long when a remote is unavailable.
timeoutId = window.setTimeout(() => {
cancel(new Error(`[squide] Remote script "${url}" time-outed.`));
}, timeoutDelay);
});
}Thank you,
Patrick
Reproduction
https://pat-mf-enhanced-poc.netlify.app/
Used Package Manager
pnpm
System Info
System:
OS: Windows 11 10.0.22631
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 13.30 GB / 31.70 GB
Binaries:
Node: 21.7.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
npm: 10.5.0 - C:\Program Files\nodejs\npm.CMD
pnpm: 8.15.4 - ~\AppData\Roaming\npm\pnpm.CMD
Browsers:
Chrome: 124.0.6367.61
Edge: Chromium (123.0.2420.97)Validations
- Read the docs.
- Read the common issues list.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Module federation issue and not a framework-specific issue.
- The provided reproduction is a minimal reproducible example of the bug.
