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.
Files changed (74) hide show
  1. package/dist/cjs/node/plugin/plugins/buildConfig.js +1 -1
  2. package/dist/cjs/node/plugin/plugins/config/index.js +3 -3
  3. package/dist/cjs/node/plugin/plugins/devConfig/determineOptimizeDeps.js +1 -1
  4. package/dist/cjs/node/plugin/plugins/importUserCode/getVirtualFileImportUserCode.js +1 -1
  5. package/dist/cjs/node/plugin/plugins/importUserCode/index.js +1 -1
  6. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +1 -1
  7. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +5 -2
  8. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +4 -2
  9. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +18 -6
  10. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/transpileAndExecuteFile.js +2 -2
  11. package/dist/cjs/node/prerender/runPrerender.js +17 -8
  12. package/dist/cjs/node/runtime/renderPage/log404/index.js +2 -1
  13. package/dist/cjs/node/shared/getConfigVike.js +4 -1
  14. package/dist/cjs/shared/page-configs/serialize/parseConfigValuesImported.js +8 -5
  15. package/dist/cjs/shared/route/noRouteMatch.js +4 -0
  16. package/dist/cjs/utils/isExternalLink.js +7 -0
  17. package/dist/cjs/utils/onPageVisibilityChange.js +19 -0
  18. package/dist/cjs/utils/projectInfo.js +1 -1
  19. package/dist/esm/client/client-routing-runtime/createPageContext.js +0 -1
  20. package/dist/esm/client/client-routing-runtime/getBaseServer.d.ts +2 -1
  21. package/dist/esm/client/client-routing-runtime/getBaseServer.js +2 -1
  22. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.d.ts +39 -0
  23. package/dist/esm/client/client-routing-runtime/{getPageContext.js → getPageContextFromHooks.js} +48 -74
  24. package/dist/esm/client/client-routing-runtime/history.js +9 -5
  25. package/dist/esm/client/client-routing-runtime/installClientRouter.d.ts +0 -19
  26. package/dist/esm/client/client-routing-runtime/installClientRouter.js +11 -488
  27. package/dist/esm/client/client-routing-runtime/isClientSideRoutable.d.ts +3 -3
  28. package/dist/esm/client/client-routing-runtime/isClientSideRoutable.js +4 -7
  29. package/dist/esm/client/client-routing-runtime/navigate.js +2 -3
  30. package/dist/esm/client/client-routing-runtime/onBrowserHistoryNavigation.d.ts +4 -0
  31. package/dist/esm/client/client-routing-runtime/onBrowserHistoryNavigation.js +63 -0
  32. package/dist/esm/client/client-routing-runtime/onLinkClick.d.ts +2 -0
  33. package/dist/esm/client/client-routing-runtime/onLinkClick.js +40 -0
  34. package/dist/esm/client/client-routing-runtime/prefetch.js +7 -8
  35. package/dist/esm/client/client-routing-runtime/renderPageClientSide.d.ts +19 -0
  36. package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +347 -0
  37. package/dist/esm/client/client-routing-runtime/scrollRestoration.d.ts +6 -0
  38. package/dist/esm/client/client-routing-runtime/scrollRestoration.js +25 -0
  39. package/dist/esm/client/client-routing-runtime/setScrollPosition.d.ts +7 -0
  40. package/dist/esm/client/client-routing-runtime/setScrollPosition.js +77 -0
  41. package/dist/esm/client/client-routing-runtime/skipLink.js +9 -4
  42. package/dist/esm/client/client-routing-runtime/utils.d.ts +2 -0
  43. package/dist/esm/client/client-routing-runtime/utils.js +2 -0
  44. package/dist/esm/node/plugin/plugins/buildConfig.js +2 -2
  45. package/dist/esm/node/plugin/plugins/config/index.js +4 -4
  46. package/dist/esm/node/plugin/plugins/devConfig/determineOptimizeDeps.js +2 -2
  47. package/dist/esm/node/plugin/plugins/importUserCode/getVirtualFileImportUserCode.js +1 -1
  48. package/dist/esm/node/plugin/plugins/importUserCode/index.js +1 -1
  49. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +1 -1
  50. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.d.ts +2 -1
  51. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +6 -3
  52. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.d.ts +2 -2
  53. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +4 -2
  54. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.d.ts +2 -2
  55. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +18 -6
  56. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/transpileAndExecuteFile.js +3 -3
  57. package/dist/esm/node/prerender/runPrerender.js +17 -8
  58. package/dist/esm/node/runtime/renderPage/log404/index.js +2 -1
  59. package/dist/esm/node/shared/getConfigVike.d.ts +2 -1
  60. package/dist/esm/node/shared/getConfigVike.js +4 -1
  61. package/dist/esm/shared/page-configs/serialize/parseConfigValuesImported.js +9 -3
  62. package/dist/esm/shared/route/noRouteMatch.d.ts +1 -0
  63. package/dist/esm/shared/route/noRouteMatch.js +1 -0
  64. package/dist/esm/utils/onPageVisibilityChange.d.ts +4 -0
  65. package/dist/esm/utils/onPageVisibilityChange.js +16 -0
  66. package/dist/esm/utils/projectInfo.d.ts +1 -1
  67. package/dist/esm/utils/projectInfo.js +1 -1
  68. package/node/cli/bin-entry.js +1 -1
  69. package/package.json +1 -1
  70. package/dist/esm/client/client-routing-runtime/getPageContext.d.ts +0 -28
  71. package/dist/esm/client/client-routing-runtime/navigationState.d.ts +0 -5
  72. package/dist/esm/client/client-routing-runtime/navigationState.js +0 -14
  73. /package/dist/esm/{client/client-routing-runtime → utils}/isExternalLink.d.ts +0 -0
  74. /package/dist/esm/{client/client-routing-runtime → utils}/isExternalLink.js +0 -0
@@ -1,499 +1,22 @@
1
1
  export { installClientRouter };
2
- export { disableClientRouting };
3
- export { isDisableAutomaticLinkInterception };
4
- export { renderPageClientSide };
5
- import { assert, getCurrentUrl, isEquivalentError, objectAssign, serverSideRouteTo, throttle, sleep, getGlobalObject, executeHook } from './utils.js';
6
- import { navigationState } from './navigationState.js';
7
- import { checkIf404, getPageContext, getPageContextErrorPage, isAlreadyServerSideRouted } from './getPageContext.js';
8
- import { createPageContext } from './createPageContext.js';
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
- declare function isClientSideRoutable(pageContext: {
5
- _pageId: string | null;
4
+ type PageContextPageFiles = {
6
5
  _pageFilesAll: PageFile[];
7
6
  _pageConfigs: PageConfigRuntime[];
8
- }): Promise<boolean>;
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
- if (!pageContext._pageId) {
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, pageContext._pageId);
13
- const { isClientSideRenderable, isClientRouting } = analyzeClientSide(pageConfig, pageContext._pageFilesAll, pageContext._pageId);
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 './installClientRouter.js';
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,4 @@
1
+ export { onBrowserHistoryNavigation };
2
+ export { updateState };
3
+ declare function onBrowserHistoryNavigation(): void;
4
+ declare function updateState(): void;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { onLinkClick };
2
+ declare function onLinkClick(): void;