vike 0.4.144-commit-de18325 → 0.4.145-commit-2520555
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/node/plugin/plugins/buildConfig.js +1 -1
- package/dist/cjs/node/plugin/plugins/config/index.js +3 -3
- package/dist/cjs/node/plugin/plugins/devConfig/determineOptimizeDeps.js +1 -1
- package/dist/cjs/node/plugin/plugins/importUserCode/getVirtualFileImportUserCode.js +1 -1
- package/dist/cjs/node/plugin/plugins/importUserCode/index.js +1 -1
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +1 -1
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +5 -2
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +4 -2
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +18 -6
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/transpileAndExecuteFile.js +2 -2
- package/dist/cjs/node/prerender/runPrerender.js +17 -8
- package/dist/cjs/node/runtime/renderPage/log404/index.js +2 -1
- package/dist/cjs/node/shared/getConfigVike.js +4 -1
- package/dist/cjs/shared/page-configs/serialize/parseConfigValuesImported.js +8 -5
- package/dist/cjs/shared/route/noRouteMatch.js +4 -0
- package/dist/cjs/utils/isExternalLink.js +7 -0
- package/dist/cjs/utils/onPageVisibilityChange.js +19 -0
- package/dist/cjs/utils/projectInfo.js +1 -1
- package/dist/esm/client/client-routing-runtime/createPageContext.js +0 -1
- package/dist/esm/client/client-routing-runtime/getBaseServer.d.ts +2 -1
- package/dist/esm/client/client-routing-runtime/getBaseServer.js +2 -1
- package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.d.ts +39 -0
- package/dist/esm/client/client-routing-runtime/{getPageContext.js → getPageContextFromHooks.js} +48 -74
- package/dist/esm/client/client-routing-runtime/history.js +9 -5
- package/dist/esm/client/client-routing-runtime/installClientRouter.d.ts +0 -19
- package/dist/esm/client/client-routing-runtime/installClientRouter.js +11 -488
- package/dist/esm/client/client-routing-runtime/isClientSideRoutable.d.ts +3 -3
- package/dist/esm/client/client-routing-runtime/isClientSideRoutable.js +4 -7
- package/dist/esm/client/client-routing-runtime/navigate.js +2 -3
- package/dist/esm/client/client-routing-runtime/onBrowserHistoryNavigation.d.ts +4 -0
- package/dist/esm/client/client-routing-runtime/onBrowserHistoryNavigation.js +63 -0
- package/dist/esm/client/client-routing-runtime/onLinkClick.d.ts +2 -0
- package/dist/esm/client/client-routing-runtime/onLinkClick.js +40 -0
- package/dist/esm/client/client-routing-runtime/prefetch.js +7 -8
- package/dist/esm/client/client-routing-runtime/renderPageClientSide.d.ts +19 -0
- package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +347 -0
- package/dist/esm/client/client-routing-runtime/scrollRestoration.d.ts +6 -0
- package/dist/esm/client/client-routing-runtime/scrollRestoration.js +25 -0
- package/dist/esm/client/client-routing-runtime/setScrollPosition.d.ts +7 -0
- package/dist/esm/client/client-routing-runtime/setScrollPosition.js +77 -0
- package/dist/esm/client/client-routing-runtime/skipLink.js +9 -4
- package/dist/esm/client/client-routing-runtime/utils.d.ts +2 -0
- package/dist/esm/client/client-routing-runtime/utils.js +2 -0
- package/dist/esm/node/plugin/plugins/buildConfig.js +2 -2
- package/dist/esm/node/plugin/plugins/config/index.js +4 -4
- package/dist/esm/node/plugin/plugins/devConfig/determineOptimizeDeps.js +2 -2
- package/dist/esm/node/plugin/plugins/importUserCode/getVirtualFileImportUserCode.js +1 -1
- package/dist/esm/node/plugin/plugins/importUserCode/index.js +1 -1
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +1 -1
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.d.ts +2 -1
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +6 -3
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.d.ts +2 -2
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +4 -2
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.d.ts +2 -2
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +18 -6
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/transpileAndExecuteFile.js +3 -3
- package/dist/esm/node/prerender/runPrerender.js +17 -8
- package/dist/esm/node/runtime/renderPage/log404/index.js +2 -1
- package/dist/esm/node/shared/getConfigVike.d.ts +2 -1
- package/dist/esm/node/shared/getConfigVike.js +4 -1
- package/dist/esm/shared/page-configs/serialize/parseConfigValuesImported.js +9 -3
- package/dist/esm/shared/route/noRouteMatch.d.ts +1 -0
- package/dist/esm/shared/route/noRouteMatch.js +1 -0
- package/dist/esm/utils/onPageVisibilityChange.d.ts +4 -0
- package/dist/esm/utils/onPageVisibilityChange.js +16 -0
- package/dist/esm/utils/projectInfo.d.ts +1 -1
- package/dist/esm/utils/projectInfo.js +1 -1
- package/node/cli/bin-entry.js +1 -1
- package/package.json +1 -1
- package/dist/esm/client/client-routing-runtime/getPageContext.d.ts +0 -28
- package/dist/esm/client/client-routing-runtime/navigationState.d.ts +0 -5
- package/dist/esm/client/client-routing-runtime/navigationState.js +0 -14
- /package/dist/esm/{client/client-routing-runtime → utils}/isExternalLink.d.ts +0 -0
- /package/dist/esm/{client/client-routing-runtime → utils}/isExternalLink.js +0 -0
|
@@ -1,499 +1,22 @@
|
|
|
1
1
|
export { installClientRouter };
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { addLinkPrefetchHandlers } from './prefetch.js';
|
|
10
|
-
import { assertInfo, assertWarning, isReact } from './utils.js';
|
|
11
|
-
import { executeOnRenderClientHook } from '../shared/executeOnRenderClientHook.js';
|
|
12
|
-
import { assertHook } from '../../shared/hooks/getHook.js';
|
|
13
|
-
import { skipLink } from './skipLink.js';
|
|
14
|
-
import { isErrorFetchingStaticAssets } from '../shared/loadPageFilesClientSide.js';
|
|
15
|
-
import { initHistoryState, getHistoryState, pushHistory, saveScrollPosition, monkeyPatchHistoryPushState } from './history.js';
|
|
16
|
-
import { assertNoInfiniteAbortLoop, getPageContextFromAllRewrites, isAbortError, logAbortErrorHandled } from '../../shared/route/abort.js';
|
|
17
|
-
import { route } from '../../shared/route/index.js';
|
|
18
|
-
import { isClientSideRoutable } from './isClientSideRoutable.js';
|
|
19
|
-
const globalObject = getGlobalObject('installClientRouter.ts', { previousState: getState(), renderCounter: 0 });
|
|
2
|
+
import { assert } from './utils.js';
|
|
3
|
+
import { initHistoryState, monkeyPatchHistoryPushState } from './history.js';
|
|
4
|
+
import { getRenderCount, renderPageClientSide } from './renderPageClientSide.js';
|
|
5
|
+
import { onBrowserHistoryNavigation } from './onBrowserHistoryNavigation.js';
|
|
6
|
+
import { onLinkClick } from './onLinkClick.js';
|
|
7
|
+
import { setupNativeScrollRestoration } from './scrollRestoration.js';
|
|
8
|
+
import { autoSaveScrollPosition } from './setScrollPosition.js';
|
|
20
9
|
function installClientRouter() {
|
|
21
10
|
setupNativeScrollRestoration();
|
|
22
11
|
initHistoryState();
|
|
23
12
|
autoSaveScrollPosition();
|
|
24
13
|
monkeyPatchHistoryPushState();
|
|
14
|
+
// First initial render
|
|
15
|
+
assert(getRenderCount() === 0);
|
|
16
|
+
renderPageClientSide({ scrollTarget: 'preserve-scroll', isBackwardNavigation: null, isClientSideNavigation: false });
|
|
17
|
+
assert(getRenderCount() === 1);
|
|
25
18
|
// Intercept <a> links
|
|
26
19
|
onLinkClick();
|
|
27
20
|
// Handle back-/forward navigation
|
|
28
21
|
onBrowserHistoryNavigation();
|
|
29
|
-
// Intial render
|
|
30
|
-
renderPageClientSide({ scrollTarget: 'preserve-scroll', isBackwardNavigation: null });
|
|
31
|
-
}
|
|
32
|
-
async function renderPageClientSide(renderArgs) {
|
|
33
|
-
const { scrollTarget, urlOriginal = getCurrentUrl(), overwriteLastHistoryEntry = false, isBackwardNavigation, checkIfClientSideRenderable, pageContextsFromRewrite = [], redirectCount = 0, isUserLandNavigation } = renderArgs;
|
|
34
|
-
assertNoInfiniteAbortLoop(pageContextsFromRewrite.length, redirectCount);
|
|
35
|
-
if (globalObject.clientRoutingIsDisabled) {
|
|
36
|
-
serverSideRouteTo(urlOriginal);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const pageContext = await createPageContext(urlOriginal);
|
|
40
|
-
objectAssign(pageContext, {
|
|
41
|
-
isBackwardNavigation
|
|
42
|
-
});
|
|
43
|
-
{
|
|
44
|
-
const pageContextFromAllRewrites = getPageContextFromAllRewrites(pageContextsFromRewrite);
|
|
45
|
-
objectAssign(pageContext, pageContextFromAllRewrites);
|
|
46
|
-
}
|
|
47
|
-
let hasError = false;
|
|
48
|
-
let err;
|
|
49
|
-
{
|
|
50
|
-
let pageContextFromRoute;
|
|
51
|
-
try {
|
|
52
|
-
pageContextFromRoute = await route(pageContext);
|
|
53
|
-
}
|
|
54
|
-
catch (err_) {
|
|
55
|
-
hasError = true;
|
|
56
|
-
err = err_;
|
|
57
|
-
}
|
|
58
|
-
if (pageContextFromRoute) {
|
|
59
|
-
objectAssign(pageContext, pageContextFromRoute);
|
|
60
|
-
if (checkIfClientSideRenderable) {
|
|
61
|
-
const isClientRoutable = await isClientSideRoutable(pageContext);
|
|
62
|
-
if (!isClientRoutable) {
|
|
63
|
-
serverSideRouteTo(urlOriginal);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (isUserLandNavigation && pageContextFromRoute._pageId === globalObject.previousPageContext?._pageId) {
|
|
68
|
-
// Skip's Vike's rendering; let the user handle the navigation
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const { renderNumber, shouldAbort, setHydrationCanBeAborted } = getRenderNumber();
|
|
74
|
-
const isFirstRenderAttempt = renderNumber === 1;
|
|
75
|
-
objectAssign(pageContext, { _isFirstRenderAttempt: isFirstRenderAttempt });
|
|
76
|
-
// Start transition before any await's
|
|
77
|
-
if (renderNumber > 1) {
|
|
78
|
-
if (!globalObject.isTransitioning) {
|
|
79
|
-
await globalObject.onPageTransitionStart?.(pageContext);
|
|
80
|
-
globalObject.isTransitioning = true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (shouldAbort()) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
let pageContextAddendum;
|
|
87
|
-
if (!hasError) {
|
|
88
|
-
try {
|
|
89
|
-
pageContextAddendum = await getPageContext(pageContext);
|
|
90
|
-
}
|
|
91
|
-
catch (err_) {
|
|
92
|
-
hasError = true;
|
|
93
|
-
err = err_;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (hasError) {
|
|
97
|
-
if (!isAbortError(err)) {
|
|
98
|
-
// We don't swallow 404 errors:
|
|
99
|
-
// - On the server-side, Vike swallows / doesn't show any 404 error log because it's expected that a user may go to some random non-existent URL. (We don't want to flood the app's error tracking with 404 logs.)
|
|
100
|
-
// - On the client-side, if the user navigates to a 404 then it means that the UI has a broken link. (It isn't expected that users can go to some random URL using the client-side router, as it would require, for example, the user to manually change the URL of a link by manually manipulating the DOM which highly unlikely.)
|
|
101
|
-
console.error(err);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// We swallow throw redirect()/render() called by client-side hooks onBeforeRender() and guard()
|
|
105
|
-
// We handle the abort error down below.
|
|
106
|
-
}
|
|
107
|
-
if (shouldSwallowAndInterrupt(err, pageContext))
|
|
108
|
-
return;
|
|
109
|
-
if (isAbortError(err)) {
|
|
110
|
-
const errAbort = err;
|
|
111
|
-
logAbortErrorHandled(err, pageContext._isProduction, pageContext);
|
|
112
|
-
const pageContextAbort = errAbort._pageContextAbort;
|
|
113
|
-
// throw render('/some-url')
|
|
114
|
-
if (pageContextAbort._urlRewrite) {
|
|
115
|
-
await renderPageClientSide({
|
|
116
|
-
...renderArgs,
|
|
117
|
-
scrollTarget: 'scroll-to-top-or-hash',
|
|
118
|
-
checkIfClientSideRenderable: true,
|
|
119
|
-
pageContextsFromRewrite: [...pageContextsFromRewrite, pageContextAbort]
|
|
120
|
-
});
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
// throw redirect('/some-url')
|
|
124
|
-
if (pageContextAbort._urlRedirect) {
|
|
125
|
-
const urlRedirect = pageContextAbort._urlRedirect.url;
|
|
126
|
-
if (urlRedirect.startsWith('http')) {
|
|
127
|
-
// External redirection
|
|
128
|
-
window.location.href = urlRedirect;
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
await renderPageClientSide({
|
|
133
|
-
...renderArgs,
|
|
134
|
-
scrollTarget: 'scroll-to-top-or-hash',
|
|
135
|
-
urlOriginal: urlRedirect,
|
|
136
|
-
overwriteLastHistoryEntry: false,
|
|
137
|
-
isBackwardNavigation: false,
|
|
138
|
-
checkIfClientSideRenderable: true,
|
|
139
|
-
redirectCount: redirectCount + 1
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
// throw render(statusCode)
|
|
145
|
-
assert(pageContextAbort.abortStatusCode);
|
|
146
|
-
objectAssign(pageContext, pageContextAbort);
|
|
147
|
-
if (pageContextAbort.abortStatusCode === 404) {
|
|
148
|
-
objectAssign(pageContext, { is404: true });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
objectAssign(pageContext, { is404: checkIf404(err) });
|
|
153
|
-
}
|
|
154
|
-
try {
|
|
155
|
-
pageContextAddendum = await getPageContextErrorPage(pageContext);
|
|
156
|
-
}
|
|
157
|
-
catch (err2) {
|
|
158
|
-
// - When user hasn't defined a `_error.page.js` file
|
|
159
|
-
// - Some unpexected vike internal error
|
|
160
|
-
if (shouldSwallowAndInterrupt(err2, pageContext))
|
|
161
|
-
return;
|
|
162
|
-
if (!isFirstRenderAttempt) {
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
// We let the server show the 404 page
|
|
165
|
-
window.location.pathname = urlOriginal;
|
|
166
|
-
}, 0);
|
|
167
|
-
}
|
|
168
|
-
if (!isEquivalentError(err, err2)) {
|
|
169
|
-
throw err2;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// Abort
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
assert(pageContextAddendum);
|
|
178
|
-
objectAssign(pageContext, pageContextAddendum);
|
|
179
|
-
assertHook(pageContext, 'onPageTransitionStart');
|
|
180
|
-
globalObject.onPageTransitionStart = pageContext.exports.onPageTransitionStart;
|
|
181
|
-
if (pageContext.exports.hydrationCanBeAborted) {
|
|
182
|
-
setHydrationCanBeAborted();
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
assertWarning(!isReact(), 'You seem to be using React; we recommend setting hydrationCanBeAborted to true, see https://vike.dev/clientRouting', { onlyOnce: true });
|
|
186
|
-
}
|
|
187
|
-
if (shouldAbort()) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (globalObject.renderPromise) {
|
|
191
|
-
// Always make sure that the previous render has finished,
|
|
192
|
-
// otherwise that previous render may finish after this one.
|
|
193
|
-
await globalObject.renderPromise;
|
|
194
|
-
}
|
|
195
|
-
if (shouldAbort()) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
changeUrl(urlOriginal, overwriteLastHistoryEntry);
|
|
199
|
-
navigationState.markNavigationChange();
|
|
200
|
-
globalObject.previousPageContext = pageContext;
|
|
201
|
-
assert(globalObject.renderPromise === undefined);
|
|
202
|
-
globalObject.renderPromise = (async () => {
|
|
203
|
-
await executeOnRenderClientHook(pageContext, true);
|
|
204
|
-
addLinkPrefetchHandlers(pageContext);
|
|
205
|
-
})();
|
|
206
|
-
await globalObject.renderPromise;
|
|
207
|
-
globalObject.renderPromise = undefined;
|
|
208
|
-
if (pageContext._isFirstRenderAttempt) {
|
|
209
|
-
assertHook(pageContext, 'onHydrationEnd');
|
|
210
|
-
const { onHydrationEnd } = pageContext.exports;
|
|
211
|
-
if (onHydrationEnd) {
|
|
212
|
-
const hookFilePath = pageContext.exportsAll.onHydrationEnd[0].exportSource;
|
|
213
|
-
assert(hookFilePath);
|
|
214
|
-
await executeHook(() => onHydrationEnd(pageContext), 'onHydrationEnd', hookFilePath);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else if (renderNumber === globalObject.renderCounter) {
|
|
218
|
-
if (pageContext.exports.onPageTransitionEnd) {
|
|
219
|
-
assertHook(pageContext, 'onPageTransitionEnd');
|
|
220
|
-
await pageContext.exports.onPageTransitionEnd(pageContext);
|
|
221
|
-
}
|
|
222
|
-
globalObject.isTransitioning = undefined;
|
|
223
|
-
}
|
|
224
|
-
setScrollPosition(scrollTarget);
|
|
225
|
-
browserNativeScrollRestoration_disable();
|
|
226
|
-
globalObject.initialRenderIsDone = true;
|
|
227
|
-
}
|
|
228
|
-
function onLinkClick() {
|
|
229
|
-
document.addEventListener('click', onClick);
|
|
230
|
-
return;
|
|
231
|
-
// Code adapted from https://github.com/HenrikJoreteg/internal-nav-helper/blob/5199ec5448d0b0db7ec63cf76d88fa6cad878b7d/src/index.js#L11-L29
|
|
232
|
-
function onClick(ev) {
|
|
233
|
-
if (!isNormalLeftClick(ev))
|
|
234
|
-
return;
|
|
235
|
-
const linkTag = findLinkTag(ev.target);
|
|
236
|
-
if (!linkTag)
|
|
237
|
-
return;
|
|
238
|
-
const url = linkTag.getAttribute('href');
|
|
239
|
-
if (skipLink(linkTag))
|
|
240
|
-
return;
|
|
241
|
-
assert(url);
|
|
242
|
-
ev.preventDefault();
|
|
243
|
-
const keepScrollPosition = ![null, 'false'].includes(linkTag.getAttribute('keep-scroll-position'));
|
|
244
|
-
const scrollTarget = keepScrollPosition ? 'preserve-scroll' : 'scroll-to-top-or-hash';
|
|
245
|
-
renderPageClientSide({
|
|
246
|
-
scrollTarget,
|
|
247
|
-
urlOriginal: url,
|
|
248
|
-
isBackwardNavigation: false,
|
|
249
|
-
checkIfClientSideRenderable: true
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
function isNormalLeftClick(ev) {
|
|
253
|
-
return ev.button === 0 && !ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey;
|
|
254
|
-
}
|
|
255
|
-
function findLinkTag(target) {
|
|
256
|
-
while (target.tagName !== 'A') {
|
|
257
|
-
const { parentNode } = target;
|
|
258
|
-
if (!parentNode) {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
target = parentNode;
|
|
262
|
-
}
|
|
263
|
-
return target;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function onBrowserHistoryNavigation() {
|
|
267
|
-
// - The popstate event is trigged upon:
|
|
268
|
-
// - Back-/forward navigation.
|
|
269
|
-
// - By user clicking on his browser's back-/forward navigation (or using a shortcut)
|
|
270
|
-
// - By JavaScript: `history.back()` / `history.forward()`
|
|
271
|
-
// - URL hash change.
|
|
272
|
-
// - By user clicking on a hash link `<a href="#some-hash" />`
|
|
273
|
-
// - The popstate event is *only* triggered if `href` starts with '#' (even if `href` is '/#some-hash' while the current URL's pathname is '/' then the popstate still isn't triggered)
|
|
274
|
-
// - By JavaScript: `location.hash = 'some-hash'`
|
|
275
|
-
// - The `event` of `window.addEventListener('popstate', (event) => /*...*/)` is useless: the History API doesn't provide the previous state (the popped state), see https://stackoverflow.com/questions/48055323/is-history-state-always-the-same-as-popstate-event-state
|
|
276
|
-
window.addEventListener('popstate', () => {
|
|
277
|
-
const currentState = getState();
|
|
278
|
-
const scrollTarget = currentState.historyState.scrollPosition || 'scroll-to-top-or-hash';
|
|
279
|
-
const isUserLandNavigation = currentState.historyState.triggedBy === 'user';
|
|
280
|
-
const isHashNavigation = currentState.urlWithoutHash === globalObject.previousState.urlWithoutHash;
|
|
281
|
-
const isBackwardNavigation = !currentState.historyState.timestamp || !globalObject.previousState.historyState.timestamp
|
|
282
|
-
? null
|
|
283
|
-
: currentState.historyState.timestamp < globalObject.previousState.historyState.timestamp;
|
|
284
|
-
globalObject.previousState = currentState;
|
|
285
|
-
if (isHashNavigation && !isUserLandNavigation) {
|
|
286
|
-
// - `history.state` is uninitialized (`null`) when:
|
|
287
|
-
// - The user's code runs `window.location.hash = '#section'`.
|
|
288
|
-
// - The user clicks on an anchor link `<a href="#section">Section</a>` (because Vike's `onLinkClick()` handler skips hash links).
|
|
289
|
-
// - `history.state` is `null` when uninitialized: https://developer.mozilla.org/en-US/docs/Web/API/History/state
|
|
290
|
-
// - Alternatively, we completely take over hash navigation and reproduce the browser's native behavior upon hash navigation.
|
|
291
|
-
// - Problem: we cannot intercept `window.location.hash = '#section'`. (Or maybe we can with the `hashchange` event?)
|
|
292
|
-
// - Other potential problem: would there be a conflict when the user wants to override the browser's default behavior? E.g. for smooth scrolling, or when using hashes for saving states of some fancy animations.
|
|
293
|
-
// - Another alternative: we use the browser's scroll restoration mechanism (see `browserNativeScrollRestoration_enable()` below).
|
|
294
|
-
// - Problem: not clear when to call `browserNativeScrollRestoration_disable()`/`browserNativeScrollRestoration_enable()`
|
|
295
|
-
// - Other potential problem are inconsistencies between browsers: specification says that setting `window.history.scrollRestoration` only affects the current entry in the session history. But this seems to contradict what folks saying.
|
|
296
|
-
// - Specification: https://html.spec.whatwg.org/multipage/history.html#the-history-interface
|
|
297
|
-
// - https://stackoverflow.com/questions/70188241/history-scrollrestoration-manual-doesnt-prevent-safari-from-restoring-scrol
|
|
298
|
-
if (window.history.state === null) {
|
|
299
|
-
// The browser already scrolled to `#${hash}` => the current scroll position is the right one => we save it with `initHistoryState()`.
|
|
300
|
-
initHistoryState();
|
|
301
|
-
globalObject.previousState = getState();
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
// If `history.state !== null` then it means that `popstate` was triggered by the user clicking on his browser's forward/backward history button.
|
|
305
|
-
setScrollPosition(scrollTarget);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
renderPageClientSide({ scrollTarget, isBackwardNavigation, isUserLandNavigation });
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
function changeUrl(url, overwriteLastHistoryEntry) {
|
|
314
|
-
if (getCurrentUrl() === url)
|
|
315
|
-
return;
|
|
316
|
-
browserNativeScrollRestoration_disable();
|
|
317
|
-
pushHistory(url, overwriteLastHistoryEntry);
|
|
318
|
-
globalObject.previousState = getState();
|
|
319
|
-
}
|
|
320
|
-
function getState() {
|
|
321
|
-
return {
|
|
322
|
-
urlWithoutHash: getCurrentUrl({ withoutHash: true }),
|
|
323
|
-
historyState: getHistoryState()
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function setScrollPosition(scrollTarget) {
|
|
327
|
-
if (scrollTarget === 'preserve-scroll') {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
let scrollPosition;
|
|
331
|
-
if (scrollTarget === 'scroll-to-top-or-hash') {
|
|
332
|
-
const hash = getUrlHash();
|
|
333
|
-
// We replicate the browser's native behavior
|
|
334
|
-
if (hash && hash !== 'top') {
|
|
335
|
-
const hashTarget = document.getElementById(hash) || document.getElementsByName(hash)[0];
|
|
336
|
-
if (hashTarget) {
|
|
337
|
-
hashTarget.scrollIntoView();
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
scrollPosition = { x: 0, y: 0 };
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
assert('x' in scrollTarget && 'y' in scrollTarget);
|
|
345
|
-
scrollPosition = scrollTarget;
|
|
346
|
-
}
|
|
347
|
-
setScroll(scrollPosition);
|
|
348
|
-
}
|
|
349
|
-
/** Change the browser's scoll position, in a way that works during a repaint. */
|
|
350
|
-
function setScroll(scrollPosition) {
|
|
351
|
-
const scroll = () => window.scrollTo(scrollPosition.x, scrollPosition.y);
|
|
352
|
-
const done = () => window.scrollX === scrollPosition.x && window.scrollY === scrollPosition.y;
|
|
353
|
-
// In principle, this `done()` call should force the repaint to be finished. But that doesn't seem to be the case with `Firefox 97.0.1`.
|
|
354
|
-
if (done())
|
|
355
|
-
return;
|
|
356
|
-
scroll();
|
|
357
|
-
// Because `done()` doesn't seem to always force the repaint to be finished, we potentially need to retry again.
|
|
358
|
-
if (done())
|
|
359
|
-
return;
|
|
360
|
-
requestAnimationFrame(() => {
|
|
361
|
-
scroll();
|
|
362
|
-
if (done())
|
|
363
|
-
return;
|
|
364
|
-
setTimeout(async () => {
|
|
365
|
-
scroll();
|
|
366
|
-
if (done())
|
|
367
|
-
return;
|
|
368
|
-
// In principle, `requestAnimationFrame() -> setTimeout(, 0)` should be enough.
|
|
369
|
-
// - https://stackoverflow.com/questions/61281139/waiting-for-repaint-in-javascript
|
|
370
|
-
// - But it's not enough for `Firefox 97.0.1`.
|
|
371
|
-
// - The following strategy is very agressive. It doesn't need to be that aggressive for Firefox. But we do it to be safe.
|
|
372
|
-
const start = new Date().getTime();
|
|
373
|
-
while (true) {
|
|
374
|
-
await sleep(10);
|
|
375
|
-
scroll();
|
|
376
|
-
if (done())
|
|
377
|
-
return;
|
|
378
|
-
const millisecondsElapsed = new Date().getTime() - start;
|
|
379
|
-
if (millisecondsElapsed > 100)
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
}, 0);
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
// Save scroll position (needed for back-/forward navigation)
|
|
386
|
-
function autoSaveScrollPosition() {
|
|
387
|
-
// Safari cannot handle more than 100 `history.replaceState()` calls within 30 seconds (https://github.com/vikejs/vike/issues/46)
|
|
388
|
-
window.addEventListener('scroll', throttle(saveScrollPosition, Math.ceil(1000 / 3)), { passive: true });
|
|
389
|
-
onPageHide(saveScrollPosition);
|
|
390
|
-
}
|
|
391
|
-
function getUrlHash() {
|
|
392
|
-
let { hash } = window.location;
|
|
393
|
-
if (hash === '')
|
|
394
|
-
return null;
|
|
395
|
-
assert(hash.startsWith('#'));
|
|
396
|
-
hash = hash.slice(1);
|
|
397
|
-
return hash;
|
|
398
|
-
}
|
|
399
|
-
// We use the browser's native scroll restoration mechanism only for the first render
|
|
400
|
-
function setupNativeScrollRestoration() {
|
|
401
|
-
browserNativeScrollRestoration_enable();
|
|
402
|
-
onPageHide(browserNativeScrollRestoration_enable);
|
|
403
|
-
onPageShow(() => globalObject.initialRenderIsDone && browserNativeScrollRestoration_disable());
|
|
404
|
-
}
|
|
405
|
-
function browserNativeScrollRestoration_disable() {
|
|
406
|
-
if ('scrollRestoration' in window.history) {
|
|
407
|
-
window.history.scrollRestoration = 'manual';
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
function browserNativeScrollRestoration_enable() {
|
|
411
|
-
if ('scrollRestoration' in window.history) {
|
|
412
|
-
window.history.scrollRestoration = 'auto';
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
function onPageHide(listener) {
|
|
416
|
-
window.addEventListener('visibilitychange', () => {
|
|
417
|
-
if (document.visibilityState === 'hidden') {
|
|
418
|
-
listener();
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
function onPageShow(listener) {
|
|
423
|
-
window.addEventListener('visibilitychange', () => {
|
|
424
|
-
if (document.visibilityState === 'visible') {
|
|
425
|
-
listener();
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
function shouldSwallowAndInterrupt(err, pageContext) {
|
|
430
|
-
if (isAlreadyServerSideRouted(err))
|
|
431
|
-
return true;
|
|
432
|
-
if (handleErrorFetchingStaticAssets(err, pageContext))
|
|
433
|
-
return true;
|
|
434
|
-
return false;
|
|
435
|
-
}
|
|
436
|
-
function handleErrorFetchingStaticAssets(err, pageContext) {
|
|
437
|
-
if (!isErrorFetchingStaticAssets(err)) {
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
if (pageContext._isFirstRenderAttempt) {
|
|
441
|
-
disableClientRouting(err, false);
|
|
442
|
-
// This may happen if the frontend was newly deployed during hydration.
|
|
443
|
-
// Ideally: re-try a couple of times by reloading the page (not entirely trivial to implement since `localStorage` is needed.)
|
|
444
|
-
throw err;
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
disableClientRouting(err, true);
|
|
448
|
-
}
|
|
449
|
-
serverSideRouteTo(pageContext.urlOriginal);
|
|
450
|
-
return true;
|
|
451
|
-
}
|
|
452
|
-
function isDisableAutomaticLinkInterception() {
|
|
453
|
-
// @ts-ignore
|
|
454
|
-
return !!window._disableAutomaticLinkInterception;
|
|
455
|
-
/* globalObject should be used if we want to make disableAutomaticLinkInterception a page-by-page setting
|
|
456
|
-
return globalObject.disableAutomaticLinkInterception ?? false
|
|
457
|
-
*/
|
|
458
|
-
}
|
|
459
|
-
function disableClientRouting(err, log) {
|
|
460
|
-
assert(isErrorFetchingStaticAssets(err));
|
|
461
|
-
globalObject.clientRoutingIsDisabled = true;
|
|
462
|
-
if (log) {
|
|
463
|
-
// We don't use console.error() to avoid flooding error trackers such as Sentry
|
|
464
|
-
console.log(err);
|
|
465
|
-
}
|
|
466
|
-
// @ts-ignore Since dist/cjs/client/ is never used, we can ignore this error.
|
|
467
|
-
const isProd = import.meta.env.PROD;
|
|
468
|
-
assertInfo(false, [
|
|
469
|
-
'Failed to fetch static asset.',
|
|
470
|
-
isProd ? 'This usually happens when a new frontend is deployed.' : null,
|
|
471
|
-
'Falling back to Server Routing.',
|
|
472
|
-
'(The next page navigation will use Server Routing instead of Client Routing.)'
|
|
473
|
-
]
|
|
474
|
-
.filter(Boolean)
|
|
475
|
-
.join(' '), { onlyOnce: true });
|
|
476
|
-
}
|
|
477
|
-
function getRenderNumber() {
|
|
478
|
-
const renderNumber = ++globalObject.renderCounter;
|
|
479
|
-
assert(renderNumber >= 1);
|
|
480
|
-
let hydrationCanBeAborted = false;
|
|
481
|
-
const setHydrationCanBeAborted = () => {
|
|
482
|
-
hydrationCanBeAborted = true;
|
|
483
|
-
};
|
|
484
|
-
const shouldAbort = () => {
|
|
485
|
-
{
|
|
486
|
-
// We should never abort the hydration if `hydrationCanBeAborted` isn't `true`
|
|
487
|
-
const isHydration = renderNumber === 1;
|
|
488
|
-
if (isHydration && hydrationCanBeAborted === false) {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// If there is a newer rendering, we should abort all previous renderings
|
|
493
|
-
if (renderNumber !== globalObject.renderCounter) {
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
496
|
-
return false;
|
|
497
|
-
};
|
|
498
|
-
return { renderNumber, shouldAbort, setHydrationCanBeAborted };
|
|
499
22
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { isClientSideRoutable };
|
|
2
2
|
import type { PageFile } from '../../shared/getPageFiles.js';
|
|
3
3
|
import type { PageConfigRuntime } from '../../shared/page-configs/PageConfig.js';
|
|
4
|
-
|
|
5
|
-
_pageId: string | null;
|
|
4
|
+
type PageContextPageFiles = {
|
|
6
5
|
_pageFilesAll: PageFile[];
|
|
7
6
|
_pageConfigs: PageConfigRuntime[];
|
|
8
|
-
}
|
|
7
|
+
};
|
|
8
|
+
declare function isClientSideRoutable(pageId: string, pageContext: PageContextPageFiles): Promise<boolean>;
|
|
@@ -2,14 +2,11 @@ export { isClientSideRoutable };
|
|
|
2
2
|
import { analyzePageClientSideInit } from '../../shared/getPageFiles/analyzePageClientSide.js';
|
|
3
3
|
import { findPageConfig } from '../../shared/page-configs/findPageConfig.js';
|
|
4
4
|
import { analyzeClientSide } from '../../shared/getPageFiles/analyzeClientSide.js';
|
|
5
|
-
async function isClientSideRoutable(pageContext) {
|
|
6
|
-
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
await analyzePageClientSideInit(pageContext._pageFilesAll, pageContext._pageId, {
|
|
5
|
+
async function isClientSideRoutable(pageId, pageContext) {
|
|
6
|
+
await analyzePageClientSideInit(pageContext._pageFilesAll, pageId, {
|
|
10
7
|
sharedPageFilesAlreadyLoaded: false
|
|
11
8
|
});
|
|
12
|
-
const pageConfig = findPageConfig(pageContext._pageConfigs,
|
|
13
|
-
const { isClientSideRenderable, isClientRouting } = analyzeClientSide(pageConfig, pageContext._pageFilesAll,
|
|
9
|
+
const pageConfig = findPageConfig(pageContext._pageConfigs, pageId);
|
|
10
|
+
const { isClientSideRenderable, isClientRouting } = analyzeClientSide(pageConfig, pageContext._pageFilesAll, pageId);
|
|
14
11
|
return isClientSideRenderable && isClientRouting;
|
|
15
12
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { navigate };
|
|
2
2
|
export { reload };
|
|
3
|
-
import { renderPageClientSide } from './
|
|
3
|
+
import { renderPageClientSide } from './renderPageClientSide.js';
|
|
4
4
|
import { assertUsage, isBrowser, assertClientRouting, checkIfClientRouting, getCurrentUrl } from './utils.js';
|
|
5
5
|
assertClientRouting();
|
|
6
6
|
/** Programmatically navigate to a new page.
|
|
@@ -27,8 +27,7 @@ async function navigate(url, { keepScrollPosition = false, overwriteLastHistoryE
|
|
|
27
27
|
scrollTarget,
|
|
28
28
|
urlOriginal: url,
|
|
29
29
|
overwriteLastHistoryEntry,
|
|
30
|
-
isBackwardNavigation: false
|
|
31
|
-
checkIfClientSideRenderable: true
|
|
30
|
+
isBackwardNavigation: false
|
|
32
31
|
});
|
|
33
32
|
}
|
|
34
33
|
async function reload() {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export { onBrowserHistoryNavigation };
|
|
2
|
+
export { updateState };
|
|
3
|
+
import { getCurrentUrl, getGlobalObject } from './utils.js';
|
|
4
|
+
import { initHistoryState, getHistoryState } from './history.js';
|
|
5
|
+
import { renderPageClientSide } from './renderPageClientSide.js';
|
|
6
|
+
import { setScrollPosition } from './setScrollPosition.js';
|
|
7
|
+
const globalObject = getGlobalObject('onBrowserHistoryNavigation.ts', { previousState: getState() });
|
|
8
|
+
function onBrowserHistoryNavigation() {
|
|
9
|
+
// - The popstate event is trigged upon:
|
|
10
|
+
// - Back-/forward navigation.
|
|
11
|
+
// - By user clicking on his browser's back-/forward navigation (or using a shortcut)
|
|
12
|
+
// - By JavaScript: `history.back()` / `history.forward()`
|
|
13
|
+
// - URL hash change.
|
|
14
|
+
// - By user clicking on a hash link `<a href="#some-hash" />`
|
|
15
|
+
// - The popstate event is *only* triggered if `href` starts with '#' (even if `href` is '/#some-hash' while the current URL's pathname is '/' then the popstate still isn't triggered)
|
|
16
|
+
// - By JavaScript: `location.hash = 'some-hash'`
|
|
17
|
+
// - The `event` of `window.addEventListener('popstate', (event) => /*...*/)` is useless: the History API doesn't provide the previous state (the popped state), see https://stackoverflow.com/questions/48055323/is-history-state-always-the-same-as-popstate-event-state
|
|
18
|
+
window.addEventListener('popstate', () => {
|
|
19
|
+
const currentState = getState();
|
|
20
|
+
const scrollTarget = currentState.historyState.scrollPosition || 'scroll-to-top-or-hash';
|
|
21
|
+
const isUserLandPushStateNavigation = currentState.historyState.triggedBy === 'user';
|
|
22
|
+
const isHashNavigation = currentState.urlWithoutHash === globalObject.previousState.urlWithoutHash;
|
|
23
|
+
const isBackwardNavigation = !currentState.historyState.timestamp || !globalObject.previousState.historyState.timestamp
|
|
24
|
+
? null
|
|
25
|
+
: currentState.historyState.timestamp < globalObject.previousState.historyState.timestamp;
|
|
26
|
+
globalObject.previousState = currentState;
|
|
27
|
+
if (isHashNavigation && !isUserLandPushStateNavigation) {
|
|
28
|
+
// - `history.state` is uninitialized (`null`) when:
|
|
29
|
+
// - The user's code runs `window.location.hash = '#section'`.
|
|
30
|
+
// - The user clicks on an anchor link `<a href="#section">Section</a>` (because Vike's `onLinkClick()` handler skips hash links).
|
|
31
|
+
// - `history.state` is `null` when uninitialized: https://developer.mozilla.org/en-US/docs/Web/API/History/state
|
|
32
|
+
// - Alternatively, we completely take over hash navigation and reproduce the browser's native behavior upon hash navigation.
|
|
33
|
+
// - Problem: we cannot intercept `window.location.hash = '#section'`. (Or maybe we can with the `hashchange` event?)
|
|
34
|
+
// - Other potential problem: would there be a conflict when the user wants to override the browser's default behavior? E.g. for smooth scrolling, or when using hashes for saving states of some fancy animations.
|
|
35
|
+
// - Another alternative: we use the browser's scroll restoration mechanism (see `browserNativeScrollRestoration_enable()` below).
|
|
36
|
+
// - Problem: not clear when to call `browserNativeScrollRestoration_disable()`/`browserNativeScrollRestoration_enable()`
|
|
37
|
+
// - Other potential problem are inconsistencies between browsers: specification says that setting `window.history.scrollRestoration` only affects the current entry in the session history. But this seems to contradict what folks saying.
|
|
38
|
+
// - Specification: https://html.spec.whatwg.org/multipage/history.html#the-history-interface
|
|
39
|
+
// - https://stackoverflow.com/questions/70188241/history-scrollrestoration-manual-doesnt-prevent-safari-from-restoring-scrol
|
|
40
|
+
if (window.history.state === null) {
|
|
41
|
+
// The browser already scrolled to `#${hash}` => the current scroll position is the right one => we save it with `initHistoryState()`.
|
|
42
|
+
initHistoryState();
|
|
43
|
+
globalObject.previousState = getState();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// If `history.state !== null` then it means that `popstate` was triggered by the user clicking on his browser's forward/backward history button.
|
|
47
|
+
setScrollPosition(scrollTarget);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
renderPageClientSide({ scrollTarget, isBackwardNavigation, isUserLandPushStateNavigation });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function getState() {
|
|
56
|
+
return {
|
|
57
|
+
urlWithoutHash: getCurrentUrl({ withoutHash: true }),
|
|
58
|
+
historyState: getHistoryState()
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function updateState() {
|
|
62
|
+
globalObject.previousState = getState();
|
|
63
|
+
}
|