Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle disabled preload config in firefox #7106

Merged
merged 4 commits into from
Aug 8, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions packages/remix-react/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,46 @@ export function getLinksForMatches(
return dedupe(descriptors, preloads);
}

let stylesheetPreloadTimeouts = 0;
let isPreloadDisabled = false;

export async function prefetchStyleLinks(
routeModule: RouteModule
): Promise<void> {
if (!routeModule.links) return;
let descriptors = routeModule.links();
if (!descriptors) return;

if (isPreloadDisabled) {
return;
}

// If we've hit our timeout 3 times, we may be in firefox with the
// `network.preload` config disabled and we'll _never_ get onload/onerror
// callbacks. Let's try to confirm this with a totally invalid link preload
// which should immediately throw the onerror
if (stylesheetPreloadTimeouts >= 3) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented on the Firefox bug, but you really want document.createElement("link").relList.supports("preload") or so.

In any case, I wouldn't bother dealing with it unless you want to also deal with browsers that don't support <link rel=preload> (which is fine, but can be simpler as per the above).

let linkLoadedOrErrored = await prefetchStyleLink({
rel: "preload",
as: "style",
href: "__remix-preload-detection-404.css",
});
if (linkLoadedOrErrored) {
// If this processed correctly, then our previous timeouts were probably
// legit, reset the counter.
stylesheetPreloadTimeouts = 0;
} else {
// If this bogus preload also times out without an onerror then it's safe
// to assume preloading is disabled and let's just stop trying. This
// _will_ cause FOUC on destination pages but there's nothing we can
// really do there if preloading is disabled since client-side injected
// scripts aren't render blocking. Maybe eventually React's client side
// async component stuff will provide an easier solution here
console.warn("Disabling preload due to lack of browser support");
isPreloadDisabled = true;
}
}

let styleLinks: HtmlLinkDescriptor[] = [];
for (let descriptor of descriptors) {
if (!isPageLinkDescriptor(descriptor) && descriptor.rel === "stylesheet") {
Expand All @@ -246,17 +279,15 @@ export async function prefetchStyleLinks(
(!link.media || window.matchMedia(link.media).matches) &&
!document.querySelector(`link[rel="stylesheet"][href="${link.href}"]`)
);

await Promise.all(matchingLinks.map(prefetchStyleLink));
}

async function prefetchStyleLink(
descriptor: HtmlLinkDescriptor
): Promise<void> {
): Promise<boolean> {
return new Promise((resolve) => {
let link = document.createElement("link");
Object.assign(link, descriptor);

function removeLink() {
// if a navigation interrupts this prefetch React will update the <head>
// and remove the link we put in there manually, so we check if it's still
Expand All @@ -266,20 +297,23 @@ async function prefetchStyleLink(
}
}

link.onload = () => {
// Allow 3s for the link preload to timeout
let timeoutId = setTimeout(() => {
stylesheetPreloadTimeouts++;
removeLink();
resolve();
};
resolve(false);
}, 3_000);

link.onerror = () => {
let done = () => {
clearTimeout(timeoutId);
removeLink();
resolve();
resolve(true);
};

link.onload = done;
link.onerror = done;
document.head.appendChild(link);
});
}

////////////////////////////////////////////////////////////////////////////////
export function isPageLinkDescriptor(
object: any
Expand Down