vike 0.4.219 → 0.4.220-commit-a9f46b8
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/api/build.js +23 -58
- package/dist/cjs/node/api/context.js +6 -8
- package/dist/cjs/node/api/prepareViteApiCall.js +6 -7
- package/dist/cjs/node/cli/context.js +16 -0
- package/dist/cjs/node/cli/entry.js +2 -0
- package/dist/cjs/node/cli/utils.js +1 -0
- package/dist/cjs/node/plugin/plugins/autoFullBuild.js +32 -19
- package/dist/cjs/node/plugin/plugins/baseUrls.js +1 -1
- package/dist/cjs/node/plugin/plugins/commonConfig.js +10 -8
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +3 -2
- package/dist/cjs/node/prerender/{isPrerenderAutoRunEnabled.js → context.js} +9 -1
- package/dist/cjs/node/prerender/runPrerender.js +55 -33
- package/dist/cjs/node/prerender/utils.js +1 -0
- package/dist/cjs/node/runtime/globalContext.js +2 -22
- package/dist/cjs/node/runtime/page-files/setup.js +1 -1
- package/dist/cjs/node/runtime/utils.js +1 -0
- package/dist/cjs/shared/page-configs/loadConfigValues.js +5 -1
- package/dist/cjs/utils/PROJECT_VERSION.js +1 -1
- package/dist/cjs/utils/assertSetup.js +15 -1
- package/dist/cjs/utils/catchInfiniteLoop.js +34 -0
- package/dist/cjs/utils/isDev.js +2 -0
- package/dist/cjs/utils/makePublicCopy.js +32 -0
- package/dist/esm/client/client-routing-runtime/history.d.ts +3 -1
- package/dist/esm/client/client-routing-runtime/history.js +23 -18
- package/dist/esm/client/client-routing-runtime/index.d.ts +0 -1
- package/dist/esm/client/client-routing-runtime/index.js +0 -1
- package/dist/esm/client/client-routing-runtime/initClientRouter.js +2 -2
- package/dist/esm/client/client-routing-runtime/initOnLinkClick.js +3 -4
- package/dist/esm/client/client-routing-runtime/initOnPopState.d.ts +0 -10
- package/dist/esm/client/client-routing-runtime/initOnPopState.js +50 -62
- package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +15 -15
- package/dist/esm/client/client-routing-runtime/scrollRestoration.d.ts +4 -6
- package/dist/esm/client/client-routing-runtime/scrollRestoration.js +17 -12
- package/dist/esm/client/client-routing-runtime/setScrollPosition.d.ts +1 -1
- package/dist/esm/client/client-routing-runtime/setScrollPosition.js +29 -5
- package/dist/esm/client/client-routing-runtime/utils.d.ts +1 -0
- package/dist/esm/client/client-routing-runtime/utils.js +1 -0
- package/dist/esm/client/shared/normalizeClientSideUrl.js +2 -3
- package/dist/esm/node/api/build.d.ts +1 -6
- package/dist/esm/node/api/build.js +20 -25
- package/dist/esm/node/api/context.d.ts +4 -4
- package/dist/esm/node/api/context.js +6 -9
- package/dist/esm/node/api/prepareViteApiCall.d.ts +0 -1
- package/dist/esm/node/api/prepareViteApiCall.js +7 -8
- package/dist/esm/node/cli/context.d.ts +5 -0
- package/dist/esm/node/cli/context.js +14 -0
- package/dist/esm/node/cli/entry.js +2 -0
- package/dist/esm/node/cli/parseCli.d.ts +3 -1
- package/dist/esm/node/cli/utils.d.ts +1 -0
- package/dist/esm/node/cli/utils.js +1 -0
- package/dist/esm/node/plugin/plugins/autoFullBuild.js +31 -18
- package/dist/esm/node/plugin/plugins/baseUrls.js +1 -1
- package/dist/esm/node/plugin/plugins/commonConfig.d.ts +8 -2
- package/dist/esm/node/plugin/plugins/commonConfig.js +8 -6
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVirtualFilePageConfigs.js +3 -2
- package/dist/esm/node/prerender/{isPrerenderAutoRunEnabled.d.ts → context.d.ts} +4 -0
- package/dist/esm/node/prerender/{isPrerenderAutoRunEnabled.js → context.js} +9 -1
- package/dist/esm/node/prerender/runPrerender.d.ts +42 -1
- package/dist/esm/node/prerender/runPrerender.js +56 -34
- package/dist/esm/node/prerender/utils.d.ts +1 -0
- package/dist/esm/node/prerender/utils.js +1 -0
- package/dist/esm/node/runtime/globalContext.js +3 -23
- package/dist/esm/node/runtime/page-files/setup.js +1 -1
- package/dist/esm/node/runtime/utils.d.ts +1 -0
- package/dist/esm/node/runtime/utils.js +1 -0
- package/dist/esm/shared/page-configs/PageConfig.d.ts +6 -3
- package/dist/esm/shared/page-configs/loadConfigValues.js +6 -2
- package/dist/esm/utils/PROJECT_VERSION.d.ts +1 -1
- package/dist/esm/utils/PROJECT_VERSION.js +1 -1
- package/dist/esm/utils/assertSetup.js +15 -1
- package/dist/esm/utils/catchInfiniteLoop.d.ts +2 -0
- package/dist/esm/utils/catchInfiniteLoop.js +32 -0
- package/dist/esm/utils/isDev.js +2 -0
- package/dist/esm/utils/makePublicCopy.d.ts +3 -0
- package/dist/esm/utils/makePublicCopy.js +30 -0
- package/dist/esm/utils/projectInfo.d.ts +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,11 @@ async function loadConfigValues(pageConfig, isDev) {
|
|
|
9
9
|
!isDev) {
|
|
10
10
|
return pageConfig;
|
|
11
11
|
}
|
|
12
|
-
const
|
|
12
|
+
const { moduleId, moduleExports } = pageConfig.loadConfigValuesAll();
|
|
13
|
+
const configValuesLoaded = await moduleExports;
|
|
14
|
+
// `configValuesLoaded` is sometimes `undefined` https://github.com/vikejs/vike/discussions/2092
|
|
15
|
+
if (!configValuesLoaded)
|
|
16
|
+
(0, utils_js_1.assert)(false, { moduleExports, configValuesLoaded, moduleId });
|
|
13
17
|
const configValues = (0, parsePageConfigs_js_1.parseConfigValuesSerialized)(configValuesLoaded.configValuesSerialized);
|
|
14
18
|
Object.assign(pageConfig.configValues, configValues);
|
|
15
19
|
(0, utils_js_1.objectAssign)(pageConfig, { isAllLoaded: true });
|
|
@@ -121,7 +121,7 @@ function getEnvDescription() {
|
|
|
121
121
|
// https://github.com/cloudflare/workers-sdk/issues/7886
|
|
122
122
|
function assertNodeEnvIsNotUndefinedString() {
|
|
123
123
|
const nodeEnv = getNodeEnv();
|
|
124
|
-
(0, assert_js_1.assertWarning)(nodeEnv !== 'undefined', `${picocolors_1.default.cyan('process.env.NODE_ENV==="undefined"')} which is unexpected: ${picocolors_1.default.cyan('process.env.NODE_ENV')}
|
|
124
|
+
(0, assert_js_1.assertWarning)(nodeEnv !== 'undefined', `${picocolors_1.default.cyan('process.env.NODE_ENV==="undefined"')} which is unexpected: ${picocolors_1.default.cyan('process.env.NODE_ENV')} is allowed to be the *value* ${picocolors_1.default.cyan('undefined')} (i.e. ${picocolors_1.default.cyan('process.env.NODE_ENV===undefined')}) but it shouldn't be the *string* ${picocolors_1.default.cyan('"undefined"')} ${picocolors_1.default.underline('https://vike.dev/NODE_ENV')}`, { onlyOnce: true });
|
|
125
125
|
}
|
|
126
126
|
function isNodeEnvDev() {
|
|
127
127
|
const nodeEnv = getNodeEnv();
|
|
@@ -141,6 +141,20 @@ function getNodeEnv() {
|
|
|
141
141
|
catch {
|
|
142
142
|
return undefined;
|
|
143
143
|
}
|
|
144
|
+
/*
|
|
145
|
+
// Should we show the following warning? So far I don't think so because of the following. Maybe we can show it once we enable users to disable warnings.
|
|
146
|
+
// - The warning isn't always actionable, e.g. if it's a tool that dynamically sets `process.env.NODE_ENV`.
|
|
147
|
+
// - We assume that tools use `process.env.NODE_ENV` and not someting like `const { env } = process; env.NODE_ENV`. Thus, in practice, `val` overrides `val2` so having `val!==val2` isn't an issue.
|
|
148
|
+
{
|
|
149
|
+
const val2 = process.env['NODE' + '_ENV']
|
|
150
|
+
if (val2)
|
|
151
|
+
assertWarning(
|
|
152
|
+
val === val2,
|
|
153
|
+
`Dynamically setting process.env.NODE_ENV to ${val2} hasn't any effect because process.env.NODE_ENV is being statically replaced to ${val}.`,
|
|
154
|
+
{ onlyOnce: true }
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
//*/
|
|
144
158
|
return val;
|
|
145
159
|
}
|
|
146
160
|
function setNodeEnvProduction() {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.catchInfiniteLoop = catchInfiniteLoop;
|
|
4
|
+
const assert_js_1 = require("./assert.js");
|
|
5
|
+
const trackers = {};
|
|
6
|
+
function catchInfiniteLoop(functionName, maxNumberOfCalls = 100, withinSeconds = 5) {
|
|
7
|
+
// Init
|
|
8
|
+
const now = new Date();
|
|
9
|
+
let tracker = (trackers[functionName] ?? (trackers[functionName] = createTracker(now)));
|
|
10
|
+
// Reset
|
|
11
|
+
const elapsedTime = now.getTime() - tracker.start.getTime();
|
|
12
|
+
if (elapsedTime > withinSeconds * 1000)
|
|
13
|
+
tracker = trackers[functionName] = createTracker(now);
|
|
14
|
+
// Count
|
|
15
|
+
tracker.count++;
|
|
16
|
+
// Error
|
|
17
|
+
const msg = `[Infinite Loop] ${functionName} called ${tracker.count} times within ${withinSeconds} seconds`;
|
|
18
|
+
if (tracker.count > maxNumberOfCalls) {
|
|
19
|
+
(0, assert_js_1.assert)(false, msg);
|
|
20
|
+
}
|
|
21
|
+
// Warning, at 50% threshold
|
|
22
|
+
if (!tracker.warned && tracker.count > maxNumberOfCalls * 0.5) {
|
|
23
|
+
// Warning is shown upon 10 calls a second, on average during 5 seconds, given the default parameters
|
|
24
|
+
(0, assert_js_1.assertWarning)(false, msg, { onlyOnce: false });
|
|
25
|
+
tracker.warned = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function createTracker(now) {
|
|
29
|
+
const tracker = {
|
|
30
|
+
count: 0,
|
|
31
|
+
start: now
|
|
32
|
+
};
|
|
33
|
+
return tracker;
|
|
34
|
+
}
|
package/dist/cjs/utils/isDev.js
CHANGED
|
@@ -7,6 +7,8 @@ const assert_js_1 = require("./assert.js");
|
|
|
7
7
|
function isDevCheck(configEnv) {
|
|
8
8
|
const { isPreview, command } = configEnv;
|
|
9
9
|
// Released at vite@5.1.0 which is guaranteed with `assertVersion('Vite', version, '5.1.0')`
|
|
10
|
+
// - Release: https://github.com/vitejs/vite/blob/6f7466e6211027686f40ad7e4ce6ec8477414546/packages/vite/CHANGELOG.md#510-beta4-2024-01-26:~:text=fix(preview)%3A-,set%20isPreview%20true,-(%2315695)%20(93fce55
|
|
11
|
+
// - Surprisingly, this assert() can fail: https://github.com/vikejs/vike/issues/2120
|
|
10
12
|
(0, assert_js_1.assert)(typeof isPreview === 'boolean');
|
|
11
13
|
return command === 'serve' && !isPreview;
|
|
12
14
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makePublicCopy = makePublicCopy;
|
|
4
|
+
const assert_js_1 = require("./assert.js");
|
|
5
|
+
const objectKeys_js_1 = require("./objectKeys.js");
|
|
6
|
+
/** Prefix internal properties with `_` + show warning */
|
|
7
|
+
function makePublicCopy(obj, objName, propsPublic, propsInternalNoWarning) {
|
|
8
|
+
const objPublic = {};
|
|
9
|
+
(0, objectKeys_js_1.objectKeys)(obj).forEach((key) => {
|
|
10
|
+
const val = obj[key];
|
|
11
|
+
if (propsPublic.includes(key)) {
|
|
12
|
+
objPublic[key] = val;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const keyPublic = key.startsWith('_') ? key : `_${key}`;
|
|
16
|
+
if (propsInternalNoWarning?.includes(key)) {
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
objPublic[keyPublic] = val;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
Object.defineProperty(objPublic, keyPublic, {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get() {
|
|
24
|
+
(0, assert_js_1.assertWarning)(false, `Using internal ${objName}.${keyPublic} which may break in any minor version update. Reach out on GitHub and elaborate your use case so that the Vike team can add official support for your use case.`, { onlyOnce: true });
|
|
25
|
+
return val;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return objPublic;
|
|
32
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { pushHistoryState };
|
|
2
|
+
export { replaceHistoryStateOriginal };
|
|
2
3
|
export { onPopStateBegin };
|
|
3
4
|
export { saveScrollPosition };
|
|
4
5
|
export { initHistoryState };
|
|
@@ -17,13 +18,14 @@ type ScrollPosition = {
|
|
|
17
18
|
};
|
|
18
19
|
declare function saveScrollPosition(): void;
|
|
19
20
|
declare function pushHistoryState(url: string, overwriteLastHistoryEntry: boolean): void;
|
|
21
|
+
declare function replaceHistoryStateOriginal(state: unknown, url: string): void;
|
|
20
22
|
declare function monkeyPatchHistoryAPI(): void;
|
|
21
23
|
type HistoryInfo = {
|
|
22
24
|
url: `/${string}`;
|
|
23
25
|
state: StateEnhanced;
|
|
24
26
|
};
|
|
25
27
|
declare function onPopStateBegin(): {
|
|
26
|
-
|
|
28
|
+
isHistoryStateEnhanced: boolean;
|
|
27
29
|
previous: HistoryInfo;
|
|
28
30
|
current: HistoryInfo;
|
|
29
31
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { pushHistoryState };
|
|
2
|
+
export { replaceHistoryStateOriginal };
|
|
2
3
|
export { onPopStateBegin };
|
|
3
4
|
export { saveScrollPosition };
|
|
4
5
|
export { initHistoryState };
|
|
5
6
|
export { monkeyPatchHistoryAPI };
|
|
6
7
|
import { getCurrentUrl } from '../shared/getCurrentUrl.js';
|
|
7
|
-
import { assert, assertUsage, getGlobalObject,
|
|
8
|
+
import { assert, assertUsage, getGlobalObject, isObject } from './utils.js';
|
|
8
9
|
initHistoryState(); // we redundantly call initHistoryState() to ensure it's called early
|
|
9
10
|
const globalObject = getGlobalObject('history.ts', { previous: getHistoryInfo() });
|
|
10
11
|
// `window.history.state === null` when:
|
|
@@ -90,6 +91,11 @@ function replaceHistoryState(state, url) {
|
|
|
90
91
|
const url_ = url ?? null; // Passing `undefined` chokes older Edge versions.
|
|
91
92
|
window.history.replaceState(state, '', url_);
|
|
92
93
|
}
|
|
94
|
+
function replaceHistoryStateOriginal(state, url) {
|
|
95
|
+
// Bypass all monkey patches.
|
|
96
|
+
// - Useful, for example, to avoid other tools listening to history.replaceState() calls
|
|
97
|
+
History.prototype.replaceState.bind(window.history)(state, '', url);
|
|
98
|
+
}
|
|
93
99
|
// Monkey patch:
|
|
94
100
|
// - history.pushState()
|
|
95
101
|
// - history.replaceState()
|
|
@@ -116,21 +122,19 @@ function monkeyPatchHistoryAPI() {
|
|
|
116
122
|
});
|
|
117
123
|
}
|
|
118
124
|
function isVikeEnhanced(state) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (state.scrollPosition !== null) {
|
|
131
|
-
assert(hasProp(state, 'scrollPosition', 'object'));
|
|
132
|
-
assert(hasProp(state.scrollPosition, 'x', 'number') && hasProp(state.scrollPosition, 'y', 'number'));
|
|
125
|
+
if (isObject(state) && '_isVikeEnhanced' in state) {
|
|
126
|
+
/* We don't use the assert() below to save client-side KBs.
|
|
127
|
+
assert(hasProp(state, '_isVikeEnhanced', 'true'))
|
|
128
|
+
assert(hasProp(state, 'timestamp', 'number'))
|
|
129
|
+
assert(hasProp(state, 'scrollPosition'))
|
|
130
|
+
if (state.scrollPosition !== null) {
|
|
131
|
+
assert(hasProp(state, 'scrollPosition', 'object'))
|
|
132
|
+
assert(hasProp(state.scrollPosition, 'x', 'number') && hasProp(state.scrollPosition, 'y', 'number'))
|
|
133
|
+
}
|
|
134
|
+
//*/
|
|
135
|
+
return true;
|
|
133
136
|
}
|
|
137
|
+
return false;
|
|
134
138
|
}
|
|
135
139
|
function getHistoryInfo() {
|
|
136
140
|
return {
|
|
@@ -140,12 +144,13 @@ function getHistoryInfo() {
|
|
|
140
144
|
}
|
|
141
145
|
function onPopStateBegin() {
|
|
142
146
|
const { previous } = globalObject;
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
147
|
+
const isHistoryStateEnhanced = window.history.state !== null;
|
|
148
|
+
if (!isHistoryStateEnhanced)
|
|
145
149
|
enhanceHistoryState();
|
|
150
|
+
assert(isVikeEnhanced(window.history.state));
|
|
146
151
|
const current = getHistoryInfo();
|
|
147
152
|
globalObject.previous = current;
|
|
148
|
-
return {
|
|
153
|
+
return { isHistoryStateEnhanced, previous, current };
|
|
149
154
|
}
|
|
150
155
|
function initHistoryState() {
|
|
151
156
|
enhanceHistoryState();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { navigate, reload } from './navigate.js';
|
|
2
2
|
export { prefetch } from './prefetch.js';
|
|
3
|
-
export { onPopState } from './initOnPopState.js';
|
|
4
3
|
export { PROJECT_VERSION as version } from './utils.js';
|
|
5
4
|
import type { PageContextBuiltInClientWithClientRouting } from '../../shared/types.js';
|
|
6
5
|
/** @deprecated
|
|
@@ -3,7 +3,7 @@ import { assert } from './utils.js';
|
|
|
3
3
|
import { getRenderCount, renderPageClientSide } from './renderPageClientSide.js';
|
|
4
4
|
import { initOnPopState } from './initOnPopState.js';
|
|
5
5
|
import { initOnLinkClick } from './initOnLinkClick.js';
|
|
6
|
-
import {
|
|
6
|
+
import { scrollRestoration_init } from './scrollRestoration.js';
|
|
7
7
|
import { autoSaveScrollPosition } from './setScrollPosition.js';
|
|
8
8
|
import { initLinkPrefetchHandlers } from './prefetch.js';
|
|
9
9
|
import { initHistoryState, monkeyPatchHistoryAPI } from './history.js';
|
|
@@ -28,9 +28,9 @@ async function renderFirstPage() {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
function initHistoryAndScroll() {
|
|
31
|
+
scrollRestoration_init();
|
|
31
32
|
monkeyPatchHistoryAPI();
|
|
32
33
|
initHistoryState(); // we redundantly call initHistoryState() to ensure it's called early
|
|
33
|
-
setupNativeScrollRestoration();
|
|
34
34
|
autoSaveScrollPosition();
|
|
35
35
|
// Handle back-/forward navigation
|
|
36
36
|
initOnPopState();
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Code adapted from https://github.com/HenrikJoreteg/internal-nav-helper/blob/5199ec5448d0b0db7ec63cf76d88fa6cad878b7d/src/index.js#L11-L29
|
|
2
1
|
export { initOnLinkClick };
|
|
3
|
-
import { assert } from './utils.js';
|
|
4
2
|
import { isSameAsCurrentUrl, skipLink } from './skipLink.js';
|
|
5
3
|
import { renderPageClientSide } from './renderPageClientSide.js';
|
|
6
4
|
import { scrollToHashOrTop } from './setScrollPosition.js';
|
|
@@ -14,10 +12,12 @@ async function onClick(ev) {
|
|
|
14
12
|
if (!linkTag)
|
|
15
13
|
return;
|
|
16
14
|
const href = linkTag.getAttribute('href');
|
|
15
|
+
if (href === null)
|
|
16
|
+
return;
|
|
17
17
|
// Workaround for Firefox bug: clicking on a hash link that doesn't change the current URL causes Firefox to erroneously set `window.history.state = null` without firing any signal that we can detect.
|
|
18
18
|
// - https://github.com/vikejs/vike/issues/1962
|
|
19
19
|
// - https://github.com/sveltejs/kit/issues/8725
|
|
20
|
-
if (href
|
|
20
|
+
if (href.includes('#') && isSameAsCurrentUrl(href)) {
|
|
21
21
|
// Prevent Firefox from setting `window.history.state` to `null`
|
|
22
22
|
ev.preventDefault();
|
|
23
23
|
// Replicate the browser's native behavior
|
|
@@ -26,7 +26,6 @@ async function onClick(ev) {
|
|
|
26
26
|
}
|
|
27
27
|
if (skipLink(linkTag))
|
|
28
28
|
return;
|
|
29
|
-
assert(href);
|
|
30
29
|
ev.preventDefault();
|
|
31
30
|
let scrollTarget;
|
|
32
31
|
{
|
|
@@ -1,12 +1,2 @@
|
|
|
1
1
|
export { initOnPopState };
|
|
2
|
-
export { onPopState };
|
|
3
|
-
import { type HistoryInfo } from './history.js';
|
|
4
2
|
declare function initOnPopState(): void;
|
|
5
|
-
type Listener = (arg: {
|
|
6
|
-
previous: HistoryInfo;
|
|
7
|
-
}) => void | boolean;
|
|
8
|
-
/** Control back-/forward navigation.
|
|
9
|
-
*
|
|
10
|
-
* https://vike.dev/onPopState
|
|
11
|
-
*/
|
|
12
|
-
declare function onPopState(listener: Listener): void;
|
|
@@ -1,71 +1,59 @@
|
|
|
1
1
|
export { initOnPopState };
|
|
2
|
-
export { onPopState };
|
|
3
|
-
import { assertWarning, getGlobalObject } from './utils.js';
|
|
4
2
|
import { onPopStateBegin } from './history.js';
|
|
5
3
|
import { renderPageClientSide } from './renderPageClientSide.js';
|
|
6
4
|
import { setScrollPosition } from './setScrollPosition.js';
|
|
7
|
-
|
|
5
|
+
import { catchInfiniteLoop } from './utils.js';
|
|
6
|
+
// The 'popstate' event is trigged when the browser doesn't fully load the new URL, for example:
|
|
7
|
+
// - `location.hash='#foo'` triggers the popstate event while `location.href='/foo'` doesn't.
|
|
8
|
+
// - Clicking on the browser's back-/forward button triggers a popstate event only if the history entry was generated with history.pushState() — no popstate event is fired upon Server Routing.
|
|
9
|
+
// Concretely, 'popstate' is fired when:
|
|
10
|
+
// 1. Back-/forward navigation:
|
|
11
|
+
// - By the user using the browser's back-/forward navigation
|
|
12
|
+
// - By the app using `history.back()` / `history.forward()` / `history.go()`
|
|
13
|
+
// > Except of history entries triggered by Server Routing, see comment above.
|
|
14
|
+
// 2. URL hash changes:
|
|
15
|
+
// - By the user clicking on `<a href="#some-hash">`
|
|
16
|
+
// - The popstate event is *only* triggered if `href` starts with '#' (even if `href==='/foo#bar'` and the current URL has the same pathname '/foo' then popstate isn't triggered)
|
|
17
|
+
// - Vike doesn't intercept hash links (see `skipLink()`) and let's the browser handle them.
|
|
18
|
+
// - By the app using a `location` API such as `location.hash = 'some-hash'`
|
|
19
|
+
// - Only upon hash navigation: setting `location.href='/foo'` triggers a full page reload and no popstate event is fired.
|
|
20
|
+
// - Also upon `location.href='/foo#bar'` while the current URL is '/foo' (unlike <a> clicks).
|
|
21
|
+
// Notes:
|
|
22
|
+
// - The 'hashchange' event is fired after popstate, so we cannot use it to distinguish between hash and non-hash navigations.
|
|
23
|
+
// - It isn't possible to monkey patch the `location` APIs. (Chrome throws `TypeError: Cannot redefine property` when attempt to overwrite any `location` property.)
|
|
24
|
+
// - Text links aren't supported: https://github.com/vikejs/vike/issues/2114
|
|
25
|
+
// - docs/ is a good playground to test all this.
|
|
8
26
|
function initOnPopState() {
|
|
9
|
-
|
|
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
|
-
// - Click on `<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
|
-
// - `location.hash = 'some-hash'`
|
|
17
|
-
// - The `event` argument 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', async () => {
|
|
19
|
-
const { isNewState, previous, current } = onPopStateBegin();
|
|
20
|
-
const scrollTarget = current.state.scrollPosition || undefined;
|
|
21
|
-
const isUserPushStateNavigation = current.state.triggeredBy === 'user' || previous.state.triggeredBy === 'user';
|
|
22
|
-
const isHashNavigation = removeHash(current.url) === removeHash(previous.url) && current.url !== previous.url;
|
|
23
|
-
// - `isNewState === true` when:
|
|
24
|
-
// - Click on `<a href="#some-hash" />` (note that Vike's `initOnLinkClick()` handler skips hash links)
|
|
25
|
-
// - `location.hash = 'some-hash'`
|
|
26
|
-
// - `isNewState === false` when `popstate` was triggered by the user clicking on his browser's forward/backward history button.
|
|
27
|
-
const isHashNavigationNew = isHashNavigation && isNewState;
|
|
28
|
-
const isBackwardNavigation = !current.state.timestamp || !previous.state.timestamp ? null : current.state.timestamp < previous.state.timestamp;
|
|
29
|
-
// We have to scroll ourselves because we use `window.history.scrollRestoration = 'manual'`. So far this seems to work. Alternatives in case it doesn't work:
|
|
30
|
-
// - Alternative: we use `window.history.scrollRestoration = 'auto'`
|
|
31
|
-
// - Problem: I don't think it's possbible to set `window.history.scrollRestoration = 'auto'` only for hash navigation and not for non-hash navigations?
|
|
32
|
-
// - Problem: inconsistencies between browsers? For example specification says that setting `window.history.scrollRestoration` only affects the current entry in the session history but this contradicts what people are experiencing in practice.
|
|
33
|
-
// - Specification: https://html.spec.whatwg.org/multipage/history.html#the-history-interface
|
|
34
|
-
// - Practice: https://stackoverflow.com/questions/70188241/history-scrollrestoration-manual-doesnt-prevent-safari-from-restoring-scrol
|
|
35
|
-
// - Alternative: we completely take over hash navigation and reproduce the browser's native behavior upon hash navigation.
|
|
36
|
-
// - By using the `hashchange` event.
|
|
37
|
-
// - Problem: conflict if 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.
|
|
38
|
-
if (isHashNavigation) {
|
|
39
|
-
if (!isHashNavigationNew) {
|
|
40
|
-
setScrollPosition(scrollTarget);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// The browser already scrolled to `#${hash}` => the current scroll position is the right one => we saved it with `enhanceHistoryState()`.
|
|
44
|
-
}
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
let doNotRenderIfSamePage = isUserPushStateNavigation;
|
|
48
|
-
let abort;
|
|
49
|
-
globalObject.listeners.forEach((listener) => {
|
|
50
|
-
abort || (abort = listener({ previous }));
|
|
51
|
-
});
|
|
52
|
-
if (abort) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (abort === false) {
|
|
56
|
-
doNotRenderIfSamePage = false;
|
|
57
|
-
}
|
|
58
|
-
await renderPageClientSide({ scrollTarget, isBackwardNavigation, doNotRenderIfSamePage });
|
|
59
|
-
});
|
|
27
|
+
window.addEventListener('popstate', onPopState);
|
|
60
28
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
29
|
+
async function onPopState() {
|
|
30
|
+
catchInfiniteLoop('onPopState()');
|
|
31
|
+
const { isHistoryStateEnhanced, previous, current } = onPopStateBegin();
|
|
32
|
+
// - `isHistoryStateEnhanced===false` <=> new hash navigation:
|
|
33
|
+
// - Click on `<a href="#some-hash">`
|
|
34
|
+
// - Using the `location` API (only hash navigation, see comments above).
|
|
35
|
+
// - `isHistoryStateEnhanced===true` <=> back-/forward navigation (including back-/forward hash navigation).
|
|
36
|
+
// > Only back-/forward client-side navigation: no 'popstate' event is fired upon Server Routing (when the user clicks on a link before the page's JavaScript loaded), see comments above.
|
|
37
|
+
if (!isHistoryStateEnhanced) {
|
|
38
|
+
// Let the browser handle it
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
await handleBackForwardNavigation(previous, current);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function handleBackForwardNavigation(previous, current) {
|
|
46
|
+
const scrollTarget = current.state.scrollPosition || undefined;
|
|
47
|
+
const isHashNavigation = removeHash(current.url) === removeHash(previous.url) && current.url !== previous.url;
|
|
48
|
+
if (isHashNavigation) {
|
|
49
|
+
// We have to scroll ourselves because we have set `window.history.scrollRestoration = 'manual'`
|
|
50
|
+
setScrollPosition(scrollTarget);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const isUserPushStateNavigation = current.state.triggeredBy === 'user' || previous.state.triggeredBy === 'user';
|
|
54
|
+
const doNotRenderIfSamePage = isUserPushStateNavigation;
|
|
55
|
+
const isBackwardNavigation = !current.state.timestamp || !previous.state.timestamp ? null : current.state.timestamp < previous.state.timestamp;
|
|
56
|
+
await renderPageClientSide({ scrollTarget, isBackwardNavigation, doNotRenderIfSamePage });
|
|
69
57
|
}
|
|
70
58
|
function removeHash(url) {
|
|
71
59
|
return url.split('#')[0];
|
|
@@ -2,7 +2,7 @@ export { renderPageClientSide };
|
|
|
2
2
|
export { getRenderCount };
|
|
3
3
|
export { disableClientRouting };
|
|
4
4
|
export { firstRenderStartPromise };
|
|
5
|
-
import { assert, isSameErrorMessage, objectAssign, redirectHard, getGlobalObject, executeHook, hasProp, augmentType, genPromise, isCallable } from './utils.js';
|
|
5
|
+
import { assert, isSameErrorMessage, objectAssign, redirectHard, getGlobalObject, executeHook, hasProp, augmentType, genPromise, isCallable, catchInfiniteLoop } from './utils.js';
|
|
6
6
|
import { getPageContextFromClientHooks, getPageContextFromServerHooks, getPageContextFromHooks_isHydration, getPageContextFromHooks_serialized, setPageContextInitIsPassedToClient } from './getPageContextFromHooks.js';
|
|
7
7
|
import { createPageContext } from './createPageContext.js';
|
|
8
8
|
import { addLinkPrefetchHandlers, addLinkPrefetchHandlers_unwatch, addLinkPrefetchHandlers_watch, getPageContextPrefetched, populatePageContextPrefetchCache } from './prefetch.js';
|
|
@@ -15,7 +15,7 @@ import { assertNoInfiniteAbortLoop, getPageContextFromAllRewrites, isAbortError,
|
|
|
15
15
|
import { route } from '../../shared/route/index.js';
|
|
16
16
|
import { isClientSideRoutable } from './isClientSideRoutable.js';
|
|
17
17
|
import { setScrollPosition } from './setScrollPosition.js';
|
|
18
|
-
import {
|
|
18
|
+
import { scrollRestoration_initialRenderIsDone } from './scrollRestoration.js';
|
|
19
19
|
import { getErrorPageId } from '../../shared/error-page.js';
|
|
20
20
|
import { setPageContextCurrent } from './getPageContextCurrent.js';
|
|
21
21
|
import { getRouteStringParameterList } from '../../shared/route/resolveRouteString.js';
|
|
@@ -31,6 +31,7 @@ const globalObject = getGlobalObject('renderPageClientSide.ts', (() => {
|
|
|
31
31
|
})());
|
|
32
32
|
const { firstRenderStartPromise } = globalObject;
|
|
33
33
|
async function renderPageClientSide(renderArgs) {
|
|
34
|
+
catchInfiniteLoop('renderPageClientSide()');
|
|
34
35
|
const { urlOriginal = getCurrentUrl(), overwriteLastHistoryEntry = false, isBackwardNavigation, pageContextsFromRewrite = [], redirectCount = 0, doNotRenderIfSamePage, isClientSideNavigation = true, pageContextInitClient } = renderArgs;
|
|
35
36
|
let { scrollTarget } = renderArgs;
|
|
36
37
|
const { previousPageContext } = globalObject;
|
|
@@ -391,18 +392,18 @@ async function renderPageClientSide(renderArgs) {
|
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
};
|
|
394
|
-
// We use globalObject.
|
|
395
|
-
if (globalObject.
|
|
395
|
+
// We use globalObject.onRenderClientPreviousPromise in order to ensure that there is never two concurrent onRenderClient() calls
|
|
396
|
+
if (globalObject.onRenderClientPreviousPromise) {
|
|
396
397
|
// Make sure that the previous render has finished
|
|
397
|
-
await globalObject.
|
|
398
|
-
assert(globalObject.
|
|
398
|
+
await globalObject.onRenderClientPreviousPromise;
|
|
399
|
+
assert(globalObject.onRenderClientPreviousPromise === undefined);
|
|
399
400
|
if (isRenderOutdated())
|
|
400
401
|
return;
|
|
401
402
|
}
|
|
402
403
|
changeUrl(urlOriginal, overwriteLastHistoryEntry);
|
|
403
404
|
globalObject.previousPageContext = pageContext;
|
|
404
|
-
assert(globalObject.
|
|
405
|
-
|
|
405
|
+
assert(globalObject.onRenderClientPreviousPromise === undefined);
|
|
406
|
+
const onRenderClientPromise = (async () => {
|
|
406
407
|
let onRenderClientError;
|
|
407
408
|
try {
|
|
408
409
|
await executeOnRenderClientHook(pageContext, true);
|
|
@@ -410,12 +411,13 @@ async function renderPageClientSide(renderArgs) {
|
|
|
410
411
|
catch (err) {
|
|
411
412
|
onRenderClientError = err;
|
|
412
413
|
}
|
|
413
|
-
globalObject.
|
|
414
|
+
globalObject.onRenderClientPreviousPromise = undefined;
|
|
414
415
|
globalObject.isFirstRenderDone = true;
|
|
415
416
|
return onRenderClientError;
|
|
416
417
|
})();
|
|
417
|
-
|
|
418
|
-
|
|
418
|
+
globalObject.onRenderClientPreviousPromise = onRenderClientPromise;
|
|
419
|
+
const onRenderClientError = await onRenderClientPromise;
|
|
420
|
+
assert(globalObject.onRenderClientPreviousPromise === undefined);
|
|
419
421
|
if (onRenderClientError) {
|
|
420
422
|
await onError(onRenderClientError);
|
|
421
423
|
if (!isErrorPage)
|
|
@@ -475,9 +477,8 @@ async function renderPageClientSide(renderArgs) {
|
|
|
475
477
|
}
|
|
476
478
|
}
|
|
477
479
|
// Page scrolling
|
|
478
|
-
setScrollPosition(scrollTarget);
|
|
479
|
-
|
|
480
|
-
setInitialRenderIsDone();
|
|
480
|
+
setScrollPosition(scrollTarget, urlOriginal);
|
|
481
|
+
scrollRestoration_initialRenderIsDone();
|
|
481
482
|
if (pageContext._hasPageContextFromServer)
|
|
482
483
|
setPageContextInitIsPassedToClient(pageContext);
|
|
483
484
|
// Add link prefetch handlers
|
|
@@ -488,7 +489,6 @@ async function renderPageClientSide(renderArgs) {
|
|
|
488
489
|
function changeUrl(url, overwriteLastHistoryEntry) {
|
|
489
490
|
if (getCurrentUrl() === url)
|
|
490
491
|
return;
|
|
491
|
-
browserNativeScrollRestoration_disable();
|
|
492
492
|
pushHistoryState(url, overwriteLastHistoryEntry);
|
|
493
493
|
}
|
|
494
494
|
function handleErrorFetchingStaticAssets(err, pageContext, isFirstRender) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
declare function
|
|
5
|
-
declare function setInitialRenderIsDone(): void;
|
|
6
|
-
declare function browserNativeScrollRestoration_disable(): void;
|
|
1
|
+
export { scrollRestoration_init };
|
|
2
|
+
export { scrollRestoration_initialRenderIsDone };
|
|
3
|
+
declare function scrollRestoration_init(): void;
|
|
4
|
+
declare function scrollRestoration_initialRenderIsDone(): void;
|
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export { scrollRestoration_init };
|
|
2
|
+
export { scrollRestoration_initialRenderIsDone };
|
|
3
|
+
// Using `window.history.scrollRestoration` to recover scroll position when user reloads the page or Cmd-Shift-T back to it.
|
|
4
|
+
// We let the browser do it because it's fast.
|
|
5
|
+
// - Alternatively we could inject an inline script `<script>scrollTo(history.state.scrollPosition)</script>` early, which seems to be equally fast. (See for example https://vike.dev/usePageContext which sets the main scroll position and the navigation scroll position equally fast.)
|
|
6
|
+
// - Firefox doesn't restore the scroll position upon page reload but does upon Cmd-Shift-T
|
|
7
|
+
// See also: https://github.com/cyco130/knave/blob/e9e1bc7687848504293197f1b314b7d12ad0d228/design.md#scroll-restoration
|
|
5
8
|
import { getGlobalObject, onPageHide, onPageShow } from './utils.js';
|
|
6
9
|
const globalObject = getGlobalObject('scrollRestoration.ts', {});
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
onPageHide(
|
|
11
|
-
onPageShow(() => globalObject.initialRenderIsDone &&
|
|
10
|
+
function scrollRestoration_init() {
|
|
11
|
+
// Use the native scroll restoration mechanism only for the first render
|
|
12
|
+
scrollRestoration_enable();
|
|
13
|
+
onPageHide(scrollRestoration_enable);
|
|
14
|
+
onPageShow(() => globalObject.initialRenderIsDone && scrollRestoration_disable());
|
|
12
15
|
}
|
|
13
|
-
function
|
|
16
|
+
function scrollRestoration_initialRenderIsDone() {
|
|
14
17
|
globalObject.initialRenderIsDone = true;
|
|
18
|
+
scrollRestoration_disable();
|
|
15
19
|
}
|
|
16
|
-
function
|
|
20
|
+
function scrollRestoration_disable() {
|
|
17
21
|
if ('scrollRestoration' in window.history) {
|
|
18
22
|
window.history.scrollRestoration = 'manual';
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
|
-
function
|
|
25
|
+
function scrollRestoration_enable() {
|
|
22
26
|
if ('scrollRestoration' in window.history) {
|
|
27
|
+
// Use the browser's native scroll restoration mechanism
|
|
23
28
|
window.history.scrollRestoration = 'auto';
|
|
24
29
|
}
|
|
25
30
|
}
|
|
@@ -6,6 +6,6 @@ import { type ScrollPosition } from './history.js';
|
|
|
6
6
|
type ScrollTarget = undefined | {
|
|
7
7
|
preserveScroll: boolean;
|
|
8
8
|
} | ScrollPosition;
|
|
9
|
-
declare function setScrollPosition(scrollTarget: ScrollTarget): void;
|
|
9
|
+
declare function setScrollPosition(scrollTarget: ScrollTarget, url?: string): void;
|
|
10
10
|
declare function scrollToHashOrTop(hash: null | string): void;
|
|
11
11
|
declare function autoSaveScrollPosition(): void;
|