Skip to content

When a remote is offline, it takes multiple seconds on Windows before a remote is considered as unavailable. #2367

@patricklafrance

Description

@patricklafrance

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.

image

Similar issues has been observed for other projects:

@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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions