vike 0.4.144-commit-6aef8a6 → 0.4.144-commit-7f5e99a

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 (112) hide show
  1. package/dist/cjs/__internal/index.js +6 -2
  2. package/dist/cjs/node/plugin/plugins/buildConfig.js +2 -2
  3. package/dist/cjs/node/plugin/plugins/commonConfig.js +0 -3
  4. package/dist/cjs/node/plugin/plugins/devConfig/determineOptimizeDeps.js +5 -5
  5. package/dist/cjs/node/plugin/plugins/devConfig/index.js +1 -0
  6. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +4 -3
  7. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +29 -42
  8. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +2 -2
  9. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +14 -5
  10. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/helpers.js +1 -14
  11. package/dist/cjs/node/plugin/plugins/previewConfig.js +11 -2
  12. package/dist/cjs/node/prerender/runPrerender.js +16 -17
  13. package/dist/cjs/node/prerender/utils.js +1 -1
  14. package/dist/cjs/node/runtime/html/serializePageContextClientSide.js +20 -6
  15. package/dist/cjs/node/runtime/renderPage/renderPageAlreadyRouted.js +2 -2
  16. package/dist/cjs/node/runtime/renderPage.js +2 -2
  17. package/dist/cjs/node/runtime/utils.js +1 -1
  18. package/dist/cjs/node/shared/getClientEntryFilePath.js +2 -2
  19. package/dist/cjs/shared/addUrlComputedProps.js +24 -12
  20. package/dist/cjs/shared/getPageFiles/analyzeClientSide.js +4 -6
  21. package/dist/cjs/shared/getPageFiles/getExports.js +3 -3
  22. package/dist/cjs/shared/hooks/getHook.js +1 -1
  23. package/dist/cjs/shared/page-configs/helpers/getConfigDefinedAtString.js +43 -0
  24. package/dist/cjs/shared/page-configs/helpers/getConfigValue.js +44 -0
  25. package/dist/cjs/shared/page-configs/helpers.js +33 -0
  26. package/dist/cjs/shared/page-configs/serialize/parseConfigValuesImported.js +6 -8
  27. package/dist/cjs/shared/page-configs/serialize/parsePageConfigs.js +2 -2
  28. package/dist/cjs/shared/page-configs/serialize/serializeConfigValue.js +2 -2
  29. package/dist/cjs/shared/route/executeOnBeforeRouteHook.js +11 -13
  30. package/dist/cjs/shared/route/index.js +3 -3
  31. package/dist/cjs/shared/route/loadPageRoutes.js +11 -10
  32. package/dist/cjs/shared/route/resolveRouteFunction.js +1 -1
  33. package/dist/cjs/shared/utils.js +1 -1
  34. package/dist/cjs/utils/{hasPropertyGetter.js → isPropertyGetter.js} +3 -3
  35. package/dist/cjs/utils/projectInfo.js +1 -1
  36. package/dist/esm/__internal/index.d.ts +6 -3
  37. package/dist/esm/__internal/index.js +8 -3
  38. package/dist/esm/client/client-routing-runtime/createPageContext.d.ts +2 -3
  39. package/dist/esm/client/client-routing-runtime/createPageContext.js +3 -3
  40. package/dist/esm/client/client-routing-runtime/entry.js +2 -2
  41. package/dist/esm/client/client-routing-runtime/getPageContext.d.ts +0 -1
  42. package/dist/esm/client/client-routing-runtime/getPageContext.js +4 -7
  43. package/dist/esm/client/client-routing-runtime/getPageId.d.ts +1 -1
  44. package/dist/esm/client/client-routing-runtime/getPageId.js +4 -7
  45. package/dist/esm/client/client-routing-runtime/history.d.ts +3 -1
  46. package/dist/esm/client/client-routing-runtime/history.js +26 -8
  47. package/dist/esm/client/client-routing-runtime/installClientRouter.d.ts +21 -0
  48. package/dist/esm/client/client-routing-runtime/{useClientRouter.js → installClientRouter.js} +248 -242
  49. package/dist/esm/client/client-routing-runtime/isClientSideRoutable.d.ts +8 -0
  50. package/dist/esm/client/client-routing-runtime/isClientSideRoutable.js +15 -0
  51. package/dist/esm/client/client-routing-runtime/navigate.d.ts +0 -2
  52. package/dist/esm/client/client-routing-runtime/navigate.js +10 -8
  53. package/dist/esm/client/client-routing-runtime/prefetch.js +12 -5
  54. package/dist/esm/client/client-routing-runtime/skipLink.d.ts +0 -1
  55. package/dist/esm/client/client-routing-runtime/skipLink.js +1 -2
  56. package/dist/esm/client/shared/executeOnRenderClientHook.js +6 -5
  57. package/dist/esm/client/shared/getPageContextProxyForUser.js +13 -7
  58. package/dist/esm/node/plugin/plugins/buildConfig.js +1 -1
  59. package/dist/esm/node/plugin/plugins/commonConfig.js +0 -3
  60. package/dist/esm/node/plugin/plugins/devConfig/determineOptimizeDeps.js +5 -5
  61. package/dist/esm/node/plugin/plugins/devConfig/index.js +1 -0
  62. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.d.ts +2 -2
  63. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +4 -3
  64. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig.js +29 -42
  65. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigValuesAll.js +1 -1
  66. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +15 -6
  67. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/helpers.js +1 -14
  68. package/dist/esm/node/plugin/plugins/previewConfig.js +11 -2
  69. package/dist/esm/node/prerender/runPrerender.js +11 -12
  70. package/dist/esm/node/prerender/utils.d.ts +1 -1
  71. package/dist/esm/node/prerender/utils.js +1 -1
  72. package/dist/esm/node/runtime/html/serializePageContextClientSide.js +21 -7
  73. package/dist/esm/node/runtime/renderPage/renderPageAlreadyRouted.js +1 -1
  74. package/dist/esm/node/runtime/renderPage.js +2 -2
  75. package/dist/esm/node/runtime/utils.d.ts +1 -1
  76. package/dist/esm/node/runtime/utils.js +1 -1
  77. package/dist/esm/node/shared/getClientEntryFilePath.js +1 -1
  78. package/dist/esm/shared/addUrlComputedProps.d.ts +1 -0
  79. package/dist/esm/shared/addUrlComputedProps.js +25 -13
  80. package/dist/esm/shared/getPageFiles/analyzeClientSide.js +2 -4
  81. package/dist/esm/shared/getPageFiles/getExports.js +2 -2
  82. package/dist/esm/shared/hooks/getHook.js +1 -1
  83. package/dist/esm/shared/page-configs/PageConfig.d.ts +4 -12
  84. package/dist/esm/shared/page-configs/helpers/getConfigDefinedAtString.d.ts +7 -0
  85. package/dist/esm/shared/page-configs/helpers/getConfigDefinedAtString.js +37 -0
  86. package/dist/esm/shared/page-configs/helpers/getConfigValue.d.ts +14 -0
  87. package/dist/esm/shared/page-configs/helpers/getConfigValue.js +38 -0
  88. package/dist/esm/shared/page-configs/helpers.d.ts +13 -0
  89. package/dist/esm/shared/page-configs/helpers.js +27 -0
  90. package/dist/esm/shared/page-configs/serialize/parseConfigValuesImported.js +6 -8
  91. package/dist/esm/shared/page-configs/serialize/parsePageConfigs.js +2 -2
  92. package/dist/esm/shared/page-configs/serialize/serializeConfigValue.js +2 -2
  93. package/dist/esm/shared/route/executeOnBeforeRouteHook.d.ts +1 -1
  94. package/dist/esm/shared/route/executeOnBeforeRouteHook.js +11 -13
  95. package/dist/esm/shared/route/index.d.ts +11 -9
  96. package/dist/esm/shared/route/index.js +3 -3
  97. package/dist/esm/shared/route/loadPageRoutes.js +7 -6
  98. package/dist/esm/shared/route/resolveRouteFunction.js +1 -1
  99. package/dist/esm/shared/utils.d.ts +1 -1
  100. package/dist/esm/shared/utils.js +1 -1
  101. package/dist/esm/utils/isPropertyGetter.d.ts +1 -0
  102. package/dist/esm/utils/{hasPropertyGetter.js → isPropertyGetter.js} +1 -1
  103. package/dist/esm/utils/projectInfo.d.ts +1 -1
  104. package/dist/esm/utils/projectInfo.js +1 -1
  105. package/package.json +2 -2
  106. package/dist/cjs/shared/page-configs/utils.js +0 -96
  107. package/dist/esm/client/client-routing-runtime/skipLink/isClientSideRoutable.d.ts +0 -2
  108. package/dist/esm/client/client-routing-runtime/skipLink/isClientSideRoutable.js +0 -15
  109. package/dist/esm/client/client-routing-runtime/useClientRouter.d.ts +0 -6
  110. package/dist/esm/shared/page-configs/utils.d.ts +0 -35
  111. package/dist/esm/shared/page-configs/utils.js +0 -90
  112. package/dist/esm/utils/hasPropertyGetter.d.ts +0 -1
@@ -1,6 +1,7 @@
1
- export { useClientRouter };
1
+ export { installClientRouter };
2
2
  export { disableClientRouting };
3
3
  export { isDisableAutomaticLinkInterception };
4
+ export { renderPageClientSide };
4
5
  import { assert, getCurrentUrl, isEquivalentError, objectAssign, serverSideRouteTo, throttle, sleep, getGlobalObject, executeHook } from './utils.js';
5
6
  import { navigationState } from './navigationState.js';
6
7
  import { checkIf404, getPageContext, getPageContextErrorPage, isAlreadyServerSideRouted } from './getPageContext.js';
@@ -9,130 +10,81 @@ import { addLinkPrefetchHandlers } from './prefetch.js';
9
10
  import { assertInfo, assertWarning, isReact } from './utils.js';
10
11
  import { executeOnRenderClientHook } from '../shared/executeOnRenderClientHook.js';
11
12
  import { assertHook } from '../../shared/hooks/getHook.js';
12
- import { isClientSideRoutable, skipLink } from './skipLink.js';
13
+ import { skipLink } from './skipLink.js';
13
14
  import { isErrorFetchingStaticAssets } from '../shared/loadPageFilesClientSide.js';
14
- import { initHistoryState, getHistoryState, pushHistory, saveScrollPosition } from './history.js';
15
- import { defineNavigate } from './navigate.js';
15
+ import { initHistoryState, getHistoryState, pushHistory, saveScrollPosition, monkeyPatchHistoryPushState } from './history.js';
16
16
  import { assertNoInfiniteAbortLoop, getPageContextFromAllRewrites, isAbortError, logAbortErrorHandled } from '../../shared/route/abort.js';
17
- const globalObject = getGlobalObject('useClientRouter.ts', { previousState: getState() });
18
- setupNativeScrollRestoration();
19
- initHistoryState();
20
- function disableClientRouting(err, log) {
21
- assert(isErrorFetchingStaticAssets(err));
22
- globalObject.clientRoutingIsDisabled = true;
23
- if (log) {
24
- // We don't use console.error() to avoid flooding error trackers such as Sentry
25
- console.log(err);
26
- }
27
- // @ts-ignore Since dist/cjs/client/ is never used, we can ignore this error.
28
- const isProd = import.meta.env.PROD;
29
- assertInfo(false, [
30
- 'Failed to fetch static asset.',
31
- isProd ? 'This usually happens when a new frontend is deployed.' : null,
32
- 'Falling back to Server Routing.',
33
- '(The next page navigation will use Server Routing instead of Client Routing.)'
34
- ]
35
- .filter(Boolean)
36
- .join(' '), { onlyOnce: true });
37
- }
38
- function useClientRouter() {
17
+ import { route } from '../../shared/route/index.js';
18
+ import { isClientSideRoutable } from './isClientSideRoutable.js';
19
+ const globalObject = getGlobalObject('installClientRouter.ts', { previousState: getState(), renderCounter: 0 });
20
+ function installClientRouter() {
21
+ setupNativeScrollRestoration();
22
+ initHistoryState();
39
23
  autoSaveScrollPosition();
40
- onLinkClick((url, { keepScrollPosition }) => {
41
- const scrollTarget = keepScrollPosition ? 'preserve-scroll' : 'scroll-to-top-or-hash';
42
- fetchAndRender({
43
- scrollTarget,
44
- urlOriginal: url,
45
- isBackwardNavigation: false,
46
- checkClientSideRenderable: true
47
- });
48
- });
49
- onBrowserHistoryNavigation((scrollTarget, isBackwardNavigation) => {
50
- fetchAndRender({ scrollTarget, isBackwardNavigation });
51
- });
52
- defineNavigate(async (url, { keepScrollPosition = false, overwriteLastHistoryEntry = false } = {}) => {
53
- const scrollTarget = keepScrollPosition ? 'preserve-scroll' : 'scroll-to-top-or-hash';
54
- await fetchAndRender({
55
- scrollTarget,
56
- urlOriginal: url,
57
- overwriteLastHistoryEntry,
58
- isBackwardNavigation: false,
59
- checkClientSideRenderable: true
60
- });
24
+ monkeyPatchHistoryPushState();
25
+ // Intercept <a> links
26
+ onLinkClick();
27
+ // Handle back-/forward navigation
28
+ 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
61
42
  });
62
- let renderingCounter = 0;
63
- let renderPromise;
64
- let isTransitioning = false;
65
- fetchAndRender({ scrollTarget: 'preserve-scroll', isBackwardNavigation: null });
66
- return;
67
- async function fetchAndRender({ scrollTarget, urlOriginal = getCurrentUrl(), overwriteLastHistoryEntry = false, isBackwardNavigation, checkClientSideRenderable, pageContextsFromRewrite = [], redirectCount = 0 }) {
68
- assertNoInfiniteAbortLoop(pageContextsFromRewrite.length, redirectCount);
69
- if (globalObject.clientRoutingIsDisabled) {
70
- serverSideRouteTo(urlOriginal);
71
- return;
72
- }
43
+ {
73
44
  const pageContextFromAllRewrites = getPageContextFromAllRewrites(pageContextsFromRewrite);
74
- if (checkClientSideRenderable) {
75
- const urlLogical = pageContextFromAllRewrites._urlRewrite ?? urlOriginal;
76
- let isClientRoutable;
77
- try {
78
- isClientRoutable = await isClientSideRoutable(urlLogical);
79
- }
80
- catch (err) {
81
- if (!isAbortError(err)) {
82
- // If a route() hook has a bug
83
- throw err;
84
- }
85
- else {
86
- // If the user's route() hook throw redirect() / throw render()
87
- // We handle the abort error down below: the user's route() hook is called again in getPageContext()
88
- isClientRoutable = true;
89
- }
90
- }
91
- if (!isClientRoutable) {
92
- serverSideRouteTo(urlOriginal);
93
- return;
94
- }
45
+ objectAssign(pageContext, pageContextFromAllRewrites);
46
+ }
47
+ let hasError = false;
48
+ let err;
49
+ {
50
+ let pageContextFromRoute;
51
+ try {
52
+ pageContextFromRoute = await route(pageContext);
95
53
  }
96
- const pageContextBase = {
97
- urlOriginal,
98
- isBackwardNavigation,
99
- ...pageContextFromAllRewrites
100
- };
101
- const renderingNumber = ++renderingCounter;
102
- assert(renderingNumber >= 1);
103
- // Start transition before any await's
104
- if (renderingNumber > 1) {
105
- if (isTransitioning === false) {
106
- await globalObject.onPageTransitionStart?.(pageContextBase);
107
- isTransitioning = true;
108
- }
54
+ catch (err_) {
55
+ hasError = true;
56
+ err = err_;
109
57
  }
110
- let hydrationCanBeAborted = false;
111
- const shouldAbort = () => {
112
- {
113
- // We should never abort the hydration if `hydrationCanBeAborted` isn't `true`
114
- const isHydration = renderingNumber === 1;
115
- if (isHydration && hydrationCanBeAborted === false) {
116
- return false;
58
+ if (pageContextFromRoute) {
59
+ objectAssign(pageContext, pageContextFromRoute);
60
+ if (checkIfClientSideRenderable) {
61
+ const isClientRoutable = await isClientSideRoutable(pageContext);
62
+ if (!isClientRoutable) {
63
+ serverSideRouteTo(urlOriginal);
64
+ return;
117
65
  }
118
66
  }
119
- // If there is a newer rendering, we should abort all previous renderings
120
- if (renderingNumber !== renderingCounter) {
121
- return true;
67
+ if (isUserLandNavigation && pageContextFromRoute._pageId === globalObject.previousPageContext?._pageId) {
68
+ // Skip's Vike's rendering; let the user handle the navigation
69
+ return;
122
70
  }
123
- return false;
124
- };
125
- const pageContext = await createPageContext(pageContextBase);
126
- if (shouldAbort()) {
127
- return;
128
71
  }
129
- const isFirstRenderAttempt = renderingNumber === 1;
130
- objectAssign(pageContext, {
131
- _isFirstRenderAttempt: isFirstRenderAttempt
132
- });
133
- let pageContextAddendum;
134
- let err;
135
- let hasError = false;
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) {
136
88
  try {
137
89
  pageContextAddendum = await getPageContext(pageContext);
138
90
  }
@@ -140,141 +92,140 @@ function useClientRouter() {
140
92
  hasError = true;
141
93
  err = err_;
142
94
  }
143
- if (hasError) {
144
- if (!isAbortError(err)) {
145
- // We don't swallow 404 errors:
146
- // - 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.)
147
- // - 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.)
148
- console.error(err);
149
- }
150
- else {
151
- // We swallow throw redirect()/render() called by client-side hooks onBeforeRender() and guard()
152
- // We handle the abort error down below.
153
- }
154
- if (shouldSwallowAndInterrupt(err, pageContext))
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
+ });
155
121
  return;
156
- if (isAbortError(err)) {
157
- const errAbort = err;
158
- logAbortErrorHandled(err, pageContext._isProduction, pageContext);
159
- const pageContextAbort = errAbort._pageContextAbort;
160
- // throw render('/some-url')
161
- if (pageContextAbort._urlRewrite) {
162
- await fetchAndRender({
163
- scrollTarget,
164
- urlOriginal,
165
- overwriteLastHistoryEntry,
166
- isBackwardNavigation,
167
- pageContextsFromRewrite: [...pageContextsFromRewrite, pageContextAbort],
168
- redirectCount
169
- });
170
- return;
171
- }
172
- // throw redirect('/some-url')
173
- if (pageContextAbort._urlRedirect) {
174
- const urlRedirect = pageContextAbort._urlRedirect.url;
175
- if (urlRedirect.startsWith('http')) {
176
- // External redirection
177
- window.location.href = urlRedirect;
178
- return;
179
- }
180
- else {
181
- await fetchAndRender({
182
- scrollTarget: 'scroll-to-top-or-hash',
183
- urlOriginal: urlRedirect,
184
- overwriteLastHistoryEntry: false,
185
- isBackwardNavigation: false,
186
- checkClientSideRenderable: true,
187
- pageContextsFromRewrite,
188
- redirectCount: redirectCount++
189
- });
190
- }
191
- return;
192
- }
193
- // throw render(statusCode)
194
- assert(pageContextAbort.abortStatusCode);
195
- objectAssign(pageContext, pageContextAbort);
196
- if (pageContextAbort.abortStatusCode === 404) {
197
- objectAssign(pageContext, { is404: true });
198
- }
199
122
  }
200
- else {
201
- objectAssign(pageContext, { is404: checkIf404(err) });
202
- }
203
- try {
204
- pageContextAddendum = await getPageContextErrorPage(pageContext);
205
- }
206
- catch (err2) {
207
- // - When user hasn't defined a `_error.page.js` file
208
- // - Some unpexected vike internal error
209
- if (shouldSwallowAndInterrupt(err2, pageContext))
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;
210
129
  return;
211
- if (!isFirstRenderAttempt) {
212
- setTimeout(() => {
213
- // We let the server show the 404 page
214
- window.location.pathname = urlOriginal;
215
- }, 0);
216
- }
217
- if (!isEquivalentError(err, err2)) {
218
- throw err2;
219
130
  }
220
131
  else {
221
- // Abort
222
- return;
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
+ });
223
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 });
224
149
  }
225
- }
226
- assert(pageContextAddendum);
227
- objectAssign(pageContext, pageContextAddendum);
228
- assertHook(pageContext, 'onPageTransitionStart');
229
- globalObject.onPageTransitionStart = pageContext.exports.onPageTransitionStart;
230
- if (pageContext.exports.hydrationCanBeAborted) {
231
- hydrationCanBeAborted = true;
232
150
  }
233
151
  else {
234
- assertWarning(!isReact(), 'You seem to be using React; we recommend setting hydrationCanBeAborted to true, see https://vike.dev/clientRouting', { onlyOnce: true });
152
+ objectAssign(pageContext, { is404: checkIf404(err) });
235
153
  }
236
- if (shouldAbort()) {
237
- return;
238
- }
239
- if (renderPromise) {
240
- // Always make sure that the previous render has finished,
241
- // otherwise that previous render may finish after this one.
242
- await renderPromise;
243
- }
244
- if (shouldAbort()) {
245
- return;
154
+ try {
155
+ pageContextAddendum = await getPageContextErrorPage(pageContext);
246
156
  }
247
- changeUrl(urlOriginal, overwriteLastHistoryEntry);
248
- navigationState.markNavigationChange();
249
- assert(renderPromise === undefined);
250
- renderPromise = (async () => {
251
- await executeOnRenderClientHook(pageContext, true);
252
- addLinkPrefetchHandlers(pageContext);
253
- })();
254
- await renderPromise;
255
- renderPromise = undefined;
256
- if (pageContext._isFirstRenderAttempt) {
257
- assertHook(pageContext, 'onHydrationEnd');
258
- const { onHydrationEnd } = pageContext.exports;
259
- if (onHydrationEnd) {
260
- const hookFilePath = pageContext.exportsAll.onHydrationEnd[0].exportSource;
261
- assert(hookFilePath);
262
- await executeHook(() => onHydrationEnd(pageContext), 'onHydrationEnd', hookFilePath);
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);
263
167
  }
264
- }
265
- else if (renderingNumber === renderingCounter) {
266
- if (pageContext.exports.onPageTransitionEnd) {
267
- assertHook(pageContext, 'onPageTransitionEnd');
268
- await pageContext.exports.onPageTransitionEnd(pageContext);
168
+ if (!isEquivalentError(err, err2)) {
169
+ throw err2;
269
170
  }
270
- isTransitioning = false;
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);
271
215
  }
272
- setScrollPosition(scrollTarget);
273
- browserNativeScrollRestoration_disable();
274
- globalObject.initialRenderIsDone = true;
275
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;
276
227
  }
277
- function onLinkClick(callback) {
228
+ function onLinkClick() {
278
229
  document.addEventListener('click', onClick);
279
230
  return;
280
231
  // Code adapted from https://github.com/HenrikJoreteg/internal-nav-helper/blob/5199ec5448d0b0db7ec63cf76d88fa6cad878b7d/src/index.js#L11-L29
@@ -290,7 +241,13 @@ function onLinkClick(callback) {
290
241
  assert(url);
291
242
  ev.preventDefault();
292
243
  const keepScrollPosition = ![null, 'false'].includes(linkTag.getAttribute('keep-scroll-position'));
293
- callback(url, { keepScrollPosition });
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
+ });
294
251
  }
295
252
  function isNormalLeftClick(ev) {
296
253
  return ev.button === 0 && !ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey;
@@ -306,21 +263,29 @@ function onLinkClick(callback) {
306
263
  return target;
307
264
  }
308
265
  }
309
- function onBrowserHistoryNavigation(callback) {
310
- // The `event` of `window.addEventListener('popstate', (event) => /*...*/)` is useless:
311
- // - The History API doesn't provide the previous state (the popped state): https://stackoverflow.com/questions/48055323/is-history-state-always-the-same-as-popstate-event-state
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
312
276
  window.addEventListener('popstate', () => {
313
277
  const currentState = getState();
314
278
  const scrollTarget = currentState.historyState.scrollPosition || 'scroll-to-top-or-hash';
279
+ const isUserLandNavigation = currentState.historyState.triggedBy === 'user';
315
280
  const isHashNavigation = currentState.urlWithoutHash === globalObject.previousState.urlWithoutHash;
316
281
  const isBackwardNavigation = !currentState.historyState.timestamp || !globalObject.previousState.historyState.timestamp
317
282
  ? null
318
283
  : currentState.historyState.timestamp < globalObject.previousState.historyState.timestamp;
319
284
  globalObject.previousState = currentState;
320
- if (isHashNavigation) {
285
+ if (isHashNavigation && !isUserLandNavigation) {
321
286
  // - `history.state` is uninitialized (`null`) when:
322
- // - The vike app runs `window.location.hash = '#section'`.
323
- // - The user clicks on an anchor link `<a href="#section">Section</a>`. (Because Vike's `onLinkClick()` handler skips hash links.)
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).
324
289
  // - `history.state` is `null` when uninitialized: https://developer.mozilla.org/en-US/docs/Web/API/History/state
325
290
  // - Alternatively, we completely take over hash navigation and reproduce the browser's native behavior upon hash navigation.
326
291
  // - Problem: we cannot intercept `window.location.hash = '#section'`. (Or maybe we can with the `hashchange` event?)
@@ -341,8 +306,7 @@ function onBrowserHistoryNavigation(callback) {
341
306
  }
342
307
  }
343
308
  else {
344
- // Fetch & render new page
345
- callback(scrollTarget, isBackwardNavigation);
309
+ renderPageClientSide({ scrollTarget, isBackwardNavigation, isUserLandNavigation });
346
310
  }
347
311
  });
348
312
  }
@@ -418,6 +382,7 @@ function setScroll(scrollPosition) {
418
382
  }, 0);
419
383
  });
420
384
  }
385
+ // Save scroll position (needed for back-/forward navigation)
421
386
  function autoSaveScrollPosition() {
422
387
  // Safari cannot handle more than 100 `history.replaceState()` calls within 30 seconds (https://github.com/vikejs/vike/issues/46)
423
388
  window.addEventListener('scroll', throttle(saveScrollPosition, Math.ceil(1000 / 3)), { passive: true });
@@ -491,3 +456,44 @@ function isDisableAutomaticLinkInterception() {
491
456
  return globalObject.disableAutomaticLinkInterception ?? false
492
457
  */
493
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
+ }
@@ -0,0 +1,8 @@
1
+ export { isClientSideRoutable };
2
+ import type { PageFile } from '../../shared/getPageFiles.js';
3
+ import type { PageConfigRuntime } from '../../shared/page-configs/PageConfig.js';
4
+ declare function isClientSideRoutable(pageContext: {
5
+ _pageId: string | null;
6
+ _pageFilesAll: PageFile[];
7
+ _pageConfigs: PageConfigRuntime[];
8
+ }): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ export { isClientSideRoutable };
2
+ import { analyzePageClientSideInit } from '../../shared/getPageFiles/analyzePageClientSide.js';
3
+ import { findPageConfig } from '../../shared/page-configs/findPageConfig.js';
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, {
10
+ sharedPageFilesAlreadyLoaded: false
11
+ });
12
+ const pageConfig = findPageConfig(pageContext._pageConfigs, pageContext._pageId);
13
+ const { isClientSideRenderable, isClientRouting } = analyzeClientSide(pageConfig, pageContext._pageFilesAll, pageContext._pageId);
14
+ return isClientSideRenderable && isClientRouting;
15
+ }
@@ -1,6 +1,5 @@
1
1
  export { navigate };
2
2
  export { reload };
3
- export { defineNavigate };
4
3
  /** Programmatically navigate to a new page.
5
4
  *
6
5
  * https://vike.dev/navigate
@@ -13,5 +12,4 @@ declare function navigate(url: string, { keepScrollPosition, overwriteLastHistor
13
12
  keepScrollPosition?: boolean | undefined;
14
13
  overwriteLastHistoryEntry?: boolean | undefined;
15
14
  }): Promise<void>;
16
- declare function defineNavigate(navigate_: typeof navigate): void;
17
15
  declare function reload(): Promise<void>;
@@ -1,9 +1,8 @@
1
1
  export { navigate };
2
2
  export { reload };
3
- export { defineNavigate };
4
- import { assertUsage, isBrowser, getGlobalObject, assertClientRouting, checkIfClientRouting, getCurrentUrl } from './utils.js';
3
+ import { renderPageClientSide } from './installClientRouter.js';
4
+ import { assertUsage, isBrowser, assertClientRouting, checkIfClientRouting, getCurrentUrl } from './utils.js';
5
5
  assertClientRouting();
6
- const globalObject = getGlobalObject('navigate.ts', {});
7
6
  /** Programmatically navigate to a new page.
8
7
  *
9
8
  * https://vike.dev/navigate
@@ -15,7 +14,6 @@ const globalObject = getGlobalObject('navigate.ts', {});
15
14
  async function navigate(url, { keepScrollPosition = false, overwriteLastHistoryEntry = false } = {}) {
16
15
  assertUsage(isBrowser(), 'The navigate() function can be called only on the client-side', { showStackTrace: true });
17
16
  const errMsg = 'navigate() works only with Client Routing, see https://vike.dev/navigate';
18
- assertUsage(globalObject.navigate, errMsg, { showStackTrace: true });
19
17
  assertUsage(checkIfClientRouting(), errMsg, { showStackTrace: true });
20
18
  assertUsage(url, '[navigate(url)] Missing argument url', { showStackTrace: true });
21
19
  assertUsage(typeof url === 'string', '[navigate(url)] Argument url should be a string', { showStackTrace: true });
@@ -24,10 +22,14 @@ async function navigate(url, { keepScrollPosition = false, overwriteLastHistoryE
24
22
  assertUsage(url.startsWith('/'), '[navigate(url)] Argument url should start with a leading /', {
25
23
  showStackTrace: true
26
24
  });
27
- await globalObject.navigate(url, { keepScrollPosition, overwriteLastHistoryEntry });
28
- }
29
- function defineNavigate(navigate_) {
30
- globalObject.navigate = navigate_;
25
+ const scrollTarget = keepScrollPosition ? 'preserve-scroll' : 'scroll-to-top-or-hash';
26
+ await renderPageClientSide({
27
+ scrollTarget,
28
+ urlOriginal: url,
29
+ overwriteLastHistoryEntry,
30
+ isBackwardNavigation: false,
31
+ checkIfClientSideRenderable: true
32
+ });
31
33
  }
32
34
  async function reload() {
33
35
  await navigate(getCurrentUrl());