Skip to content

Commit 7493fd5

Browse files
committed
chore(router): make spa-init more readable
this also improves compressed size also, remove the resolve for spaInit, the preloader takes care of that
1 parent f5c31e6 commit 7493fd5

File tree

2 files changed

+152
-169
lines changed

2 files changed

+152
-169
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,6 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
708708

709709
removeEventListener('scroll', win._qRouterInitScroll!);
710710
win._qRouterInitScroll = undefined;
711-
712-
// Cache SPA recovery script.
713-
spaInit.resolve();
714711
}
715712

716713
if (navType !== 'popstate') {

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

Lines changed: 152 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -16,197 +16,183 @@ import { event$, isDev } from '@qwik.dev/core';
1616
// ! DO NOT IMPORT OR USE ANY EXTERNAL REFERENCES IN THIS SCRIPT.
1717
export default event$((_: Event, el: Element) => {
1818
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-
) {
31-
const currentPath = location.pathname + location.search;
32-
33-
const historyPatch = '_qRouterHistoryPatch';
34-
const scrollEnabled = '_qRouterScrollEnabled';
35-
const debounceTimeout = '_qRouterScrollDebounce';
36-
const scrollHistory = '_qRouterScroll';
37-
38-
const checkAndScroll = (scrollState: ScrollState | undefined) => {
39-
if (scrollState) {
40-
win.scrollTo(scrollState.x, scrollState.y);
41-
}
42-
};
19+
if (win._qRouterInitPopstate) {
20+
return;
21+
}
22+
const currentPath = location.pathname + location.search;
4323

44-
const currentScrollState = (): ScrollState => {
45-
const elm = document.documentElement;
46-
return {
47-
x: elm.scrollLeft,
48-
y: elm.scrollTop,
49-
w: Math.max(elm.scrollWidth, elm.clientWidth),
50-
h: Math.max(elm.scrollHeight, elm.clientHeight),
51-
};
24+
const checkAndScroll = (scrollState: ScrollState | undefined) => {
25+
if (scrollState) {
26+
win.scrollTo(scrollState.x, scrollState.y);
27+
}
28+
};
29+
30+
const currentScrollState = (): ScrollState => {
31+
const elm = document.documentElement;
32+
return {
33+
x: elm.scrollLeft,
34+
y: elm.scrollTop,
35+
w: Math.max(elm.scrollWidth, elm.clientWidth),
36+
h: Math.max(elm.scrollHeight, elm.clientHeight),
5237
};
38+
};
5339

54-
const saveScrollState = (scrollState?: ScrollState) => {
55-
const state: ScrollHistoryState = history.state || {};
56-
state[scrollHistory] = scrollState || currentScrollState();
57-
history.replaceState(state, '');
58-
};
40+
const saveScrollState = (scrollState?: ScrollState) => {
41+
const state: ScrollHistoryState = history.state || {};
42+
state._qRouterScroll = scrollState || currentScrollState();
43+
history.replaceState(state, '');
44+
};
5945

60-
saveScrollState();
46+
saveScrollState();
6147

62-
win[initPopstate] = () => {
63-
if (win[spa]) {
64-
return;
65-
}
48+
// Also used in qwik-router-component.tsx to unregister
49+
win._qRouterInitPopstate = () => {
50+
if (win._qRouterSPA) {
51+
return;
52+
}
6653

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

71-
if (currentPath !== location.pathname + location.search) {
72-
const getContainer = (el: Element) =>
73-
el.closest('[q\\:container]:not([q\\:container=html]):not([q\\:container=text])');
58+
if (currentPath !== location.pathname + location.search) {
59+
const getContainer = (el: Element) =>
60+
el.closest('[q\\:container]:not([q\\:container=html]):not([q\\:container=text])');
7461

75-
const container = getContainer(el);
76-
const domContainer = (container as _ContainerElement).qContainer as DomContainer;
77-
const hostElement = domContainer.vNodeLocate(el);
62+
const container = getContainer(el);
63+
const domContainer = (container as _ContainerElement).qContainer as DomContainer;
64+
const hostElement = domContainer.vNodeLocate(el);
7865

79-
const nav = domContainer?.resolveContext(hostElement, {
80-
id: 'qc--n',
81-
} as ContextId<RouteNavigate>);
66+
const nav = domContainer?.resolveContext(hostElement, {
67+
id: 'qc--n',
68+
} as ContextId<RouteNavigate>);
8269

83-
if (nav) {
84-
nav(location.href, { type: 'popstate' });
85-
} else {
86-
// No useNavigate ctx available, fallback to reload.
87-
location.reload();
88-
}
70+
if (nav) {
71+
nav(location.href, { type: 'popstate' });
8972
} else {
90-
if (history.scrollRestoration === 'manual') {
91-
const scrollState = (history.state as ScrollHistoryState)?.[scrollHistory];
92-
checkAndScroll(scrollState);
93-
win[scrollEnabled] = true;
73+
// No useNavigate ctx available, fallback to reload.
74+
location.reload();
75+
}
76+
} else {
77+
if (history.scrollRestoration === 'manual') {
78+
const scrollState = (history.state as ScrollHistoryState)?._qRouterScroll;
79+
checkAndScroll(scrollState);
80+
win._qRouterScrollEnabled = true;
81+
}
82+
}
83+
};
84+
85+
if (!win._qRouterHistoryPatch) {
86+
win._qRouterHistoryPatch = true;
87+
const pushState = history.pushState;
88+
const replaceState = history.replaceState;
89+
90+
const prepareState = (state: any) => {
91+
if (state === null || typeof state === 'undefined') {
92+
state = {};
93+
} else if (state?.constructor !== Object) {
94+
state = { _data: state };
95+
96+
if (isDev) {
97+
console.warn(
98+
'In a Qwik SPA context, `history.state` is used to store scroll state. ' +
99+
'Direct calls to `pushState()` and `replaceState()` must supply an actual Object type. ' +
100+
'We need to be able to automatically attach the scroll state to your state object. ' +
101+
'A new state object has been created, your data has been moved to: `history.state._data`'
102+
);
94103
}
95104
}
96-
};
97105

98-
if (!win[historyPatch]) {
99-
win[historyPatch] = true;
100-
const pushState = history.pushState;
101-
const replaceState = history.replaceState;
102-
103-
const prepareState = (state: any) => {
104-
if (state === null || typeof state === 'undefined') {
105-
state = {};
106-
} else if (state?.constructor !== Object) {
107-
state = { _data: state };
108-
109-
if (isDev) {
110-
console.warn(
111-
'In a Qwik SPA context, `history.state` is used to store scroll state. ' +
112-
'Direct calls to `pushState()` and `replaceState()` must supply an actual Object type. ' +
113-
'We need to be able to automatically attach the scroll state to your state object. ' +
114-
'A new state object has been created, your data has been moved to: `history.state._data`'
115-
);
116-
}
117-
}
106+
state._qRouterScroll = state._qRouterScroll || currentScrollState();
107+
return state;
108+
};
118109

119-
state._qRouterScroll = state._qRouterScroll || currentScrollState();
120-
return state;
121-
};
110+
history.pushState = (state, title, url) => {
111+
state = prepareState(state);
112+
return pushState.call(history, state, title, url);
113+
};
122114

123-
history.pushState = (state, title, url) => {
124-
state = prepareState(state);
125-
return pushState.call(history, state, title, url);
126-
};
115+
history.replaceState = (state, title, url) => {
116+
state = prepareState(state);
117+
return replaceState.call(history, state, title, url);
118+
};
119+
}
127120

128-
history.replaceState = (state, title, url) => {
129-
state = prepareState(state);
130-
return replaceState.call(history, state, title, url);
131-
};
121+
// We need this handler in init because Firefox destroys states w/ anchor tags.
122+
win._qRouterInitAnchors = (event: MouseEvent) => {
123+
if (win._qRouterSPA || event.defaultPrevented) {
124+
return;
132125
}
133126

134-
// We need this handler in init because Firefox destroys states w/ anchor tags.
135-
win[initAnchors] = (event: MouseEvent) => {
136-
if (win[spa] || event.defaultPrevented) {
137-
return;
138-
}
139-
140-
const target = (event.target as HTMLElement).closest('a[href]');
141-
142-
if (target && !target.hasAttribute('preventdefault:click')) {
143-
const href = target.getAttribute('href')!;
144-
const prev = new URL(location.href);
145-
const dest = new URL(href, prev);
146-
const sameOrigin = dest.origin === prev.origin;
147-
const samePath = dest.pathname + dest.search === prev.pathname + prev.search;
148-
// Patch only same-page anchors.
149-
if (sameOrigin && samePath) {
150-
event.preventDefault();
151-
152-
// Check href because empty hashes don't register.
153-
if (dest.href !== prev.href) {
154-
history.pushState(null, '', dest);
155-
}
127+
const target = (event.target as HTMLElement).closest('a[href]');
128+
129+
if (target && !target.hasAttribute('preventdefault:click')) {
130+
const href = target.getAttribute('href')!;
131+
const prev = new URL(location.href);
132+
const dest = new URL(href, prev);
133+
const sameOrigin = dest.origin === prev.origin;
134+
const samePath = dest.pathname + dest.search === prev.pathname + prev.search;
135+
// Patch only same-page anchors.
136+
if (sameOrigin && samePath) {
137+
event.preventDefault();
138+
139+
// Check href because empty hashes don't register.
140+
if (dest.href !== prev.href) {
141+
history.pushState(null, '', dest);
142+
}
156143

157-
if (!dest.hash) {
158-
if (dest.href.endsWith('#')) {
159-
window.scrollTo(0, 0);
160-
} else {
161-
// Simulate same-page (no hash) anchor reload.
162-
// history.scrollRestoration = 'manual' makes these not scroll.
163-
win[scrollEnabled] = false;
164-
clearTimeout(win[debounceTimeout]);
165-
saveScrollState({ ...currentScrollState(), x: 0, y: 0 });
166-
location.reload();
167-
}
144+
if (!dest.hash) {
145+
if (dest.href.endsWith('#')) {
146+
window.scrollTo(0, 0);
168147
} else {
169-
const elmId = dest.hash.slice(1);
170-
const elm = document.getElementById(elmId);
171-
if (elm) {
172-
elm.scrollIntoView();
173-
}
148+
// Simulate same-page (no hash) anchor reload.
149+
// history.scrollRestoration = 'manual' makes these not scroll.
150+
win._qRouterScrollEnabled = false;
151+
clearTimeout(win._qRouterScrollDebounce);
152+
saveScrollState({ ...currentScrollState(), x: 0, y: 0 });
153+
location.reload();
154+
}
155+
} else {
156+
const elmId = dest.hash.slice(1);
157+
const elm = document.getElementById(elmId);
158+
if (elm) {
159+
elm.scrollIntoView();
174160
}
175161
}
176162
}
177-
};
178-
179-
win[initVisibility] = () => {
180-
if (!win[spa] && win[scrollEnabled] && document.visibilityState === 'hidden') {
181-
saveScrollState();
182-
}
183-
};
184-
185-
win[initScroll] = () => {
186-
if (win[spa] || !win[scrollEnabled]) {
187-
return;
188-
}
189-
190-
clearTimeout(win[debounceTimeout]);
191-
win[debounceTimeout] = setTimeout(() => {
192-
saveScrollState();
193-
// Needed for e2e debounceDetector.
194-
win[debounceTimeout] = undefined;
195-
}, 200);
196-
};
163+
}
164+
};
197165

198-
win[scrollEnabled] = true;
166+
win._qRouterInitVisibility = () => {
167+
if (!win._qRouterSPA && win._qRouterScrollEnabled && document.visibilityState === 'hidden') {
168+
saveScrollState();
169+
}
170+
};
199171

200-
setTimeout(() => {
201-
win.addEventListener('popstate', win[initPopstate]!);
202-
win.addEventListener('scroll', win[initScroll]!, { passive: true });
203-
document.body.addEventListener('click', win[initAnchors]!);
172+
win._qRouterInitScroll = () => {
173+
if (win._qRouterSPA || !win._qRouterScrollEnabled) {
174+
return;
175+
}
204176

205-
if (!(win as any).navigation) {
206-
document.addEventListener('visibilitychange', win[initVisibility]!, {
207-
passive: true,
208-
});
209-
}
210-
}, 0);
211-
}
177+
clearTimeout(win._qRouterScrollDebounce);
178+
win._qRouterScrollDebounce = setTimeout(() => {
179+
saveScrollState();
180+
// Needed for e2e debounceDetector.
181+
win._qRouterScrollDebounce = undefined;
182+
}, 200);
183+
};
184+
185+
win._qRouterScrollEnabled = true;
186+
187+
setTimeout(() => {
188+
win.addEventListener('popstate', win._qRouterInitPopstate!);
189+
win.addEventListener('scroll', win._qRouterInitScroll!, { passive: true });
190+
document.body.addEventListener('click', win._qRouterInitAnchors!);
191+
192+
if (!(win as any).navigation) {
193+
document.addEventListener('visibilitychange', win._qRouterInitVisibility!, {
194+
passive: true,
195+
});
196+
}
197+
}, 0);
212198
});

0 commit comments

Comments
 (0)