vike 0.4.198-commit-77f6a27 → 0.4.199-commit-3be497f

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.
@@ -88,7 +88,7 @@ async function transpileWithEsbuild(filePath, userRootDir, transformImports) {
88
88
  metafile: true,
89
89
  bundle: true
90
90
  };
91
- let pointerImports = {};
91
+ const pointerImports = {};
92
92
  options.plugins = [
93
93
  // Determine whether an import should be:
94
94
  // - A pointer import
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROJECT_VERSION = void 0;
4
4
  // Automatically updated by @brillout/release-me
5
- exports.PROJECT_VERSION = '0.4.198-commit-77f6a27';
5
+ exports.PROJECT_VERSION = '0.4.199-commit-3be497f';
@@ -1,16 +1,17 @@
1
- export { initHistoryState, getHistoryState, pushHistory, ScrollPosition, saveScrollPosition, monkeyPatchHistoryPushState };
2
- type HistoryState = {
3
- timestamp?: number;
4
- scrollPosition?: null | ScrollPosition;
5
- triggeredBy?: 'user' | 'vike' | 'browser';
1
+ export { enhanceHistoryState, getHistoryState, pushHistory, type ScrollPosition, saveScrollPosition, monkeyPatchHistoryPushState };
2
+ type StateVikeEnhanced = {
3
+ timestamp: number;
4
+ scrollPosition: null | ScrollPosition;
5
+ triggeredBy: 'user' | 'vike' | 'browser';
6
6
  _isVikeEnhanced: true;
7
7
  };
8
8
  type ScrollPosition = {
9
9
  x: number;
10
10
  y: number;
11
11
  };
12
- declare function initHistoryState(): void;
13
- declare function getHistoryState(): HistoryState;
12
+ type StateNotInitialized = null | undefined | Partial<StateVikeEnhanced> | StateVikeEnhanced;
13
+ declare function enhanceHistoryState(): void;
14
+ declare function getHistoryState(): StateNotInitialized;
14
15
  declare function saveScrollPosition(): void;
15
16
  declare function pushHistory(url: string, overwriteLastHistoryEntry: boolean): void;
16
17
  declare function monkeyPatchHistoryPushState(): void;
@@ -1,37 +1,48 @@
1
- export { initHistoryState, getHistoryState, pushHistory, saveScrollPosition, monkeyPatchHistoryPushState };
1
+ export { enhanceHistoryState, getHistoryState, pushHistory, saveScrollPosition, monkeyPatchHistoryPushState };
2
2
  import { assert, assertUsage, hasProp, isObject } from './utils.js';
3
- // Fill missing state information:
4
- // - `history.state` can uninitialized (i.e. `null`):
5
- // - The very first render
6
- // - The user's code runs `location.hash = '#section'`
7
- // - The user clicks on an anchor link `<a href="#section">Section</a>` (Vike's `initOnLinkClick()` handler skips hash links).
8
- // - State information may be incomplete if `history.state` is set by an old Vike version. (E.g. `state.timestamp` was introduced for `pageContext.isBackwardNavigation` in `0.4.19`.)
9
- function initHistoryState() {
10
- // No way found to add TypeScript types to `window.history.state`: https://github.com/microsoft/TypeScript/issues/36178
11
- let state = window.history.state;
12
- if (!state) {
13
- state = { _isVikeEnhanced: true };
14
- }
15
- let hasModifications = false;
16
- if (!('timestamp' in state)) {
17
- hasModifications = true;
18
- state.timestamp = getTimestamp();
19
- }
20
- if (!('scrollPosition' in state)) {
21
- hasModifications = true;
22
- state.scrollPosition = getScrollPosition();
23
- }
24
- if (!('triggeredBy' in state)) {
25
- state.triggeredBy = 'browser';
3
+ // `window.history.state === null` when:
4
+ // - The very first render
5
+ // - Click on `<a href="#some-hash" />`
6
+ // - `location.hash = 'some-hash'`
7
+ function enhanceHistoryState() {
8
+ const stateNotInitialized = window.history.state;
9
+ if (isVikeEnhanced(stateNotInitialized))
10
+ return;
11
+ const stateVikeEnhanced = enhance(stateNotInitialized);
12
+ replaceHistoryState(stateVikeEnhanced);
13
+ }
14
+ function enhance(stateNotInitialized) {
15
+ const timestamp = getTimestamp();
16
+ const scrollPosition = getScrollPosition();
17
+ const triggeredBy = 'browser';
18
+ let stateVikeEnhanced;
19
+ if (!stateNotInitialized) {
20
+ stateVikeEnhanced = {
21
+ timestamp,
22
+ scrollPosition,
23
+ triggeredBy,
24
+ _isVikeEnhanced: true
25
+ };
26
26
  }
27
- assertState(state);
28
- if (hasModifications) {
29
- replaceHistoryState(state);
27
+ else {
28
+ // State information may be incomplete if `window.history.state` is set by an old Vike version. (E.g. `state.timestamp` was introduced for `pageContext.isBackwardNavigation` in `0.4.19`.)
29
+ stateVikeEnhanced = {
30
+ timestamp: stateNotInitialized.timestamp ?? timestamp,
31
+ scrollPosition: stateNotInitialized.scrollPosition ?? scrollPosition,
32
+ triggeredBy: stateNotInitialized.triggeredBy ?? triggeredBy,
33
+ _isVikeEnhanced: true
34
+ };
30
35
  }
36
+ assert(isVikeEnhanced(stateVikeEnhanced));
37
+ return stateVikeEnhanced;
38
+ }
39
+ function getState_alreadyEnhanced() {
40
+ const state = getHistoryState();
41
+ assert(isVikeEnhanced(state));
42
+ return state;
31
43
  }
32
44
  function getHistoryState() {
33
- const state = window.history.state || {};
34
- assertState(state);
45
+ const state = window.history.state;
35
46
  return state;
36
47
  }
37
48
  function getScrollPosition() {
@@ -43,29 +54,22 @@ function getTimestamp() {
43
54
  }
44
55
  function saveScrollPosition() {
45
56
  const scrollPosition = getScrollPosition();
46
- const state = getHistoryState();
57
+ const state = getState_alreadyEnhanced();
47
58
  replaceHistoryState({ ...state, scrollPosition });
48
59
  }
49
60
  function pushHistory(url, overwriteLastHistoryEntry) {
50
61
  if (!overwriteLastHistoryEntry) {
51
62
  const timestamp = getTimestamp();
52
- pushHistoryState({ timestamp, scrollPosition: null, triggeredBy: 'vike', _isVikeEnhanced: true }, url);
63
+ pushHistoryState({
64
+ timestamp,
65
+ // I don't remember why I set it to `null`, maybe because setting it now would be too early? (Maybe there is a delay between renderPageClientSide() is finished and the browser updating the scroll position.) Anyways, it seems like autoSaveScrollPosition() is enough.
66
+ scrollPosition: null,
67
+ triggeredBy: 'vike',
68
+ _isVikeEnhanced: true
69
+ }, url);
53
70
  }
54
71
  else {
55
- replaceHistoryState(getHistoryState(), url);
56
- }
57
- }
58
- function assertState(state) {
59
- assert(isObject(state));
60
- if ('timestamp' in state) {
61
- const { timestamp } = state;
62
- assert(typeof timestamp === 'number');
63
- }
64
- if ('scrollPosition' in state) {
65
- const { scrollPosition } = state;
66
- if (scrollPosition !== null) {
67
- assert(hasProp(scrollPosition, 'x', 'number') && hasProp(scrollPosition, 'y', 'number'));
68
- }
72
+ replaceHistoryState(getState_alreadyEnhanced(), url);
69
73
  }
70
74
  }
71
75
  function replaceHistoryState(state, url) {
@@ -89,9 +93,24 @@ function monkeyPatchHistoryPushState() {
89
93
  triggeredBy: 'user',
90
94
  ...stateOriginal
91
95
  };
96
+ assert(isVikeEnhanced(stateEnhanced));
92
97
  return pushStateOriginal(stateEnhanced, ...rest);
93
98
  };
94
99
  }
95
100
  function isVikeEnhanced(state) {
96
- return isObject(state) && '_isVikeEnhanced' in state;
101
+ const yes = isObject(state) && '_isVikeEnhanced' in state;
102
+ if (yes)
103
+ assertStateVikeEnhanced(state);
104
+ return yes;
105
+ }
106
+ function assertStateVikeEnhanced(state) {
107
+ assert(isObject(state));
108
+ assert(hasProp(state, '_isVikeEnhanced', 'true'));
109
+ // TODO/eventually: remove assert() below to save client-side KBs
110
+ assert(hasProp(state, 'timestamp', 'number'));
111
+ assert(hasProp(state, 'scrollPosition'));
112
+ if (state.scrollPosition !== null) {
113
+ assert(hasProp(state, 'scrollPosition', 'object'));
114
+ assert(hasProp(state.scrollPosition, 'x', 'number') && hasProp(state.scrollPosition, 'y', 'number'));
115
+ }
97
116
  }
@@ -1,5 +1,6 @@
1
1
  export { navigate, reload } from './navigate.js';
2
2
  export { prefetch } from './prefetch.js';
3
+ export { onPopState } from './initOnPopState.js';
3
4
  export { PROJECT_VERSION as version } from './utils.js';
4
5
  import type { PageContextBuiltInClientWithClientRouting } from '../../shared/types.js';
5
6
  /** @deprecated
@@ -5,4 +5,5 @@
5
5
  // Use package.json#exports to make the imports isomorphic.
6
6
  export { navigate, reload } from './navigate.js';
7
7
  export { prefetch } from './prefetch.js';
8
+ export { onPopState } from './initOnPopState.js';
8
9
  export { PROJECT_VERSION as version } from './utils.js';
@@ -1,8 +1,8 @@
1
1
  export { initClientRouter };
2
2
  import { assert } from './utils.js';
3
- import { initHistoryState, monkeyPatchHistoryPushState } from './history.js';
3
+ import { enhanceHistoryState, monkeyPatchHistoryPushState } from './history.js';
4
4
  import { getRenderCount, renderPageClientSide } from './renderPageClientSide.js';
5
- import { onBrowserHistoryNavigation } from './onBrowserHistoryNavigation.js';
5
+ import { initOnPopState } from './initOnPopState.js';
6
6
  import { initOnLinkClick } from './initOnLinkClick.js';
7
7
  import { setupNativeScrollRestoration } from './scrollRestoration.js';
8
8
  import { autoSaveScrollPosition } from './setScrollPosition.js';
@@ -29,9 +29,9 @@ async function renderFirstPage() {
29
29
  }
30
30
  function initHistoryAndScroll() {
31
31
  setupNativeScrollRestoration();
32
- initHistoryState();
32
+ enhanceHistoryState();
33
33
  autoSaveScrollPosition();
34
34
  monkeyPatchHistoryPushState();
35
35
  // Handle back-/forward navigation
36
- onBrowserHistoryNavigation();
36
+ initOnPopState();
37
37
  }
@@ -0,0 +1,29 @@
1
+ export { initOnPopState };
2
+ export { updateState };
3
+ export { onPopState };
4
+ declare function initOnPopState(): void;
5
+ type Listener = (arg: {
6
+ _experimental: {
7
+ previousState: ReturnType<typeof getState>;
8
+ };
9
+ }) => undefined | boolean;
10
+ /** Control client-side navigation.
11
+ *
12
+ * https://vike.dev/onPopState
13
+ */
14
+ declare function onPopState(listener: Listener): void;
15
+ declare function getState(): {
16
+ urlWithoutHash: string;
17
+ historyState: {
18
+ timestamp: number;
19
+ scrollPosition: null | import("./history.js").ScrollPosition;
20
+ triggeredBy: "user" | "vike" | "browser";
21
+ _isVikeEnhanced: true;
22
+ } | Partial<{
23
+ timestamp: number;
24
+ scrollPosition: null | import("./history.js").ScrollPosition;
25
+ triggeredBy: "user" | "vike" | "browser";
26
+ _isVikeEnhanced: true;
27
+ }> | null | undefined;
28
+ };
29
+ declare function updateState(): void;
@@ -0,0 +1,79 @@
1
+ export { initOnPopState };
2
+ export { updateState };
3
+ export { onPopState };
4
+ import { assert, getCurrentUrl, getGlobalObject } from './utils.js';
5
+ import { enhanceHistoryState, getHistoryState } from './history.js';
6
+ import { renderPageClientSide } from './renderPageClientSide.js';
7
+ import { setScrollPosition } from './setScrollPosition.js';
8
+ const globalObject = getGlobalObject('initOnPopState.ts', { previousState: getState(), listeners: [] });
9
+ function initOnPopState() {
10
+ // - The popstate event is trigged upon:
11
+ // - Back-/forward navigation.
12
+ // - By user clicking on his browser's back-/forward navigation (or using a shortcut)
13
+ // - By JavaScript: `history.back()` / `history.forward()`
14
+ // - URL hash change.
15
+ // - Click on `<a href="#some-hash" />`
16
+ // - 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)
17
+ // - `location.hash = 'some-hash'`
18
+ // - 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
19
+ window.addEventListener('popstate', async () => {
20
+ const { previousState } = globalObject;
21
+ const currentState = getState();
22
+ globalObject.previousState = currentState;
23
+ const scrollTarget = currentState.historyState?.scrollPosition || undefined;
24
+ const isUserLandPushStateNavigation = currentState.historyState?.triggeredBy === 'user';
25
+ const isHashNavigation = currentState.urlWithoutHash === previousState.urlWithoutHash;
26
+ const isBackwardNavigation = !currentState.historyState?.timestamp || !previousState.historyState?.timestamp
27
+ ? null
28
+ : currentState.historyState.timestamp < previousState.historyState.timestamp;
29
+ // - `history.state === null` when:
30
+ // - Click on `<a href="#some-hash" />` (note that Vike's `initOnLinkClick()` handler skips hash links)
31
+ // - `location.hash = 'some-hash'`
32
+ // - `history.state !== null` when `popstate` was triggered by the user clicking on his browser's forward/backward history button.
33
+ let isHashNavigationNew = isHashNavigation && window.history.state === null;
34
+ if (window.history.state === null) {
35
+ assert(isHashNavigation);
36
+ // The browser already scrolled to `#${hash}` => the current scroll position is the right one => we save it with `enhanceHistoryState()`.
37
+ enhanceHistoryState();
38
+ globalObject.previousState = getState();
39
+ }
40
+ // 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:
41
+ // - Alternative: we use `window.history.scrollRestoration = 'auto'`
42
+ // - Problem: I don't think it's possbible to set `window.history.scrollRestoration = 'auto'` only for hash navigation and not for non-hash navigations?
43
+ // - 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.
44
+ // - Specification: https://html.spec.whatwg.org/multipage/history.html#the-history-interface
45
+ // - Practice: https://stackoverflow.com/questions/70188241/history-scrollrestoration-manual-doesnt-prevent-safari-from-restoring-scrol
46
+ // - Alternative: we completely take over hash navigation and reproduce the browser's native behavior upon hash navigation.
47
+ // - By using the `hashchange` event.
48
+ // - 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.
49
+ if (isHashNavigation && !isUserLandPushStateNavigation) {
50
+ if (!isHashNavigationNew) {
51
+ setScrollPosition(scrollTarget);
52
+ }
53
+ return;
54
+ }
55
+ let abort;
56
+ globalObject.listeners.forEach(listener => {
57
+ abort || (abort = listener({ _experimental: { previousState } }));
58
+ });
59
+ if (abort)
60
+ return;
61
+ await renderPageClientSide({ scrollTarget, isBackwardNavigation, isUserLandPushStateNavigation });
62
+ });
63
+ }
64
+ /** Control client-side navigation.
65
+ *
66
+ * https://vike.dev/onPopState
67
+ */
68
+ function onPopState(listener) {
69
+ globalObject.listeners.push(listener);
70
+ }
71
+ function getState() {
72
+ return {
73
+ urlWithoutHash: getCurrentUrl({ withoutHash: true }),
74
+ historyState: getHistoryState()
75
+ };
76
+ }
77
+ function updateState() {
78
+ globalObject.previousState = getState();
79
+ }
@@ -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 { updateState } from './onBrowserHistoryNavigation.js';
18
+ import { updateState } from './initOnPopState.js';
19
19
  import { browserNativeScrollRestoration_disable, setInitialRenderIsDone } from './scrollRestoration.js';
20
20
  import { getErrorPageId } from '../../shared/error-page.js';
21
21
  import { setPageContextCurrent } from './getPageContextCurrent.js';
@@ -83,7 +83,7 @@ async function transpileWithEsbuild(filePath, userRootDir, transformImports) {
83
83
  metafile: true,
84
84
  bundle: true
85
85
  };
86
- let pointerImports = {};
86
+ const pointerImports = {};
87
87
  options.plugins = [
88
88
  // Determine whether an import should be:
89
89
  // - A pointer import
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: "0.4.198-commit-77f6a27";
1
+ export declare const PROJECT_VERSION: "0.4.199-commit-3be497f";
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.198-commit-77f6a27';
2
+ export const PROJECT_VERSION = '0.4.199-commit-3be497f';
@@ -1,4 +1,4 @@
1
1
  export declare const projectInfo: {
2
2
  projectName: "Vike";
3
- projectVersion: "0.4.198-commit-77f6a27";
3
+ projectVersion: "0.4.199-commit-3be497f";
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.198-commit-77f6a27",
3
+ "version": "0.4.199-commit-3be497f",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {
@@ -1,4 +0,0 @@
1
- export { onBrowserHistoryNavigation };
2
- export { updateState };
3
- declare function onBrowserHistoryNavigation(): void;
4
- declare function updateState(): void;
@@ -1,63 +0,0 @@
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` 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 currentState = getState();
20
- const scrollTarget = currentState.historyState.scrollPosition || undefined;
21
- const isUserLandPushStateNavigation = currentState.historyState.triggeredBy === '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 `initOnLinkClick()` 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
- await 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
- }