@thomas.f/prnext 0.1.0-alpha.1

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 (211) hide show
  1. package/README.md +54 -0
  2. package/build/action-exports.mjs +107 -0
  3. package/build/actions.mjs +214 -0
  4. package/build/app-config.mjs +138 -0
  5. package/build/app-routing.mjs +119 -0
  6. package/build/app-static.mjs +158 -0
  7. package/build/app.mjs +536 -0
  8. package/build/cache-components.mjs +91 -0
  9. package/build/cache-handlers.mjs +54 -0
  10. package/build/check.mjs +40 -0
  11. package/build/compiler.mjs +222 -0
  12. package/build/config.mjs +193 -0
  13. package/build/css-modules.mjs +114 -0
  14. package/build/custom-routes.mjs +363 -0
  15. package/build/dev-worker.mjs +18 -0
  16. package/build/dev.mjs +76 -0
  17. package/build/directory-cache.mjs +29 -0
  18. package/build/dynamic.mjs +125 -0
  19. package/build/edge.mjs +141 -0
  20. package/build/font-data.LICENSE.txt +25 -0
  21. package/build/font-data.json +1 -0
  22. package/build/font-files.mjs +221 -0
  23. package/build/fonts.mjs +131 -0
  24. package/build/framework-imports.mjs +17 -0
  25. package/build/i18n.mjs +39 -0
  26. package/build/images-config.mjs +62 -0
  27. package/build/images.mjs +86 -0
  28. package/build/incremental.mjs +76 -0
  29. package/build/index.mjs +356 -0
  30. package/build/loader-cache.mjs +69 -0
  31. package/build/metadata-images.mjs +72 -0
  32. package/build/metadata-routes.mjs +77 -0
  33. package/build/middleware.mjs +231 -0
  34. package/build/migrate.mjs +289 -0
  35. package/build/module-resolution.mjs +78 -0
  36. package/build/native-discovery.mjs +134 -0
  37. package/build/native-packages.mjs +105 -0
  38. package/build/npm-packages.mjs +110 -0
  39. package/build/og.mjs +14 -0
  40. package/build/page-extensions.mjs +12 -0
  41. package/build/passthrough-loader.cjs +4 -0
  42. package/build/precompress.mjs +122 -0
  43. package/build/scan.mjs +178 -0
  44. package/build/script-workers.mjs +25 -0
  45. package/build/server-actions-config.mjs +27 -0
  46. package/build/source-maps.mjs +34 -0
  47. package/build/standalone-config.mjs +19 -0
  48. package/build/standalone-runner.mjs +21 -0
  49. package/build/standalone-worker.mjs +5 -0
  50. package/build/standalone.mjs +240 -0
  51. package/build/static-export.mjs +93 -0
  52. package/build/styles.mjs +174 -0
  53. package/build/transform-cache.mjs +50 -0
  54. package/build/transform.mjs +155 -0
  55. package/build/turbopack-conditions.mjs +59 -0
  56. package/build/webpack-bridge.cjs +3 -0
  57. package/build/webpack-graph.mjs +256 -0
  58. package/build/webpack-loader.cjs +9 -0
  59. package/cli.mjs +210 -0
  60. package/compat/app-client-page.cjs +32 -0
  61. package/compat/app-context.cjs +40 -0
  62. package/compat/app-context.d.cts +20 -0
  63. package/compat/app-dynamic.cjs +34 -0
  64. package/compat/app-error-boundary.cjs +53 -0
  65. package/compat/app-global-error-boundary.cjs +34 -0
  66. package/compat/app-layout-context.cjs +27 -0
  67. package/compat/app-navigation-boundary.cjs +47 -0
  68. package/compat/app.cjs +33 -0
  69. package/compat/app.d.cts +9 -0
  70. package/compat/cache-handlers.cjs +223 -0
  71. package/compat/cache-life.cjs +34 -0
  72. package/compat/cache.cjs +99 -0
  73. package/compat/cache.d.cts +36 -0
  74. package/compat/compat/router.d.cts +2 -0
  75. package/compat/compat-router.cjs +7 -0
  76. package/compat/constants.cjs +8 -0
  77. package/compat/constants.d.cts +7 -0
  78. package/compat/cookies.cjs +129 -0
  79. package/compat/data-cache.cjs +389 -0
  80. package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
  81. package/compat/dist/shared/lib/router-context.d.cts +1 -0
  82. package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
  83. package/compat/document.cjs +73 -0
  84. package/compat/document.d.cts +41 -0
  85. package/compat/draft.cjs +28 -0
  86. package/compat/dynamic-bailout.cjs +13 -0
  87. package/compat/dynamic.cjs +119 -0
  88. package/compat/dynamic.d.cts +32 -0
  89. package/compat/error.cjs +36 -0
  90. package/compat/error.d.cts +15 -0
  91. package/compat/fetch.d.cts +10 -0
  92. package/compat/font/google.d.cts +1 -0
  93. package/compat/font/local.d.cts +2 -0
  94. package/compat/font-google.cjs +2 -0
  95. package/compat/font-google.d.cts +1944 -0
  96. package/compat/font-local.cjs +5 -0
  97. package/compat/font-local.d.cts +3 -0
  98. package/compat/font-types.d.cts +25 -0
  99. package/compat/head.cjs +180 -0
  100. package/compat/head.d.cts +3 -0
  101. package/compat/headers.cjs +89 -0
  102. package/compat/headers.d.cts +26 -0
  103. package/compat/image-config.cjs +3 -0
  104. package/compat/image-imports.d.ts +12 -0
  105. package/compat/image-shared.cjs +62 -0
  106. package/compat/image.cjs +64 -0
  107. package/compat/image.d.cts +35 -0
  108. package/compat/incremental-cache.cjs +186 -0
  109. package/compat/index.d.ts +159 -0
  110. package/compat/instant-samples.cjs +39 -0
  111. package/compat/instant.cjs +13 -0
  112. package/compat/link.cjs +68 -0
  113. package/compat/link.d.cts +17 -0
  114. package/compat/locale.cjs +30 -0
  115. package/compat/metadata.d.cts +77 -0
  116. package/compat/navigation-controls.cjs +27 -0
  117. package/compat/navigation-server.cjs +25 -0
  118. package/compat/navigation.cjs +52 -0
  119. package/compat/navigation.d.cts +28 -0
  120. package/compat/next-app-router-context.cjs +6 -0
  121. package/compat/next-router-context.cjs +3 -0
  122. package/compat/og.cjs +32 -0
  123. package/compat/og.d.cts +1 -0
  124. package/compat/paths.cjs +33 -0
  125. package/compat/preview.cjs +41 -0
  126. package/compat/rewrite.cjs +41 -0
  127. package/compat/route-request.cjs +64 -0
  128. package/compat/router.cjs +137 -0
  129. package/compat/router.d.cts +58 -0
  130. package/compat/script-context.cjs +5 -0
  131. package/compat/script-loader.cjs +135 -0
  132. package/compat/script.cjs +69 -0
  133. package/compat/script.d.cts +17 -0
  134. package/compat/server-primitives.cjs +109 -0
  135. package/compat/server.cjs +13 -0
  136. package/compat/server.d.cts +37 -0
  137. package/compat/static-generation.cjs +108 -0
  138. package/compat/use-cache.cjs +129 -0
  139. package/index.mjs +1 -0
  140. package/native/platforms.json +6 -0
  141. package/native/resolve.mjs +81 -0
  142. package/package.json +127 -0
  143. package/release/verify.mjs +59 -0
  144. package/runtime/action-client.mjs +19 -0
  145. package/runtime/action-crypto.mjs +138 -0
  146. package/runtime/action-reference.mjs +2 -0
  147. package/runtime/action-server.mjs +161 -0
  148. package/runtime/action-ssr.mjs +5 -0
  149. package/runtime/api.mjs +326 -0
  150. package/runtime/app-client.mjs +539 -0
  151. package/runtime/app-content.mjs +8 -0
  152. package/runtime/app-errors.mjs +12 -0
  153. package/runtime/app-instant.mjs +30 -0
  154. package/runtime/app-metadata.mjs +410 -0
  155. package/runtime/app-partial-model.mjs +228 -0
  156. package/runtime/app-partial.mjs +130 -0
  157. package/runtime/app-prefetch.mjs +34 -0
  158. package/runtime/app-recovery.mjs +87 -0
  159. package/runtime/app-render.mjs +544 -0
  160. package/runtime/app-routing-access.mjs +143 -0
  161. package/runtime/app-routing-context.mjs +35 -0
  162. package/runtime/app-routing-state.mjs +61 -0
  163. package/runtime/app-routing.mjs +323 -0
  164. package/runtime/app-segments.mjs +22 -0
  165. package/runtime/app-static-params.mjs +120 -0
  166. package/runtime/app-static.mjs +86 -0
  167. package/runtime/app-stream-channel.mjs +92 -0
  168. package/runtime/app-stream-html.mjs +279 -0
  169. package/runtime/build-directory.mjs +48 -0
  170. package/runtime/cache-arguments.mjs +33 -0
  171. package/runtime/cache-components-rsc.mjs +64 -0
  172. package/runtime/cache-request.mjs +23 -0
  173. package/runtime/client-navigation.mjs +40 -0
  174. package/runtime/dev-client.mjs +129 -0
  175. package/runtime/dev-file-changes.mjs +110 -0
  176. package/runtime/document.mjs +96 -0
  177. package/runtime/edge.mjs +105 -0
  178. package/runtime/env.mjs +68 -0
  179. package/runtime/fetch-cache.mjs +379 -0
  180. package/runtime/font-preload.mjs +4 -0
  181. package/runtime/http.mjs +190 -0
  182. package/runtime/incremental-cache.mjs +83 -0
  183. package/runtime/metadata-route.mjs +83 -0
  184. package/runtime/middleware.mjs +186 -0
  185. package/runtime/module-loader.mjs +21 -0
  186. package/runtime/navigation.mjs +16 -0
  187. package/runtime/og-loader.mjs +17 -0
  188. package/runtime/pages-client.mjs +197 -0
  189. package/runtime/pages-error-props.mjs +6 -0
  190. package/runtime/pages-errors.mjs +85 -0
  191. package/runtime/pages-initial-props.mjs +43 -0
  192. package/runtime/pages-navigation.mjs +565 -0
  193. package/runtime/pages-paths.mjs +80 -0
  194. package/runtime/pages-revalidate.mjs +33 -0
  195. package/runtime/partial-artifact-cache.mjs +37 -0
  196. package/runtime/partial-template.mjs +174 -0
  197. package/runtime/profile.mjs +9 -0
  198. package/runtime/profiles.json +47 -0
  199. package/runtime/render.mjs +304 -0
  200. package/runtime/request-transport.mjs +41 -0
  201. package/runtime/request-work.mjs +7 -0
  202. package/runtime/route-static.mjs +117 -0
  203. package/runtime/rsc-worker.mjs +426 -0
  204. package/runtime/script-html.mjs +61 -0
  205. package/runtime/stream-select.mjs +25 -0
  206. package/runtime/stream-utils.mjs +127 -0
  207. package/runtime/transport.mjs +108 -0
  208. package/runtime/watch-project.mjs +130 -0
  209. package/runtime/worker-artifacts.mjs +22 -0
  210. package/runtime/worker-metadata.mjs +31 -0
  211. package/runtime/worker.mjs +197 -0
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import DefaultApp, { loadGetInitialProps } from '../compat/app.cjs';
3
+ import { RouterProvider, makeRouter } from '../compat/router.cjs';
4
+ import { HeadProvider } from '../compat/head.cjs';
5
+
6
+ export function applicationProps(data = {}) {
7
+ const { pageProps, __N_SSG, __N_SSP, __PRNEXT_ROUTER__, notFound, ...appProps } = data;
8
+ return appProps;
9
+ }
10
+
11
+ export async function loadPageInitialProps({ Page, App = DefaultApp, context, routerSnapshot, appTreePage = Page }) {
12
+ const router = makeRouter(routerSnapshot);
13
+ function AppTree({ pageProps = {}, ...appProps }) {
14
+ return React.createElement(RouterProvider, { router: routerSnapshot, managed: true },
15
+ React.createElement(HeadProvider, null,
16
+ React.createElement(App, { ...appProps, Component: appTreePage, pageProps, router })));
17
+ }
18
+ const result = await loadGetInitialProps(App, { Component: Page, router, AppTree, ctx: { ...context, AppTree } });
19
+ const { pageProps, ...appProps } = result;
20
+ return { props: { ...pageProps }, appProps };
21
+ }
22
+
23
+ function finishHook(slots, rejected, value) {
24
+ slots.pending--;
25
+ if (rejected) throw value;
26
+ return value;
27
+ }
28
+
29
+ export function createInitialPropsRunner({ limit = 8 } = {}) {
30
+ const slots = { pending: 0 };
31
+ return options => {
32
+ const appHook = options.App?.getInitialProps;
33
+ if ((!appHook || appHook === DefaultApp.origGetInitialProps) && typeof options.Page.getInitialProps !== 'function') {
34
+ return Promise.resolve({ props: {}, appProps: {} });
35
+ }
36
+ // A user promise cannot be forcibly cancelled. Count abandoned hooks until
37
+ // they settle, so repeated cancelled navigations cannot start unlimited
38
+ // unfinished hooks holding their contexts and application code.
39
+ if (slots.pending >= limit) return Promise.reject(new Error('Too many unfinished getInitialProps calls'));
40
+ slots.pending++;
41
+ return loadPageInitialProps(options).then(finishHook.bind(null, slots, false), finishHook.bind(null, slots, true));
42
+ };
43
+ }
@@ -0,0 +1,565 @@
1
+ import { localePath, localizedHref } from '../compat/locale.cjs';
2
+ import { formatUrl } from '../compat/router.cjs';
3
+ import { rewriteQuery } from '../compat/rewrite.cjs';
4
+ import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
5
+ import { publicNavigationURL, isApplicationURL } from './client-navigation.mjs';
6
+ import DefaultError from '../compat/error.cjs';
7
+ import { applicationProps, createInitialPropsRunner } from './pages-initial-props.mjs';
8
+
9
+ const PREFETCH_LIMIT = 8;
10
+ const PREFETCH_TTL = 30_000;
11
+ const ASSET_LIMIT = 2 * 1024 * 1024;
12
+ const PREFETCH_BYTES = 8 * 1024 * 1024;
13
+
14
+ export function abortable(promise, signal) {
15
+ return new Promise((resolve, reject) => {
16
+ const pending = { resolve, reject, signal };
17
+ pending.abort = abortOperation.bind(null, pending);
18
+ signal.addEventListener('abort', pending.abort, { once: true });
19
+ if (signal.aborted) abortOperation(pending);
20
+ Promise.resolve(promise).then(finishOperation.bind(null, pending, false), finishOperation.bind(null, pending, true));
21
+ });
22
+ }
23
+
24
+ function abortOperation(pending) {
25
+ finishOperation(pending, true, pending.signal?.reason || Object.assign(new Error('Route cancelled'), { cancelled: true }));
26
+ }
27
+ function finishOperation(pending, rejected, value) {
28
+ if (!pending.resolve) return;
29
+ const { resolve, reject, signal, abort } = pending;
30
+ signal.removeEventListener('abort', abort);
31
+ // User hooks and import() can remain pending after cancellation. Their
32
+ // remaining callbacks retain this empty cell, not the navigation's promises,
33
+ // AbortSignal or accumulated page data.
34
+ pending.resolve = pending.reject = pending.signal = pending.abort = undefined;
35
+ rejected ? reject(value) : resolve(value);
36
+ }
37
+
38
+ export function queryFromURL(url) {
39
+ const query = Object.create(null);
40
+ for (const [key, value] of url.searchParams) {
41
+ if (Object.hasOwn(query, key)) query[key] = [...(Array.isArray(query[key]) ? query[key] : [query[key]]), value];
42
+ else query[key] = value;
43
+ }
44
+ return query;
45
+ }
46
+
47
+ export function pageRouterSnapshot(router, requestURL, visibleURL, rewrite) {
48
+ const domain = router.i18n?.domains?.find(value=>value.domain.toLowerCase()===visibleURL.host.toLowerCase());
49
+ return { ...router, ...(router.i18n ? {domain:visibleURL.host,domainLocales:router.i18n.domains,defaultLocale:domain?.defaultLocale || router.i18n.defaultLocale,isLocaleDomain:!!domain} : {}), query: { ...(rewrite ? rewriteQuery(rewrite) : queryFromURL(requestURL)), ...router.query },
50
+ asPath: localePath(removeBasePath(visibleURL.pathname, router.basePath || ''), router.i18n).pathname + visibleURL.search + visibleURL.hash, isReady: true, isFallback: !!router.isFallback,
51
+ ...(rewrite ? { rewrite } : {}) };
52
+ }
53
+
54
+ export function matchPagePattern(pattern, pathname) {
55
+ let segments;
56
+ try { segments = pathname.replace(/\/+$/, '').split('/').slice(1).map(decodeURIComponent); }
57
+ catch { return null; }
58
+ if (segments.length === 1 && !segments[0]) segments = [];
59
+ if (segments.some(segment => !segment || /[/\\\0]/.test(segment) || segment === '.' || segment === '..')) return null;
60
+ const parts = pattern === '/' ? [] : pattern.split('/').slice(1), params = Object.create(null), rank = [];
61
+ let index = 0;
62
+ for (const part of parts) {
63
+ const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(part);
64
+ const catchall = /^\[\.\.\.([^\]]+)\]$/.exec(part);
65
+ const parameter = /^\[([^\]]+)\]$/.exec(part);
66
+ if (optional || catchall) {
67
+ if (catchall && index === segments.length) return null;
68
+ if (index !== segments.length) params[(optional || catchall)[1]] = segments.slice(index);
69
+ index = segments.length; rank.push(optional ? 0 : 1);
70
+ } else if (parameter) {
71
+ if (index === segments.length) return null;
72
+ params[parameter[1]] = segments[index++]; rank.push(2);
73
+ } else {
74
+ if (segments[index++] !== part) return null;
75
+ rank.push(3);
76
+ }
77
+ }
78
+ return index === segments.length ? { params, rank } : null;
79
+ }
80
+
81
+ export function resolvePageRoute(manifest, pathname) {
82
+ let winner;
83
+ for (const [routes, pages] of [[manifest.routes, true], [manifest.nonPagesRoutes || [], false]]) {
84
+ for (const route of routes) {
85
+ const match = matchPagePattern(route.pattern, pathname);
86
+ if (!match) continue;
87
+ const compare = winner && (() => {
88
+ for (let i = 0; i < Math.max(match.rank.length, winner.rank.length); i++) {
89
+ // An exact endpoint wins over an optional catch-all with no segment.
90
+ const difference = (match.rank[i] ?? 4) - (winner.rank[i] ?? 4);
91
+ if (difference) return difference;
92
+ }
93
+ return 0;
94
+ })();
95
+ if (!winner || compare > 0) winner = { ...match, route, pages };
96
+ }
97
+ }
98
+ return winner;
99
+ }
100
+
101
+ async function consume(response, limit) {
102
+ if (!response.ok || !response.body) { await response.body?.cancel(); throw new Error('Unable to load a page asset'); }
103
+ const reader = response.body.getReader(), chunks = []; let size = 0;
104
+ try {
105
+ for (;;) {
106
+ const { value, done } = await reader.read(); if (done) break;
107
+ size += value.byteLength; if (size > limit) throw new Error('Page asset exceeds the response limit'); chunks.push(value);
108
+ }
109
+ const bytes = new Uint8Array(size); let offset = 0;
110
+ for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
111
+ return new TextDecoder().decode(bytes);
112
+ } catch (error) { await reader.cancel(error).catch(() => {}); throw error; }
113
+ finally { reader.releaseLock(); }
114
+ }
115
+
116
+ export function createPagesNavigation({ initial, initialRoute, manifestUrl, readData, commit, events,
117
+ basePath = initial.router.basePath || '', trailingSlash = initial.router.trailingSlash || false, skipTrailingSlashRedirect = initial.router.skipTrailingSlashRedirect || false, assetBase = `${basePath}/_prnext/assets`,
118
+ window: win = window, document: doc = document, fetch: fetcher = fetch, importModule = source => import(/* webpackIgnore: true */ source) }) {
119
+ basePath = normalizeBasePath(basePath);
120
+ const policy = { trailingSlash, skipTrailingSlashRedirect };
121
+ const assets = new URL(assetBase, win.location.href);
122
+ if (!['http:', 'https:'].includes(assets.protocol) || assets.username || assets.password || assets.search || assets.hash) throw new Error('Invalid page asset base');
123
+ let currentURL = new URL(win.location.href), currentRequest = new URL(currentURL), current = { ...initial, route: initialRoute, clientSnapshot: true };
124
+ current.router = pageRouterSnapshot({ ...initial.router, basePath, ...policy }, currentRequest, currentURL, initial.rewrite);
125
+ let buildId = initial.buildId;
126
+ const appTreePage = initial.Page, runInitialProps = createInitialPropsRunner();
127
+ const initialErrorContext = { pathname: initial.router.pathname, query: initial.router.query,
128
+ asPath: removeBasePath(currentURL.pathname, basePath) + currentURL.search + currentURL.hash };
129
+ initial = undefined;
130
+ let manifestPromise, active, renderingError, sequence = 0, beforePop = () => true;
131
+ let currentKey = win.history.state?.__prnextPages?.key || 0, nextKey = currentKey + 1;
132
+ const prefetches = new Map(), queue = []; let prefetchRunning = 0, prefetchBytes = 0;
133
+
134
+ function assetURL(value) {
135
+ const url = new URL(value, win.location.href);
136
+ if (url.origin !== assets.origin || !url.pathname.startsWith(`${assets.pathname.replace(/\/$/, '')}/`) || url.username || url.password) throw new Error('Invalid page asset URL');
137
+ return url.href;
138
+ }
139
+ function fetchAsset(value, signal) {
140
+ const url = assetURL(value);
141
+ return fetcher(url, { signal, mode: 'cors', credentials: new URL(url).origin === win.location.origin ? 'same-origin' : 'omit' });
142
+ }
143
+ const internalPath = url => removeBasePath(url.pathname, basePath);
144
+ async function manifest() {
145
+ manifestPromise ||= (async () => {
146
+ if (!manifestUrl) throw new Error('This build does not contain a Pages navigation manifest');
147
+ const abort = new AbortController(), timer = setTimeout(() => abort.abort(), 30_000);
148
+ try {
149
+ const value = JSON.parse(await consume(await fetchAsset(manifestUrl, abort.signal), ASSET_LIMIT));
150
+ if (value.buildId !== buildId || !Array.isArray(value.routes) || value.routes.length > 10_000) throw new Error('Invalid Pages manifest');
151
+ for (const route of [...value.routes, ...Object.values(value.errors || {}).filter(Boolean)]) {
152
+ if (typeof route.pattern !== 'string' || !Array.isArray(route.css)) throw new Error('Invalid page route');
153
+ assetURL(route.client); for (const css of route.css) assetURL(css);
154
+ }
155
+ return value;
156
+ } finally { clearTimeout(timer); }
157
+ })().catch(error => { manifestPromise = undefined; throw error; });
158
+ return manifestPromise;
159
+ }
160
+ function targetURL(value) {
161
+ return publicNavigationURL(value instanceof URL ? value : formatUrl(value), currentURL, basePath, policy);
162
+ }
163
+ function redirectURL(decoded, visible) {
164
+ if (decoded.redirect) return new URL(decoded.redirect, visible);
165
+ const props = decoded.data?.pageProps;
166
+ if (!props?.__N_REDIRECT) return null;
167
+ return props.__N_REDIRECT_BASE_PATH === false ? new URL(props.__N_REDIRECT, visible) : publicNavigationURL(props.__N_REDIRECT, visible, basePath, policy);
168
+ }
169
+ function targets(url, as, options = {}) {
170
+ url = localizedHref(formatUrl(url), current.router, options.locale);
171
+ if (as) as = localizedHref(formatUrl(as), current.router, options.locale);
172
+ let request = targetURL(url), destination = as ? targetURL(as) : request;
173
+ if (/\[[^/]+\]/.test(request.pathname)) {
174
+ const params = as ? matchPagePattern(request.pathname, destination.pathname)?.params : {};
175
+ const query = { ...params, ...queryFromURL(request) };
176
+ const interpolated = formatUrl({ pathname: request.pathname, query, hash: request.hash });
177
+ request = new URL(interpolated, request);
178
+ if (/\[[^/]+\]/.test(request.pathname)) throw new Error('Missing parameters for a dynamic route');
179
+ if (!as) destination = request;
180
+ }
181
+ return { request, destination };
182
+ }
183
+ function historyState(url, request, key, options, scroll) {
184
+ return { ...win.history.state, __prnextPages: { key, url: request.pathname + request.search + request.hash, as: url.pathname + url.search + url.hash, options, scroll } };
185
+ }
186
+ function saveScroll() {
187
+ if (win.location.href === currentURL.href) win.history.replaceState(historyState(currentURL, currentRequest, currentKey,
188
+ win.history.state?.__prnextPages?.options || {}, [win.scrollX, win.scrollY]), '', currentURL.href);
189
+ }
190
+ function hard(url, replace) { win.location[replace ? 'replace' : 'assign'](url.href); }
191
+ function scrollTo(url, saved) {
192
+ if (saved) { win.scrollTo(...saved); return; }
193
+ if (url.hash) {
194
+ let id = url.hash.slice(1); try { id = decodeURIComponent(id); } catch { /* Literal malformed fragment. */ }
195
+ const element = doc.getElementById(id) || doc.getElementsByName(id)[0];
196
+ if (element) { element.scrollIntoView(); return; }
197
+ if (id && id !== 'top') return;
198
+ }
199
+ win.scrollTo(0, 0);
200
+ }
201
+ async function loadCode(route, signal) {
202
+ const module = await abortable(importModule(assetURL(route.client)), signal);
203
+ if (!module.Page) throw new Error('Invalid page module');
204
+ return module;
205
+ }
206
+ async function loadStyles(route, signal) {
207
+ const added = [], pending = new Set();
208
+ for (const font of route.fonts || []) {
209
+ const href = assetURL(font.href);
210
+ if ([...doc.querySelectorAll('link[rel="preload"][as="font"]')].some(link => link.href === href)) continue;
211
+ const link = doc.createElement('link');
212
+ link.rel = 'preload'; link.as = 'font'; link.href = href; link.type = font.type; link.crossOrigin = 'anonymous';
213
+ doc.head.appendChild(link);
214
+ }
215
+ try {
216
+ await Promise.all(route.css.map(source => {
217
+ const href = assetURL(source);
218
+ if ([...doc.querySelectorAll('link[rel="stylesheet"]')].some(link => link.href === href)) return;
219
+ return new Promise((resolve, reject) => {
220
+ const link = doc.createElement('link'); link.rel = 'stylesheet'; link.href = href; link.media = 'not all'; added.push(link);
221
+ if (new URL(href).origin !== win.location.origin) link.crossOrigin = 'anonymous';
222
+ const timer = setTimeout(() => finish(new Error('Page stylesheet timed out')), 30_000);
223
+ const abort = () => finish(signal.reason || new Error('Page navigation cancelled'));
224
+ function finish(error) { pending.delete(abort); clearTimeout(timer); signal.removeEventListener('abort', abort); link.onload = link.onerror = null; error ? reject(error) : resolve(); }
225
+ link.onload = () => finish(); link.onerror = () => finish(new Error('Unable to load page stylesheet'));
226
+ signal.addEventListener('abort', abort, { once: true });
227
+ pending.add(abort);
228
+ if (signal.aborted) abort(); else doc.head.appendChild(link);
229
+ });
230
+ }));
231
+ return { remove() { for (const link of added) link.remove(); }, activate() { for (const link of added) link.media = ''; } };
232
+ } catch (error) { for (const cancel of pending) cancel(); for (const link of added) link.remove(); throw error; }
233
+ }
234
+ async function requestData(url, signal, speculative = false) {
235
+ return readData(url, signal, { speculative, fetcher });
236
+ }
237
+ function initialProps(module, context, signal, routerSnapshot = current.router) {
238
+ return abortable(runInitialProps({ Page: module.Page, App: module.App, context,
239
+ routerSnapshot, appTreePage }), signal);
240
+ }
241
+ async function loadErrorPage(map, router, destination, signal, { notFound = false, err = null, context, forceDefault = false, tolerateHookFailure = false } = {}) {
242
+ const errors = map.localizedErrors?.[router.locale] || map.errors;
243
+ const route = !forceDefault && (notFound ? errors?.notFound || errors?.error : errors?.error);
244
+ const module = route ? await loadCode(route, signal) : { Page: DefaultError, App: forceDefault ? undefined : current.App };
245
+ let result;
246
+ if (notFound && route && route === errors?.notFound) {
247
+ if (route.ssg) {
248
+ const decoded = await abortable(requestData(publicNavigationURL(route.pattern, destination, basePath, policy), signal), signal);
249
+ const props = decoded.data?.pageProps;
250
+ if (!props || typeof props !== 'object' || Array.isArray(props)) throw new Error('Invalid custom 404 data');
251
+ result = { props, appProps: applicationProps(decoded.data) };
252
+ } else result = await initialProps(module, { err: null, pathname: route.originalPattern || route.pattern,
253
+ asPath: destination.pathname + destination.search + destination.hash, query: router.query }, signal);
254
+ } else {
255
+ try {
256
+ result = await initialProps(module, context || {
257
+ err, pathname: notFound ? '/_error' : router.pathname,
258
+ asPath: notFound ? destination.pathname + destination.search + destination.hash : router.asPath,
259
+ query: router.query,
260
+ }, signal);
261
+ } catch (error) {
262
+ if (!tolerateHookFailure || signal.aborted) throw error;
263
+ result = { props: {}, appProps: {} };
264
+ }
265
+ }
266
+ return { Page: module.Page, App: module.App, ...result, assetRoute: route || { css: [] }, errorKind: notFound ? 'notFound' : 'client', notFound };
267
+ }
268
+ function retireStyles(previous, next) {
269
+ if (!previous) return;
270
+ for (const css of previous.css || []) {
271
+ if (next?.css?.includes(css)) continue;
272
+ for (const link of doc.querySelectorAll('link[rel="stylesheet"]')) if (link.href === assetURL(css)) link.remove();
273
+ }
274
+ }
275
+ function dropPrefetch(key, abort = true) {
276
+ const entry = prefetches.get(key); if (!entry) return;
277
+ prefetches.delete(key); clearTimeout(entry.timer);
278
+ prefetchBytes -= entry.bytes || 0; entry.bytes = 0;
279
+ if (abort) entry.abort.abort(); else entry.consumed = true;
280
+ }
281
+ function pumpPrefetch() {
282
+ while (prefetchRunning < 2 && queue.length) {
283
+ const entry = queue.shift();
284
+ if (entry.abort.signal.aborted) { entry.resolve(); continue; }
285
+ prefetchRunning++;
286
+ (async () => {
287
+ const map = await abortable(manifest(), entry.abort.signal), matched = resolvePageRoute(map, internalPath(entry.request));
288
+ if (!matched?.pages) return;
289
+ // Fetch rather than evaluate speculative modules: fetch is cancellable,
290
+ // whereas the browser's import() cannot cancel a blocked module request.
291
+ await consume(await fetchAsset(matched.route.client, entry.abort.signal), ASSET_LIMIT);
292
+ if (entry.abort.signal.aborted) return;
293
+ // Warm browser HTTP caches without applying another page's CSS early.
294
+ for (const css of matched.route.css) await consume(await fetchAsset(css, entry.abort.signal), ASSET_LIMIT);
295
+ if (matched.route.ssg) {
296
+ const decoded = await requestData(entry.request, entry.abort.signal, true);
297
+ if (entry.abort.signal.aborted || (!prefetches.has(entry.key) && !entry.consumed) || decoded.cacheable === false) return;
298
+ entry.data = decoded;
299
+ if (entry.consumed) return;
300
+ entry.bytes = decoded.bytes || 0; prefetchBytes += entry.bytes;
301
+ while (prefetchBytes > PREFETCH_BYTES) dropPrefetch(prefetches.keys().next().value);
302
+ }
303
+ })().catch(() => { dropPrefetch(entry.key); }).finally(() => { prefetchRunning--; entry.resolve(); pumpPrefetch(); });
304
+ }
305
+ }
306
+ function prefetch(url, as, options) {
307
+ let request;
308
+ try { ({ request } = targets(url, as, options)); } catch { return Promise.resolve(); }
309
+ if (!isApplicationURL(request, win.location.origin, basePath) || process.env.NODE_ENV !== 'production') return Promise.resolve();
310
+ request.hash = ''; const key = request.href;
311
+ if (prefetches.has(key)) return prefetches.get(key).promise;
312
+ while (prefetches.size >= PREFETCH_LIMIT) dropPrefetch(prefetches.keys().next().value);
313
+ // Queued cancelled entries must not grow with an unbounded list of hovered links.
314
+ for (let index = queue.length - 1; index >= 0; index--) if (queue[index].abort.signal.aborted) { queue[index].resolve(); queue.splice(index, 1); }
315
+ const entry = { key, request, abort: new AbortController() };
316
+ entry.promise = new Promise(resolve => { entry.resolve = resolve; });
317
+ entry.timer = setTimeout(() => dropPrefetch(key), PREFETCH_TTL);
318
+ prefetches.set(key, entry); queue.push(entry); pumpPrefetch(); return entry.promise;
319
+ }
320
+ function cancelActive() {
321
+ if (!active) return;
322
+ active.abort.abort();
323
+ if (!active.finished) {
324
+ active.finished = true;
325
+ if (!active.silent) events.emit('routeChangeError', Object.assign(new Error('Route change cancelled'), { cancelled: true }), active.asPath, { shallow: active.shallow });
326
+ }
327
+ }
328
+ async function navigate(url, as, options = {}) {
329
+ let { request, destination } = targets(url, as, options);
330
+ if (!isApplicationURL(destination, win.location.origin, basePath) || !isApplicationURL(request, win.location.origin, basePath)) { hard(destination, options.replace); return true; }
331
+ cancelActive(); renderingError?.abort.abort(); renderingError = undefined; saveScroll();
332
+ const job = { id: ++sequence, abort: new AbortController(), asPath: destination.pathname + destination.search + destination.hash, shallow: !!options.shallow };
333
+ active = job;
334
+ const timer = setTimeout(() => job.abort.abort(new Error('Page navigation timed out')), 30_000);
335
+ const hashOnly = destination.pathname === currentURL.pathname && destination.search === currentURL.search &&
336
+ (destination.hash !== currentURL.hash || !!destination.hash) && !options.shallow;
337
+ const startEvent = hashOnly ? 'hashChangeStart' : 'routeChangeStart';
338
+ let releaseStyles;
339
+ const isCurrent = () => active === job && !job.abort.signal.aborted;
340
+ try {
341
+ events.emit(startEvent, job.asPath, { shallow: job.shallow });
342
+ const map = await abortable(manifest(), job.abort.signal);
343
+ if (!isCurrent()) return false;
344
+ const matched = resolvePageRoute(map, internalPath(request));
345
+ if (matched && !matched.pages) { hard(destination, options.replace || options.pop); job.finished = true; return true; }
346
+ const shallow = !!options.shallow && matched?.route.pattern === current.route.pattern;
347
+ let view = current, route = current.route;
348
+ if (!hashOnly && !shallow) {
349
+ const cacheKey = new URL(request); cacheKey.hash = '';
350
+ const cached = prefetches.get(cacheKey.href);
351
+ let decoded;
352
+ if (cached) {
353
+ dropPrefetch(cacheKey.href, false);
354
+ const abort = () => cached.abort.abort(); job.abort.signal.addEventListener('abort', abort, { once: true });
355
+ try { await abortable(cached.promise, job.abort.signal); decoded = cached.data; } finally { job.abort.signal.removeEventListener('abort', abort); }
356
+ }
357
+ if (map.needsServerRouting === false && matched?.pages && !matched.route.ssg && !matched.route.ssp) {
358
+ decoded = { data: { pageProps: {}, __PRNEXT_ROUTER__: { pathname: matched.route.pattern,
359
+ query: { ...queryFromURL(request), ...matched.params }, isFallback: false } } };
360
+ }
361
+ for (let redirects = 0; redirects < 9; redirects++) {
362
+ decoded ||= await abortable(requestData(request, job.abort.signal), job.abort.signal);
363
+ if (!isCurrent()) return false;
364
+ const redirect = redirectURL(decoded, destination);
365
+ if (!redirect) break;
366
+ if (redirects === 8) throw new Error('Too many page redirects');
367
+ destination = redirect; request = new URL(destination);
368
+ if (!['http:', 'https:'].includes(destination.protocol)) throw new Error('Unsupported redirect protocol');
369
+ if (!isApplicationURL(destination, win.location.origin, basePath)) { hard(destination, options.replace || options.pop); job.finished = true; return true; }
370
+ events.emit('routeChangeStart', destination.pathname + destination.search + destination.hash, { shallow: job.shallow });
371
+ decoded = undefined;
372
+ }
373
+ const routedMatch = resolvePageRoute(map, decoded.rewrite ? new URL(decoded.rewrite.url, request).pathname : internalPath(request));
374
+ let data = decoded.data;
375
+ if (decoded.legacy) {
376
+ // Explicit middleware/GIP responses may contain any body, including
377
+ // JSON resembling page data. Resolve only from the final headers and
378
+ // manifest; a followed redirect may have lost an alias's rewrite.
379
+ if (!routedMatch?.pages) { hard(destination, options.replace || options.pop); job.finished = true; return false; }
380
+ const target = routedMatch.route;
381
+ if (target.ssg || target.ssp || (!target.gip && !target.appGip)) throw new Error('Invalid legacy page data');
382
+ data = { pageProps: {}, __PRNEXT_ROUTER__: { pathname: target.pattern, isFallback: false,
383
+ query: { ...queryFromURL(request), ...(decoded.rewrite ? rewriteQuery(decoded.rewrite) : {}), ...routedMatch.params } } };
384
+ }
385
+ const snapshot = data?.__PRNEXT_ROUTER__;
386
+ route = map.routes.find(item => (item.originalPattern || item.pattern) === snapshot?.pathname && (!snapshot.locale || item.locale === snapshot.locale)) || routedMatch?.route;
387
+ if (!route?.client) { hard(destination, options.replace || options.pop); job.finished = true; return true; }
388
+ if (!data?.notFound && (!snapshot || !data.pageProps || typeof data.pageProps !== 'object' || Array.isArray(data.pageProps))) throw new Error('Invalid page data');
389
+ const router = pageRouterSnapshot({ ...current.router, ...snapshot, ...(route.locale ? {locale:route.locale} : {}), query: snapshot?.query || routedMatch?.params,
390
+ pathname: route.originalPattern || route.pattern, basePath, isFallback: false }, request, destination, decoded.rewrite);
391
+ let module = data.notFound ? await loadErrorPage(map, router, destination, job.abort.signal, { notFound: true })
392
+ : await loadCode(route, job.abort.signal);
393
+ if (!isCurrent()) return false;
394
+ let result = data.notFound ? { props: module.props, appProps: module.appProps }
395
+ : { props: data.pageProps, appProps: applicationProps(data) };
396
+ if (!data.notFound && !route.ssg && !route.ssp) {
397
+ try {
398
+ result = await initialProps(module, { pathname: route.originalPattern || route.pattern, query: router.query,
399
+ asPath: router.i18n ? router.asPath : destination.pathname + destination.search + destination.hash, locale:router.locale, locales:router.locales, defaultLocale:router.defaultLocale }, job.abort.signal);
400
+ } catch (error) {
401
+ if (!isCurrent()) return false;
402
+ job.hookError = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'getInitialProps failed');
403
+ // Next's route loader first asks _error for props with the failed
404
+ // target context. Its renderer subsequently initializes the error
405
+ // App with the original document context, unless App supplied err.
406
+ module = await loadErrorPage(map, router, destination, job.abort.signal, { err: job.hookError,
407
+ context: { err: job.hookError, pathname: route.originalPattern || route.pattern, query: router.query }, tolerateHookFailure: true });
408
+ result = { props: module.props, appProps: module.appProps };
409
+ }
410
+ if (!isCurrent()) return false;
411
+ }
412
+ const assetRoute = module.assetRoute || route;
413
+ releaseStyles = await loadStyles(assetRoute, job.abort.signal);
414
+ view = { Page: module.Page, App: module.App, ...result, notFound: !!data.notFound,
415
+ errorKind: module.errorKind, assetRoute, buildId, route, clientSnapshot: true, router };
416
+ } else {
417
+ const query = hashOnly ? current.router.query : { ...queryFromURL(request), ...matched.params };
418
+ view = { ...current, router: { ...current.router, query, asPath: internalPath(destination) + destination.search + destination.hash } };
419
+ }
420
+ if (!isCurrent()) { releaseStyles?.remove(); return false; }
421
+ const finalPath = destination.pathname + destination.search + destination.hash;
422
+ if (!hashOnly) events.emit('beforeHistoryChange', finalPath, { shallow: job.shallow });
423
+ if (!options.pop) {
424
+ const replace = options.replace || destination.href === currentURL.href;
425
+ if (!replace) currentKey = nextKey++;
426
+ win.history[replace ? 'replaceState' : 'pushState'](historyState(destination, request, currentKey, { shallow, scroll: options.scroll },
427
+ options.scroll === false || shallow ? [win.scrollX, win.scrollY] : [0, 0]), '', destination.href);
428
+ } else currentKey = win.history.state?.__prnextPages?.key ?? nextKey++;
429
+ const oldRoute = current.assetRoute || [...map.routes, ...Object.values(map.errors || {}).filter(Boolean)].find(item => item.pattern === current.route.pattern);
430
+ current = view; currentURL = destination; currentRequest = request;
431
+ if (job.hookError && !view.appProps?.err) {
432
+ try {
433
+ const result = await initialProps(view, { ...initialErrorContext, err: job.hookError }, job.abort.signal);
434
+ if (!isCurrent()) return false;
435
+ current = view = { ...view, ...result };
436
+ } catch (error) {
437
+ if (!isCurrent()) return false;
438
+ current = view = { ...view, Page: DefaultError, App: undefined, props: { statusCode: job.hookError.statusCode }, appProps: {} };
439
+ }
440
+ }
441
+ await abortable(commit(view), job.abort.signal);
442
+ if (!isCurrent()) return false;
443
+ releaseStyles?.activate();
444
+ if (!shallow) retireStyles(oldRoute, view.assetRoute || route);
445
+ if (options.scroll !== false && (!shallow || options.pop)) scrollTo(destination, options.savedScroll);
446
+ job.finished = true; active = undefined;
447
+ if (job.hookError) {
448
+ job.errorRendered = true;
449
+ events.emit('routeChangeError', job.hookError, internalPath(destination) + destination.search + destination.hash, { shallow: job.shallow });
450
+ throw job.hookError;
451
+ }
452
+ events.emit(hashOnly ? 'hashChangeComplete' : 'routeChangeComplete', finalPath, { shallow: job.shallow });
453
+ return true;
454
+ } catch (error) {
455
+ if (job.errorRendered) throw error;
456
+ releaseStyles?.remove();
457
+ if (active !== job) return false;
458
+ job.finished = true; active = undefined;
459
+ events.emit('routeChangeError', error, job.asPath, { shallow: job.shallow });
460
+ hard(destination, options.replace || options.pop); return false;
461
+ } finally { clearTimeout(timer); }
462
+ }
463
+ async function reportRenderError(error, failedView) {
464
+ if (failedView !== current && (failedView.clientSnapshot || failedView.Page !== current.Page || failedView.props !== current.props)) return false;
465
+ renderingError?.abort.abort();
466
+ const context = { ...initialErrorContext, err: error };
467
+ const task = { abort: new AbortController(), sequence };
468
+ renderingError = task;
469
+ const failed = current, forceDefault = failed.errorKind === 'client' || failed.route?.pattern === '/_error';
470
+ const timer = setTimeout(() => task.abort.abort(new Error('Error page loading timed out')), 30_000);
471
+ const isCurrent = () => renderingError === task && sequence === task.sequence && current === failed && !task.abort.signal.aborted;
472
+ let styles;
473
+ try {
474
+ let map;
475
+ try { map = await abortable(manifest(), task.abort.signal); }
476
+ catch (loadError) { if (task.abort.signal.aborted) throw loadError; map = {}; }
477
+ const module = await loadErrorPage(map, failed.router, currentURL, task.abort.signal, { err: error, context, forceDefault });
478
+ if (!isCurrent()) return false;
479
+ styles = await loadStyles(module.assetRoute, task.abort.signal);
480
+ if (!isCurrent()) { styles.remove(); return false; }
481
+ const previousStyles = failed.assetRoute || [...(map.routes || []), ...Object.values(map.errors || {}).filter(Boolean)].find(route => route.pattern === failed.route?.pattern);
482
+ current = { ...failed, ...module, ...(forceDefault ? { App: undefined } : {}), clientSnapshot: true };
483
+ await abortable(commit(current), task.abort.signal);
484
+ if (renderingError !== task || sequence !== task.sequence || task.abort.signal.aborted) return false;
485
+ styles.activate(); retireStyles(previousStyles, current.assetRoute);
486
+ return true;
487
+ } catch (failure) {
488
+ styles?.remove();
489
+ if (renderingError !== task || sequence !== task.sequence) return false;
490
+ // A broken _error, its data hook or its chunks must not start an error loop.
491
+ current = { ...current, Page: DefaultError, App: undefined, props: { statusCode: error?.statusCode }, errorKind: 'client', clientSnapshot: true };
492
+ await commit(current);
493
+ return false;
494
+ } finally {
495
+ clearTimeout(timer);
496
+ if (renderingError === task) renderingError = undefined;
497
+ }
498
+ }
499
+ async function refreshFallback() {
500
+ if (!current.router.isFallback || active) return false;
501
+ const job = { abort: new AbortController(), silent: true };
502
+ active = job;
503
+ const timer = setTimeout(() => job.abort.abort(new Error('Page data request timed out')), 30_000);
504
+ let styles;
505
+ try {
506
+ // The initial entry already provides Page and App. In particular, an old
507
+ // document's build must receive its data 404 before any new manifest lookup.
508
+ const decoded = await abortable(requestData(currentRequest, job.abort.signal), job.abort.signal);
509
+ if (active !== job || job.abort.signal.aborted) return false;
510
+ const data = decoded.data, redirect = redirectURL(decoded, currentURL);
511
+ if (redirect) {
512
+ active = undefined; job.finished = true;
513
+ return navigate(redirect, undefined, { replace: true, scroll: false });
514
+ }
515
+ if (!data?.notFound && (!data?.__PRNEXT_ROUTER__ || !data.pageProps || typeof data.pageProps !== 'object' || Array.isArray(data.pageProps))) throw new Error('Invalid page data');
516
+ const router = pageRouterSnapshot({ ...current.router, ...data.__PRNEXT_ROUTER__, basePath, isFallback: false }, currentRequest, currentURL, decoded.rewrite);
517
+ let module;
518
+ if (data.notFound) {
519
+ let map;
520
+ try { map = await abortable(manifest(), job.abort.signal); }
521
+ catch (error) { if (job.abort.signal.aborted) throw error; map = {}; }
522
+ module = await loadErrorPage(map, router, currentURL, job.abort.signal, { notFound: true });
523
+ }
524
+ styles = module ? await loadStyles(module.assetRoute, job.abort.signal) : null;
525
+ if (active !== job || job.abort.signal.aborted) { styles?.remove(); return false; }
526
+ const previousStyles = current.assetRoute;
527
+ current = { ...current, ...(module || { props: data.pageProps, appProps: applicationProps(data) }), notFound: !!data.notFound, router };
528
+ await abortable(commit(current), job.abort.signal);
529
+ if (active !== job || job.abort.signal.aborted) { styles?.remove(); return false; }
530
+ styles?.activate(); if (module) retireStyles(previousStyles, module.assetRoute);
531
+ return active === job && !job.abort.signal.aborted;
532
+ } catch (error) {
533
+ styles?.remove();
534
+ if (active !== job) return false;
535
+ current = { ...current, Page: DefaultError, props: { statusCode: 500 }, failed: true, router: { ...current.router, isFallback: false } };
536
+ await commit(current);
537
+ return false;
538
+ } finally {
539
+ clearTimeout(timer); job.finished = true;
540
+ if (active === job) active = undefined;
541
+ }
542
+ }
543
+ const popstate = event => {
544
+ const state = event.state?.__prnextPages;
545
+ if (!state) { hard(new URL(win.location.href), true); return; }
546
+ const data = { url: state.url, as: state.as, options: state.options || {} };
547
+ if (!beforePop(data)) return;
548
+ void navigate(new URL(state.url, currentURL), new URL(state.as, currentURL), { ...state.options, pop: true, savedScroll: state.scroll });
549
+ };
550
+ let frame;
551
+ const onScroll = () => { if (frame !== undefined) return; frame = win.requestAnimationFrame(() => { frame = undefined; saveScroll(); }); };
552
+ const pagehide = () => { saveScroll(); active?.abort.abort(); renderingError?.abort.abort(); for (const key of prefetches.keys()) dropPrefetch(key); };
553
+ win.history.replaceState(historyState(currentURL, currentRequest, currentKey, {}, [win.scrollX, win.scrollY]), '', currentURL.href);
554
+ win.history.scrollRestoration = 'manual';
555
+ win.addEventListener('popstate', popstate); win.addEventListener('scroll', onScroll, { passive: true }); win.addEventListener('pagehide', pagehide);
556
+ return { navigate, prefetch, saveScroll, reportRenderError, snapshot: () => current.router, beforePopState(callback) { beforePop = callback; },
557
+ ...(process.env.NODE_ENV === 'development' ? { async devRefresh(options) {
558
+ buildId = options.buildId; manifestUrl = options.manifestUrl; manifestPromise = undefined;
559
+ for (const key of prefetches.keys()) dropPrefetch(key);
560
+ await navigate(currentRequest, currentURL, { replace: true, scroll: false });
561
+ } } : {}),
562
+ refreshFallback,
563
+ dispose() { pagehide(); win.removeEventListener('popstate', popstate); win.removeEventListener('scroll', onScroll); win.removeEventListener('pagehide', pagehide); if (frame !== undefined) win.cancelAnimationFrame(frame); },
564
+ };
565
+ }