Skip to content

Commit c052a4c

Browse files
committed
refactor(router): make spa-init more compressible
keeping the window and attributes as-is actually compresses better than using string indexing
1 parent 2807b3b commit c052a4c

File tree

3 files changed

+70
-77
lines changed

3 files changed

+70
-77
lines changed

packages/qwik-router/src/runtime/src/qwik-router-component.tsx

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ import { createLoaderSignal, isSameOrigin, isSamePath, toUrl } from './utils';
7575
import { startViewTransition } from './view-transition';
7676
import transitionCss from './qwik-view-transition.css?inline';
7777

78+
declare const window: ClientSPAWindow;
79+
7880
/**
7981
* @deprecated Use `QWIK_ROUTER_SCROLLER` instead (will be removed in V3)
8082
* @public
@@ -254,16 +256,15 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
254256
(preventNav.$cbs$ ||= new Set()).add(fn$);
255257
// we need the QRLs to be synchronous if possible, for the beforeunload event
256258
fn$.resolve();
257-
// TS thinks we're a webworker and doesn't know about beforeunload
258-
(window as any).addEventListener('beforeunload', preventNav.$handler$);
259+
window.addEventListener('beforeunload', preventNav.$handler$);
259260

260261
return () => {
261262
if (preventNav.$cbs$) {
262263
preventNav.$cbs$.delete(fn$);
263264
if (!preventNav.$cbs$.size) {
264265
preventNav.$cbs$ = undefined;
265266
// unregister the event listener if no more callbacks, to make older Firefox happy
266-
(window as any).removeEventListener('beforeunload', preventNav.$handler$);
267+
window.removeEventListener('beforeunload', preventNav.$handler$!);
267268
}
268269
}
269270
};
@@ -354,7 +355,7 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
354355
restoreScroll(type, dest, new URL(location.href), scroller, getScrollHistory());
355356

356357
if (type === 'popstate') {
357-
(window as ClientSPAWindow)._qRouterScrollEnabled = true;
358+
window._qRouterScrollEnabled = true;
358359
}
359360
}
360361

@@ -571,32 +572,32 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
571572
}
572573
CLIENT_DATA_CACHE.clear();
573574

574-
const win = window as ClientSPAWindow;
575-
if (!win._qRouterSPA) {
575+
// See also spa-init.ts
576+
if (!window._qRouterSPA) {
576577
// only add event listener once
577-
win._qRouterSPA = true;
578+
window._qRouterSPA = true;
578579
history.scrollRestoration = 'manual';
579580

580-
win.addEventListener('popstate', () => {
581+
window.addEventListener('popstate', () => {
581582
// Disable scroll handler eagerly to prevent overwriting history.state.
582-
win._qRouterScrollEnabled = false;
583-
clearTimeout(win._qRouterScrollDebounce);
583+
window._qRouterScrollEnabled = false;
584+
clearTimeout(window._qRouterScrollDebounce);
584585

585586
goto(location.href, {
586587
type: 'popstate',
587588
});
588589
});
589590

590-
win.removeEventListener('popstate', win._qRouterInitPopstate!);
591-
win._qRouterInitPopstate = undefined;
591+
window.removeEventListener('popstate', window._qRouterInitPopstate!);
592+
window._qRouterInitPopstate = undefined;
592593

593594
// Browsers natively will remember scroll on ALL history entries, incl. custom pushState.
594595
// Devs could push their own states that we can't control.
595596
// If a user doesn't initiate scroll after, it will not have any scrollState.
596597
// We patch these to always include scrollState.
597598
// TODO Block this after Navigation API PR, browsers that support it have a Navigation API solution.
598-
if (!win._qRouterHistoryPatch) {
599-
win._qRouterHistoryPatch = true;
599+
if (!window._qRouterHistoryPatch) {
600+
window._qRouterHistoryPatch = true;
600601
const pushState = history.pushState;
601602
const replaceState = history.replaceState;
602603

@@ -658,8 +659,8 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
658659
history.pushState(null, '', dest);
659660
}
660661

661-
win._qRouterScrollEnabled = false;
662-
clearTimeout(win._qRouterScrollDebounce);
662+
window._qRouterScrollEnabled = false;
663+
clearTimeout(window._qRouterScrollDebounce);
663664
saveScrollHistory({
664665
...currentScrollState(scroller),
665666
x: 0,
@@ -674,8 +675,8 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
674675
}
675676
});
676677

677-
document.removeEventListener('click', win._qRouterInitAnchors!);
678-
win._qRouterInitAnchors = undefined;
678+
document.removeEventListener('click', window._qRouterInitAnchors!);
679+
window._qRouterInitAnchors = undefined;
679680

680681
// TODO Remove block after Navigation API PR.
681682
// Calling `history.replaceState` during `visibilitychange` in Chromium will nuke BFCache.
@@ -686,10 +687,10 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
686687
'visibilitychange',
687688
() => {
688689
if (
689-
(win._qRouterScrollEnabled || win._qCityScrollEnabled) &&
690+
(window._qRouterScrollEnabled || window._qCityScrollEnabled) &&
690691
document.visibilityState === 'hidden'
691692
) {
692-
if (win._qCityScrollEnabled) {
693+
if (window._qCityScrollEnabled) {
693694
console.warn(
694695
'"_qCityScrollEnabled" is deprecated. Use "_qRouterScrollEnabled" instead.'
695696
);
@@ -703,39 +704,39 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
703704
{ passive: true }
704705
);
705706

706-
document.removeEventListener('visibilitychange', win._qRouterInitVisibility!);
707-
win._qRouterInitVisibility = undefined;
707+
document.removeEventListener('visibilitychange', window._qRouterInitVisibility!);
708+
window._qRouterInitVisibility = undefined;
708709
}
709710

710-
win.addEventListener(
711+
window.addEventListener(
711712
'scroll',
712713
() => {
713714
// TODO: remove "_qCityScrollEnabled" condition in v3
714-
if (!win._qRouterScrollEnabled && !win._qCityScrollEnabled) {
715+
if (!window._qRouterScrollEnabled && !window._qCityScrollEnabled) {
715716
return;
716717
}
717718

718-
clearTimeout(win._qRouterScrollDebounce);
719-
win._qRouterScrollDebounce = setTimeout(() => {
719+
clearTimeout(window._qRouterScrollDebounce);
720+
window._qRouterScrollDebounce = setTimeout(() => {
720721
const scrollState = currentScrollState(scroller);
721722
saveScrollHistory(scrollState);
722723
// Needed for e2e debounceDetector.
723-
win._qRouterScrollDebounce = undefined;
724+
window._qRouterScrollDebounce = undefined;
724725
}, 200);
725726
},
726727
{ passive: true }
727728
);
728729

729-
removeEventListener('scroll', win._qRouterInitScroll!);
730-
win._qRouterInitScroll = undefined;
730+
removeEventListener('scroll', window._qRouterInitScroll!);
731+
window._qRouterInitScroll = undefined;
731732

732733
// Cache SPA recovery script.
733734
spaInit.resolve();
734735
}
735736

736737
if (navType !== 'popstate') {
737-
win._qRouterScrollEnabled = false;
738-
clearTimeout(win._qRouterScrollDebounce);
738+
window._qRouterScrollEnabled = false;
739+
clearTimeout(window._qRouterScrollDebounce);
739740

740741
// Save the final scroll state before pushing new state.
741742
// Upgrades/replaces state with scroll pos on nav as needed.
@@ -768,7 +769,7 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
768769
container.setAttribute(Q_ROUTE, routeName);
769770
const scrollState = currentScrollState(scroller);
770771
saveScrollHistory(scrollState);
771-
win._qRouterScrollEnabled = true;
772+
window._qRouterScrollEnabled = true;
772773
if (isBrowser) {
773774
callRestoreScrollOnDocument();
774775
}

packages/qwik-router/src/runtime/src/router-outlet-component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const RouterOutlet = component$(() => {
5454
if (s) {
5555
w.scrollTo(s.x, s.y);
5656
}
57+
// Tell qwikloader to run the spaInit code
5758
document.dispatchEvent(new Event('qcinit'));
5859
}
5960
})(window, history);

packages/qwik-router/src/runtime/src/spa-init.ts

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type { RouteNavigate, ScrollState } from './types';
55

66
import { event$, isDev } from '@qwik.dev/core';
77

8+
declare const window: ClientSPAWindow;
9+
810
// TODO Dedupe handler code from here and QwikRouterProvider?
911
// TODO Navigation API; check for support & simplify.
1012

@@ -15,29 +17,14 @@ import { event$, isDev } from '@qwik.dev/core';
1517

1618
// ! DO NOT IMPORT OR USE ANY EXTERNAL REFERENCES IN THIS SCRIPT.
1719
export default event$((_: Event, el: Element) => {
18-
const win: ClientSPAWindow = window;
19-
const spa = '_qRouterSPA';
20-
const initPopstate = '_qRouterInitPopstate';
21-
const initAnchors = '_qRouterInitAnchors';
22-
const initVisibility = '_qRouterInitVisibility';
23-
const initScroll = '_qRouterInitScroll';
24-
if (
25-
!win[spa] &&
26-
!win[initPopstate] &&
27-
!win[initAnchors] &&
28-
!win[initVisibility] &&
29-
!win[initScroll]
30-
) {
20+
// This complements qwik-router-component.ts
21+
// only run once, when router didn't init yet
22+
if (!window._qRouterSPA && !window._qRouterInitPopstate) {
3123
const currentPath = location.pathname + location.search;
3224

33-
const historyPatch = '_qRouterHistoryPatch';
34-
const scrollEnabled = '_qRouterScrollEnabled';
35-
const debounceTimeout = '_qRouterScrollDebounce';
36-
const scrollHistory = '_qRouterScroll';
37-
3825
const checkAndScroll = (scrollState: ScrollState | undefined) => {
3926
if (scrollState) {
40-
win.scrollTo(scrollState.x, scrollState.y);
27+
window.scrollTo(scrollState.x, scrollState.y);
4128
}
4229
};
4330

@@ -53,20 +40,20 @@ export default event$((_: Event, el: Element) => {
5340

5441
const saveScrollState = (scrollState?: ScrollState) => {
5542
const state: ScrollHistoryState = history.state || {};
56-
state[scrollHistory] = scrollState || currentScrollState();
43+
state._qRouterScroll = scrollState || currentScrollState();
5744
history.replaceState(state, '');
5845
};
5946

6047
saveScrollState();
6148

62-
win[initPopstate] = () => {
63-
if (win[spa]) {
49+
window._qRouterInitPopstate = () => {
50+
if (window._qRouterSPA) {
6451
return;
6552
}
6653

6754
// Disable scroll handler eagerly to prevent overwriting history.state.
68-
win[scrollEnabled] = false;
69-
clearTimeout(win[debounceTimeout]);
55+
window._qRouterScrollEnabled = false;
56+
clearTimeout(window._qRouterScrollDebounce);
7057

7158
if (currentPath !== location.pathname + location.search) {
7259
const getContainer = (el: Element) =>
@@ -88,15 +75,15 @@ export default event$((_: Event, el: Element) => {
8875
}
8976
} else {
9077
if (history.scrollRestoration === 'manual') {
91-
const scrollState = (history.state as ScrollHistoryState)?.[scrollHistory];
78+
const scrollState = (history.state as ScrollHistoryState)?._qRouterScroll;
9279
checkAndScroll(scrollState);
93-
win[scrollEnabled] = true;
80+
window._qRouterScrollEnabled = true;
9481
}
9582
}
9683
};
9784

98-
if (!win[historyPatch]) {
99-
win[historyPatch] = true;
85+
if (!window._qRouterHistoryPatch) {
86+
window._qRouterHistoryPatch = true;
10087
const pushState = history.pushState;
10188
const replaceState = history.replaceState;
10289

@@ -132,8 +119,8 @@ export default event$((_: Event, el: Element) => {
132119
}
133120

134121
// We need this handler in init because Firefox destroys states w/ anchor tags.
135-
win[initAnchors] = (event: MouseEvent) => {
136-
if (win[spa] || event.defaultPrevented) {
122+
window._qRouterInitAnchors = (event: MouseEvent) => {
123+
if (window._qRouterSPA || event.defaultPrevented) {
137124
return;
138125
}
139126

@@ -160,8 +147,8 @@ export default event$((_: Event, el: Element) => {
160147
} else {
161148
// Simulate same-page (no hash) anchor reload.
162149
// history.scrollRestoration = 'manual' makes these not scroll.
163-
win[scrollEnabled] = false;
164-
clearTimeout(win[debounceTimeout]);
150+
window._qRouterScrollEnabled = false;
151+
clearTimeout(window._qRouterScrollDebounce);
165152
saveScrollState({ ...currentScrollState(), x: 0, y: 0 });
166153
location.reload();
167154
}
@@ -176,34 +163,38 @@ export default event$((_: Event, el: Element) => {
176163
}
177164
};
178165

179-
win[initVisibility] = () => {
180-
if (!win[spa] && win[scrollEnabled] && document.visibilityState === 'hidden') {
166+
window._qRouterInitVisibility = () => {
167+
if (
168+
!window._qRouterSPA &&
169+
window._qRouterScrollEnabled &&
170+
document.visibilityState === 'hidden'
171+
) {
181172
saveScrollState();
182173
}
183174
};
184175

185-
win[initScroll] = () => {
186-
if (win[spa] || !win[scrollEnabled]) {
176+
window._qRouterInitScroll = () => {
177+
if (window._qRouterSPA || !window._qRouterScrollEnabled) {
187178
return;
188179
}
189180

190-
clearTimeout(win[debounceTimeout]);
191-
win[debounceTimeout] = setTimeout(() => {
181+
clearTimeout(window._qRouterScrollDebounce);
182+
window._qRouterScrollDebounce = setTimeout(() => {
192183
saveScrollState();
193184
// Needed for e2e debounceDetector.
194-
win[debounceTimeout] = undefined;
185+
window._qRouterScrollDebounce = undefined;
195186
}, 200);
196187
};
197188

198-
win[scrollEnabled] = true;
189+
window._qRouterScrollEnabled = true;
199190

200191
setTimeout(() => {
201-
win.addEventListener('popstate', win[initPopstate]!);
202-
win.addEventListener('scroll', win[initScroll]!, { passive: true });
203-
document.addEventListener('click', win[initAnchors]!);
192+
window.addEventListener('popstate', window._qRouterInitPopstate!);
193+
window.addEventListener('scroll', window._qRouterInitScroll!, { passive: true });
194+
document.addEventListener('click', window._qRouterInitAnchors!);
204195

205-
if (!(win as any).navigation) {
206-
document.addEventListener('visibilitychange', win[initVisibility]!, {
196+
if (!(window as any).navigation) {
197+
document.addEventListener('visibilitychange', window._qRouterInitVisibility!, {
207198
passive: true,
208199
});
209200
}

0 commit comments

Comments
 (0)