vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
61 lines (60 loc) • 3.86 kB
JavaScript
export { initOnPopState };
import { onPopStateBegin } from './history.js';
import { renderPageClientSide } from './renderPageClientSide.js';
import { setScrollPosition } from './setScrollPosition.js';
import { catchInfiniteLoop } from './utils.js';
// The 'popstate' event is triggered when the browser doesn't fully load the new URL, for example:
// - `location.hash='#foo'` triggers the popstate event while `location.href='/foo'` doesn't.
// - 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.
// Concretely, 'popstate' is fired when:
// 1. Back-/forward navigation:
// - By the user using the browser's back-/forward navigation
// - By the app using `history.back()` / `history.forward()` / `history.go()`
// > Except of history entries triggered by Server Routing, see comment above.
// 2. URL hash changes:
// - By the user clicking on `<a href="#some-hash">`
// - 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)
// - Vike doesn't intercept hash links (see `skipLink()`) and let's the browser handle them.
// - By the app using a `location` API such as `location.hash = 'some-hash'`
// - Only upon hash navigation: setting `location.href='/foo'` triggers a full page reload and no popstate event is fired.
// - Also upon `location.href='/foo#bar'` while the current URL is '/foo' (unlike <a> clicks).
// Notes:
// - The 'hashchange' event is fired after popstate, so we cannot use it to distinguish between hash and non-hash navigations.
// - It isn't possible to monkey patch the `location` APIs. (Chrome throws `TypeError: Cannot redefine property` when attempt to overwrite any `location` property.)
// - Text links aren't supported: https://github.com/vikejs/vike/issues/2114
// - docs/ is a good playground to test all this.
function initOnPopState() {
window.addEventListener('popstate', onPopState);
}
async function onPopState() {
catchInfiniteLoop('onPopState()');
const { isHistoryStateEnhanced, previous, current } = onPopStateBegin();
// - `isHistoryStateEnhanced===false` <=> new hash navigation:
// - Click on `<a href="#some-hash">`
// - Using the `location` API (only hash navigation, see comments above).
// - `isHistoryStateEnhanced===true` <=> back-/forward navigation (including back-/forward hash navigation).
// > 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.
if (!isHistoryStateEnhanced) {
// Let the browser handle it
return;
}
else {
await handleBackForwardNavigation(previous, current);
}
}
async function handleBackForwardNavigation(previous, current) {
const scrollTarget = current.state.scrollPosition || undefined;
const isHashNavigation = removeHash(current.url) === removeHash(previous.url) && current.url !== previous.url;
if (isHashNavigation) {
// We have to scroll ourselves because we have set `window.history.scrollRestoration = 'manual'`
setScrollPosition(scrollTarget);
return;
}
const isUserPushStateNavigation = current.state.triggeredBy === 'user' || previous.state.triggeredBy === 'user';
const doNotRenderIfSamePage = isUserPushStateNavigation;
const isBackwardNavigation = !current.state.timestamp || !previous.state.timestamp ? null : current.state.timestamp < previous.state.timestamp;
await renderPageClientSide({ scrollTarget, isBackwardNavigation, doNotRenderIfSamePage });
}
function removeHash(url) {
return url.split('#')[0];
}