Skip to content

Commit

Permalink
refactor(remix-react): switch to use relList to feature detect link…
Browse files Browse the repository at this point in the history
… preload support (#7152)
  • Loading branch information
brophdawg11 authored Aug 14, 2023
1 parent c00cd0e commit f3915d6
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .changeset/disabled-link-preload.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@remix-run/react": patch
---

Add `<link rel="preload">` timeout counter and disabling logic in case preloading is disabled by the user in Firefox. This prevents us from hanging on client-side navigations when we try to preload stylesheets and never receive a `load`/`error` event on the `link` tag.
Skip preloading of stylesheets on client-side route transitions if the browser does not support `<link rel=preload>`. This prevents us from hanging on client-side navigations when we try to preload stylesheets and never receive a `load`/`error` event on the `link` tag.
65 changes: 23 additions & 42 deletions packages/remix-react/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,42 +221,12 @@ export function getKeyedLinksForMatches(
return dedupeLinkDescriptors(descriptors, preloads);
}

let stylesheetPreloadTimeouts = 0;
let isPreloadDisabled = false;

export async function prefetchStyleLinks(
routeModule: RouteModule
): Promise<void> {
if (!routeModule.links) return;
if (!routeModule.links || !isPreloadSupported()) 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) {
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) {
Expand All @@ -276,12 +246,13 @@ 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<boolean> {
): Promise<void> {
return new Promise((resolve) => {
let link = document.createElement("link");
Object.assign(link, descriptor);
Expand All @@ -295,20 +266,16 @@ async function prefetchStyleLink(
}
}

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

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

document.head.appendChild(link);
});
}
Expand Down Expand Up @@ -554,3 +521,17 @@ function parsePathPatch(href: string) {
if (path.search === undefined) path.search = "";
return path;
}

// Detect if this browser supports <link rel="preload"> (or has it enabled).
// Originally added to handle the firefox `network.preload` config:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1847811
let _isPreloadSupported: boolean | undefined;
function isPreloadSupported(): boolean {
if (_isPreloadSupported !== undefined) {
return _isPreloadSupported;
}
let el: HTMLLinkElement | null = document.createElement("link");
_isPreloadSupported = el.relList.supports("preload");
el = null;
return _isPreloadSupported;
}

0 comments on commit f3915d6

Please sign in to comment.