Skip to content

Commit e6f934a

Browse files
committed
feat: MPA fallback for when SPA Link navigation is too slow
1 parent 322ad28 commit e6f934a

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

packages/qwik-city/src/runtime/src/link-component.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const Link = component$<LinkProps>((props) => {
6868
const preventDefault = clientNavPath
6969
? sync$((event: MouseEvent, target: HTMLAnchorElement) => {
7070
if (!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
71+
(window as any).__qwikPendingFallbackHref = target.href;
7172
event.preventDefault();
7273
}
7374
})
@@ -80,10 +81,12 @@ export const Link = component$<LinkProps>((props) => {
8081
if (elm.hasAttribute('q:nbs')) {
8182
// Allow bootstrapping into useNavigate.
8283
await nav(location.href, { type: 'popstate' });
84+
delete (window as any).__qwikPendingFallbackHref;
8385
} else if (elm.href) {
8486
elm.setAttribute('aria-pressed', 'true');
8587
await nav(elm.href, { forceReload: reload, replaceState, scroll });
8688
elm.removeAttribute('aria-pressed');
89+
delete (window as any).__qwikPendingFallbackHref;
8790
}
8891
}
8992
})

packages/qwik/src/core/preloader/queue.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export let shouldResetFactor: boolean;
1515
let queueDirty: boolean;
1616
let preloadCount = 0;
1717
const queue: BundleImport[] = [];
18+
const MPA_FALLBACK_THRESHOLD = 100;
1819

1920
export const log = (...args: any[]) => {
2021
// eslint-disable-next-line no-console
@@ -203,7 +204,23 @@ export const adjustProbabilities = (
203204
* too.
204205
*/
205206
let newInverseProbability: number;
206-
if (probability === 1 || (probability >= 0.99 && depsCount < 100)) {
207+
208+
/**
209+
* 100 deps to be preloaded at once would mean a ~10s delay on chrome 3G throttling.
210+
*
211+
* This can happen for Link components as they load all of the route bundles at once, but in
212+
* this case we fallback to MPA.
213+
*
214+
* This should never happen for a normal component. But in case it happens, we set the limit
215+
* based on MPA_FALLBACK_THRESHOLD + 1 === 101 (to ensure the fallback works), because if the
216+
* user has to wait for 10s before anything happens it is possible that they try to click on
217+
* something else, in which case we don't want to block reprioritization of this new event
218+
* bundles for too long. (If browsers supported aborting modulepreloads, we wouldn't have to
219+
* do this.)
220+
*
221+
* TODO: Set the limit to a number of kb instead of a number of bundles.
222+
*/
223+
if (probability === 1 || (probability >= 0.99 && depsCount <= MPA_FALLBACK_THRESHOLD + 1)) {
207224
depsCount++;
208225
// we're loaded at max probability, so elevate dynamic imports to 99% sure
209226
newInverseProbability = Math.min(0.01, 1 - dep.$importProbability$);
@@ -217,6 +234,8 @@ export const adjustProbabilities = (
217234
dep.$factor$ = factor;
218235
}
219236

237+
dispatchMPAFallback();
238+
220239
adjustProbabilities(depBundle, newInverseProbability, seen);
221240
}
222241
}
@@ -225,6 +244,7 @@ export const adjustProbabilities = (
225244
export const handleBundle = (name: string, inverseProbability: number) => {
226245
const bundle = getBundle(name);
227246
if (bundle && bundle.$inverseProbability$ > inverseProbability) {
247+
// prioritize the event bundles first
228248
adjustProbabilities(bundle, inverseProbability);
229249
}
230250
};
@@ -267,3 +287,24 @@ if (isBrowser) {
267287
}
268288
});
269289
}
290+
291+
/**
292+
* On chrome 3G throttling, 10kb takes ~1s to download Bundles weight ~1kb on average, so 100
293+
* bundles is ~100kb which takes ~10s to download.
294+
*
295+
* We want to fallback to MPA if more than 100 bundles are queued because MPA is always faster than
296+
* ~10s (usually between 3-7s).
297+
*
298+
* Note: if the next route bundles have already been preloaded, the fallback won't be triggered.
299+
*
300+
* TODO: get total kb size and compare with 100kb instead of relying on the number of bundles.
301+
*/
302+
const dispatchMPAFallback = () => {
303+
const nextRouteBundles = queue.filter((item) => item.$inverseProbability$ <= 0.1);
304+
if (nextRouteBundles.length >= MPA_FALLBACK_THRESHOLD) {
305+
const href = (window as any).__qwikPendingFallbackHref;
306+
if (href) {
307+
window.location.href = href;
308+
}
309+
}
310+
};

0 commit comments

Comments
 (0)