@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha
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/README.md +27 -868
- package/build/actions.mjs +27 -15
- package/build/analyze.mjs +180 -24
- package/build/app-config.mjs +13 -0
- package/build/app-static.mjs +34 -12
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +122 -46
- package/build/bundle-runtime.mjs +22 -9
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +34 -10
- package/build/config.mjs +34 -3
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +56 -12
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +18 -14
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +14 -7
- package/build/edge.mjs +3 -3
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +15 -5
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +90 -28
- package/build/instrumentation.mjs +30 -0
- package/build/lazy-dynamic-imports.mjs +137 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migrate.mjs +4 -4
- package/build/migration-audit.mjs +19 -31
- package/build/migration-imports.mjs +245 -0
- package/build/migration-scenarios-runner.mjs +73 -0
- package/build/migration-scenarios.mjs +72 -0
- package/build/native-discovery.mjs +65 -5
- package/build/native-packages.mjs +2 -0
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +1 -0
- package/build/persistent-cache.mjs +18 -3
- package/build/precompress.mjs +1 -1
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +19 -9
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +5 -5
- package/build/production-builder.mjs +59 -10
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/standalone.mjs +21 -3
- package/build/static-export.mjs +4 -4
- package/build/styles.mjs +6 -4
- package/build/transform.mjs +1 -1
- package/build/typed-routes.mjs +4 -0
- package/build/typescript-dev.mjs +35 -8
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript.mjs +6 -4
- package/build/webpack-graph.mjs +19 -6
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +70 -5
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +15 -3
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +152 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +69 -4
- package/compat/link.cjs +13 -10
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +248 -2
- package/docs/ai/data.md +165 -5
- package/docs/ai/deployment.md +158 -0
- package/docs/ai/quickstart.md +29 -4
- package/docs/ai/routing.md +93 -0
- package/docs/ai/troubleshooting.md +116 -2
- package/package.json +40 -9
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +316 -92
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +88 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +7 -2
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +13 -3
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +83 -25
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +74 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/fs.mjs +15 -8
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +14 -7
- package/runtime/pages-navigation.mjs +48 -63
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +34 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/watch-project.mjs +19 -15
- package/runtime/worker.mjs +70 -26
- package/vendor/nft/LICENSE +7 -0
- package/vendor/nft/README.md +16 -0
- package/vendor/nft/out/analyze.d.ts +8 -0
- package/vendor/nft/out/analyze.js +1216 -0
- package/vendor/nft/out/analyze.js.map +1 -0
- package/vendor/nft/out/cli.d.ts +2 -0
- package/vendor/nft/out/cli.js +94 -0
- package/vendor/nft/out/cli.js.map +1 -0
- package/vendor/nft/out/fs.d.ts +22 -0
- package/vendor/nft/out/fs.js +106 -0
- package/vendor/nft/out/fs.js.map +1 -0
- package/vendor/nft/out/index.d.ts +4 -0
- package/vendor/nft/out/index.js +25 -0
- package/vendor/nft/out/index.js.map +1 -0
- package/vendor/nft/out/node-file-trace.d.ts +41 -0
- package/vendor/nft/out/node-file-trace.js +380 -0
- package/vendor/nft/out/node-file-trace.js.map +1 -0
- package/vendor/nft/out/resolve-dependency.d.ts +6 -0
- package/vendor/nft/out/resolve-dependency.js +427 -0
- package/vendor/nft/out/resolve-dependency.js.map +1 -0
- package/vendor/nft/out/types.d.ts +66 -0
- package/vendor/nft/out/types.js +2 -0
- package/vendor/nft/out/types.js.map +1 -0
- package/vendor/nft/out/utils/ast-helpers.js +49 -0
- package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
- package/vendor/nft/out/utils/binary-locators.js +88 -0
- package/vendor/nft/out/utils/binary-locators.js.map +1 -0
- package/vendor/nft/out/utils/get-package-base.js +30 -0
- package/vendor/nft/out/utils/get-package-base.js.map +1 -0
- package/vendor/nft/out/utils/interop-require.js +24 -0
- package/vendor/nft/out/utils/interop-require.js.map +1 -0
- package/vendor/nft/out/utils/node-version.js +10 -0
- package/vendor/nft/out/utils/node-version.js.map +1 -0
- package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
- package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
- package/vendor/nft/out/utils/special-cases.js +347 -0
- package/vendor/nft/out/utils/special-cases.js.map +1 -0
- package/vendor/nft/out/utils/static-eval.js +579 -0
- package/vendor/nft/out/utils/static-eval.js.map +1 -0
- package/vendor/nft/out/utils/types.js +2 -0
- package/vendor/nft/out/utils/types.js.map +1 -0
- package/vendor/nft/out/utils/wrappers.js +659 -0
- package/vendor/nft/out/utils/wrappers.js.map +1 -0
- package/vendor/nft/package.json +4 -0
- package/vendor/nft/upstream.json +51 -0
package/runtime/app-client.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { deploymentHeaders, assertDeploymentResponse } from './deployment-client.mjs';
|
|
1
2
|
import React, { startTransition, useLayoutEffect } from 'react';
|
|
2
3
|
import { preload } from 'react-dom';
|
|
3
|
-
import {
|
|
4
|
-
import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES, readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
|
|
4
|
+
import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
|
|
5
5
|
import { isDynamicBailout } from '../compat/dynamic-bailout.cjs';
|
|
6
6
|
import { createRoot, hydrateRoot } from 'react-dom/client';
|
|
7
7
|
import { AppRouterProvider } from '../compat/app-context.cjs';
|
|
@@ -13,6 +13,14 @@ import { flightResponseURL, publicNavigationURL, isApplicationURL } from './clie
|
|
|
13
13
|
import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
|
|
14
14
|
import GlobalErrorBoundary, { DefaultGlobalError, GlobalErrorTrigger } from '../compat/app-global-error-boundary.cjs';
|
|
15
15
|
import { loadBeforeInteractive } from '../compat/script-loader.cjs';
|
|
16
|
+
import { createPrefetchScheduler, prefetchCacheVictim } from './prefetch-scheduler.mjs';
|
|
17
|
+
import { appStyleManager } from './app-styles.mjs';
|
|
18
|
+
import { retainAppStyles } from './app-style-selection.mjs';
|
|
19
|
+
import { isHtmlDocumentRoute } from './route-pattern.mjs';
|
|
20
|
+
import {recoverNavigation} from './navigation-recovery.mjs';
|
|
21
|
+
import { createActivityCache } from '../compat/activity-cache.cjs';
|
|
22
|
+
import { createFlightTransport, TYPE as FLIGHT_DELTA_TYPE } from '../compat/flight-delta.cjs';
|
|
23
|
+
import {decodeBase64} from '../compat/base64.cjs';
|
|
16
24
|
|
|
17
25
|
const PREFETCH_LIMIT = 8;
|
|
18
26
|
const PREFETCH_LIFETIME = 30_000;
|
|
@@ -31,7 +39,9 @@ function loadClientModule(source) {
|
|
|
31
39
|
// supplies ESM import functions so React and application contexts stay shared.
|
|
32
40
|
export function installFlightModuleLoader(clientModules) {
|
|
33
41
|
const loaded = new Map();
|
|
42
|
+
const loadedVersions = new Map();
|
|
34
43
|
const pending = new Map();
|
|
44
|
+
let version = 0;
|
|
35
45
|
const previousRequire = globalThis.__webpack_require__;
|
|
36
46
|
const previousLoad = globalThis.__webpack_chunk_load__;
|
|
37
47
|
globalThis.__webpack_require__ = id => {
|
|
@@ -44,32 +54,42 @@ export function installFlightModuleLoader(clientModules) {
|
|
|
44
54
|
return typeof source === 'string' ? source : source?.browserModule || String(id);
|
|
45
55
|
};
|
|
46
56
|
globalThis.__webpack_get_script_filename__ = id => globalThis.__webpack_require__.u(id);
|
|
47
|
-
|
|
48
|
-
if (loaded.has(id)) return Promise.resolve();
|
|
57
|
+
function loadChunk(id) {
|
|
49
58
|
if (pending.has(id)) return pending.get(id);
|
|
59
|
+
if (loaded.has(id) && loadedVersions.get(id) === version) return Promise.resolve();
|
|
50
60
|
if (!Object.hasOwn(clientModules, id)) {
|
|
51
61
|
if (previousLoad) return previousLoad(id);
|
|
52
62
|
return Promise.reject(new Error(`Unknown PRNext client module ${id}`));
|
|
53
63
|
}
|
|
54
64
|
const source = clientModules[id];
|
|
65
|
+
const requestedVersion = version;
|
|
66
|
+
const current = () => {
|
|
67
|
+
if (!Object.hasOwn(clientModules, id)) throw new Error(`Unknown PRNext client module ${id}`);
|
|
68
|
+
return loadChunk(id);
|
|
69
|
+
};
|
|
55
70
|
const promise = Promise.resolve().then(() => loadClientModule(source))
|
|
56
|
-
.then(module => {
|
|
57
|
-
|
|
71
|
+
.then(module => {
|
|
72
|
+
if (requestedVersion !== version) return current();
|
|
73
|
+
loaded.set(id, module); loadedVersions.set(id, version);
|
|
74
|
+
}, error => {
|
|
75
|
+
if (requestedVersion !== version) return current();
|
|
76
|
+
loaded.delete(id); loadedVersions.delete(id);
|
|
58
77
|
throw error;
|
|
78
|
+
}).finally(() => {
|
|
79
|
+
if (pending.get(id) === promise) pending.delete(id);
|
|
59
80
|
});
|
|
60
81
|
pending.set(id, promise);
|
|
61
82
|
return promise;
|
|
62
|
-
}
|
|
83
|
+
}
|
|
84
|
+
globalThis.__webpack_chunk_load__ = loadChunk;
|
|
63
85
|
return {
|
|
64
86
|
async updateModules(nextModules) {
|
|
65
|
-
const ids = [...loaded.keys()].filter(id => Object.hasOwn(nextModules, id));
|
|
87
|
+
const ids = [...new Set([...loaded.keys(), ...pending.keys()])].filter(id => Object.hasOwn(nextModules, id));
|
|
88
|
+
version++;
|
|
66
89
|
clientModules = nextModules;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
loaded.set(id, module);
|
|
71
|
-
}));
|
|
72
|
-
for (const id of loaded.keys()) if (!Object.hasOwn(nextModules, id)) loaded.delete(id);
|
|
90
|
+
pending.clear();
|
|
91
|
+
for (const id of loaded.keys()) if (!Object.hasOwn(nextModules, id)) { loaded.delete(id); loadedVersions.delete(id); }
|
|
92
|
+
await Promise.all(ids.map(loadChunk));
|
|
73
93
|
},
|
|
74
94
|
};
|
|
75
95
|
}
|
|
@@ -88,8 +108,7 @@ function embeddedFlight() {
|
|
|
88
108
|
document.removeEventListener('DOMContentLoaded', onComplete);
|
|
89
109
|
controller.close();
|
|
90
110
|
} else {
|
|
91
|
-
|
|
92
|
-
controller.enqueue(Uint8Array.from(binary, character => character.charCodeAt(0)));
|
|
111
|
+
controller.enqueue(decodeBase64(value));
|
|
93
112
|
}
|
|
94
113
|
};
|
|
95
114
|
onComplete = () => {
|
|
@@ -108,28 +127,27 @@ function embeddedFlight() {
|
|
|
108
127
|
}
|
|
109
128
|
const element = document.getElementById('__PRNEXT_FLIGHT__');
|
|
110
129
|
if (!element) throw new Error('PRNext App Router Flight payload is missing');
|
|
111
|
-
const
|
|
112
|
-
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
130
|
+
const bytes = decodeBase64(element.textContent.trim());
|
|
113
131
|
element.remove();
|
|
114
132
|
return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
function NormalStyles({ css, fonts }) {
|
|
118
|
-
const [styles, setStyles] = React.useState([]);
|
|
119
136
|
useLayoutEffect(() => {
|
|
120
137
|
for (const font of fonts || []) preload(font.href, { as: 'font', type: font.type, crossOrigin: 'anonymous' });
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const loaded = new Set([...document.querySelectorAll('link[rel="stylesheet"]')].map(node => node.href));
|
|
125
|
-
const missing = (css || []).filter(href => !loaded.has(new URL(href, window.location.href).href));
|
|
126
|
-
if (missing.length) setStyles(current => [...new Set([...current, ...missing])]);
|
|
138
|
+
const styles = appStyleManager(document);
|
|
139
|
+
styles.commit(css || []);
|
|
140
|
+
return () => styles.release();
|
|
127
141
|
}, [css, fonts]);
|
|
128
|
-
return
|
|
142
|
+
return null;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss }) {
|
|
145
|
+
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss, retentionEpoch, retentionAuthorizationEpoch }) {
|
|
132
146
|
const [hydrated, setHydrated] = React.useState(false);
|
|
147
|
+
const cache = React.useRef(null);
|
|
148
|
+
if (!cache.current) cache.current = createActivityCache({ limit: controller._navigationCache?.maxEntries, ttl: controller._navigationCache?.ttlMs });
|
|
149
|
+
const activityCache = React.useMemo(() => ({ cache: cache.current, epoch: retentionEpoch, authorizationEpoch: retentionAuthorizationEpoch,
|
|
150
|
+
authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey]);
|
|
133
151
|
const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
|
|
134
152
|
useLayoutEffect(() => {
|
|
135
153
|
if (process.env.NODE_ENV === 'development' && model.instantDiagnostics?.length) globalThis.__PRNEXT_DEV__?.reportError(Object.assign(new Error(model.instantDiagnostics.join('\n')), { digest: 'PRNEXT_INSTANT_BLOCKING' }));
|
|
@@ -145,10 +163,11 @@ function AppRoot({ model, controller, committed, url, globalErrorComponent, glob
|
|
|
145
163
|
return React.createElement(AppRouterProvider, { router, controller, ...(model.partialPreview ? { prerenderSearch: previewDynamic, ...(model.partialPreview.unknownParams ? { prerenderParams: previewDynamic } : {}) } : {}) },
|
|
146
164
|
React.createElement(GlobalErrorBoundary, { component: globalErrorComponent, css: globalErrorCss, resetKey: model },
|
|
147
165
|
model.globalError ? React.createElement(GlobalErrorTrigger, { error: model.globalError })
|
|
148
|
-
: appContent(model.tree, { layoutCache: model.layoutCache || null, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
|
|
166
|
+
: appContent(model.tree, { layoutCache: model.layoutCache || null, activityCache, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
|
|
149
167
|
}
|
|
150
168
|
|
|
151
169
|
function prepareLayoutCache(model, previous) {
|
|
170
|
+
retainAppStyles(model, previous);
|
|
152
171
|
if (!model.routing || model.layoutCache) return model;
|
|
153
172
|
const active = new Set(Object.keys(model.routing.slots));
|
|
154
173
|
model.layoutCache = {
|
|
@@ -166,7 +185,7 @@ function AppView(props) {
|
|
|
166
185
|
}
|
|
167
186
|
|
|
168
187
|
function appView(props) {
|
|
169
|
-
return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, props));
|
|
188
|
+
return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, { ...props, retentionEpoch: props.controller?._retentionEpoch || 0, retentionAuthorizationEpoch: props.controller?._authorizationEpoch || 0 }));
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
function historyState(key, scroll, routing) {
|
|
@@ -179,7 +198,7 @@ function scrollToTarget(url, saved) {
|
|
|
179
198
|
if (url.hash) {
|
|
180
199
|
let name = url.hash.slice(1);
|
|
181
200
|
try { name = decodeURIComponent(name); } catch { /* A literal malformed escape can be an element ID. */ }
|
|
182
|
-
const anchor = document.
|
|
201
|
+
const anchor = [...document.querySelectorAll('[id], [name]')].find(element => (element.id === name || element.getAttribute('name') === name) && element.getClientRects().length);
|
|
183
202
|
if (anchor) { anchor.scrollIntoView(); return; }
|
|
184
203
|
}
|
|
185
204
|
window.scrollTo(0, 0);
|
|
@@ -191,7 +210,7 @@ function assertModel(model) {
|
|
|
191
210
|
return model;
|
|
192
211
|
}
|
|
193
212
|
|
|
194
|
-
async function startApp({ clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false } = {}) {
|
|
213
|
+
async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false, partialPrefetching = false, navigationCache } = {}) {
|
|
195
214
|
strictMode = enabledStrictMode;
|
|
196
215
|
await loadBeforeInteractive();
|
|
197
216
|
basePath = normalizeBasePath(basePath);
|
|
@@ -201,8 +220,18 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
201
220
|
await globalThis.__webpack_chunk_load__(globalErrorId);
|
|
202
221
|
return { default: globalThis.__webpack_require__(globalErrorId).default };
|
|
203
222
|
}) : DefaultGlobalError;
|
|
223
|
+
// The production browser graph fixes this feature at build time. The
|
|
224
|
+
// fallback also keeps direct, unbundled runtime consumers functional.
|
|
225
|
+
const partialRuntime = typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined'
|
|
226
|
+
? cacheComponents ? import('./app-partial-client.mjs') : null
|
|
227
|
+
: __PRNEXT_CACHE_COMPONENTS__ ? import('./app-partial-client.mjs') : null;
|
|
204
228
|
// The Flight adapter reads __webpack_require__.u during module evaluation.
|
|
205
|
-
|
|
229
|
+
// Both imports start concurrently and receive rejection handlers in this
|
|
230
|
+
// turn. A rejected PPR chunk must not wait unhandled for a slow Flight chunk.
|
|
231
|
+
const [flightClient, partial] = await Promise.all([import('react-server-dom-webpack/client.browser'), partialRuntime]);
|
|
232
|
+
// Ordinary App routes do not need the resumable PPR model. Keep it out of
|
|
233
|
+
// their initial module graph; Cache Components still loads it before use.
|
|
234
|
+
const { partialModel, readPartialPrefetch, partialFlightStream } = partial || {};
|
|
206
235
|
const { createFromFetch, createFromReadableStream, createServerReference, createTemporaryReferenceSet, encodeReply } = flightClient.default || flightClient;
|
|
207
236
|
let root;
|
|
208
237
|
let actionQueue = Promise.resolve();
|
|
@@ -221,61 +250,111 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
221
250
|
if (initialRewrite) initialModel.rewrite = initialRewrite;
|
|
222
251
|
let currentUrl = new URL(window.location.href);
|
|
223
252
|
let currentRouting = initialModel.routing;
|
|
253
|
+
// A pending destination may already own the history entry while React still
|
|
254
|
+
// shows the previous view. Rolling back its render must not rewrite that
|
|
255
|
+
// entry with the previous view's routing state on the next scroll event.
|
|
256
|
+
let historyRouting = currentRouting;
|
|
224
257
|
let currentModel = initialModel;
|
|
225
|
-
|
|
258
|
+
let committedView = { model: initialModel, routing: currentRouting, url: currentUrl.href };
|
|
259
|
+
let pendingView;
|
|
260
|
+
const historyFocus = new Map();
|
|
226
261
|
let currentKey = window.history.state?.__prnext?.key || 0;
|
|
227
262
|
let nextKey = currentKey + 1;
|
|
228
263
|
let navigation = 0;
|
|
229
|
-
let activeRequest;
|
|
230
|
-
const prefetches = new Map();
|
|
231
|
-
const partialSegments = new Map();
|
|
264
|
+
let activeRequest, finishNavigation, finishCommit, pageHidden = false, notifyingPrefetch = false;
|
|
265
|
+
const prefetches = new Map(), scheduler = createPrefetchScheduler();
|
|
266
|
+
const partialSegments = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? new Map() : null;
|
|
232
267
|
let segmentBytes = 0;
|
|
233
268
|
|
|
234
|
-
if (currentRouting) historyModels.set(currentKey, initialModel);
|
|
235
269
|
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
|
|
236
270
|
window.history.scrollRestoration = 'manual';
|
|
237
271
|
|
|
238
272
|
function saveScroll() {
|
|
239
273
|
// popstate has already changed location; never overwrite its destination.
|
|
240
274
|
if (window.location.href === currentUrl.href) {
|
|
241
|
-
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY],
|
|
275
|
+
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], historyRouting), '', currentUrl.href);
|
|
276
|
+
if (typeof WeakRef !== 'undefined' && document.activeElement && document.activeElement !== document.body) historyFocus.set(currentKey, new WeakRef(document.activeElement));
|
|
277
|
+
while (historyFocus.size > 16) historyFocus.delete(historyFocus.keys().next().value);
|
|
242
278
|
}
|
|
243
279
|
}
|
|
244
280
|
|
|
245
|
-
|
|
281
|
+
function renderView(model, url, { token = navigation, canonical = true, committed } = {}) {
|
|
282
|
+
const view = { model, token };
|
|
283
|
+
pendingView = view;
|
|
284
|
+
startTransition(() => root.render(appView({ model, controller, url, globalErrorComponent, globalErrorCss,
|
|
285
|
+
committed: () => {
|
|
286
|
+
if (token !== navigation || pendingView !== view) return;
|
|
287
|
+
pendingView = undefined;
|
|
288
|
+
// A PPR preview is authorized UI, but not the completed route model
|
|
289
|
+
// from which a later navigation should inherit its layout cache.
|
|
290
|
+
if (canonical) committedView = { model, routing: model.routing, url };
|
|
291
|
+
committed?.();
|
|
292
|
+
},
|
|
293
|
+
})));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function abandonPendingView() {
|
|
297
|
+
if (!pendingView) return;
|
|
298
|
+
pendingView = undefined;
|
|
299
|
+
currentModel = committedView.model;
|
|
300
|
+
currentRouting = committedView.routing;
|
|
301
|
+
// React can already be reading a lazy row after createFromFetch resolves
|
|
302
|
+
// the root model. Replace that suspended render before abort rejects its
|
|
303
|
+
// remaining rows; it must not poison the retained layout or page cache.
|
|
304
|
+
root.render(appView({ model: committedView.model, controller, url: committedView.url, globalErrorComponent, globalErrorCss }));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const flightTransport = createFlightTransport();
|
|
308
|
+
async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false,fullFlight=false}={}) {
|
|
246
309
|
if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
|
|
247
310
|
const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
|
|
248
|
-
const
|
|
311
|
+
const base = !exported && !fullFlight ? flightTransport.snapshot() : undefined;
|
|
312
|
+
const target = exported ? new URL(basePath + '/_prnext/flight' + (deploymentId ? '/' + encodeURIComponent(deploymentId) : '') + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
|
|
249
313
|
const response = await fetch(target.href, {
|
|
250
|
-
headers: { RSC: '1', Accept: 'text/x-component', ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
|
|
314
|
+
headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: 'text/x-component', ...(!exported&&!fullFlight?{'x-prnext-flight-accept':'v1',...(base?{'x-prnext-flight-base':base.token}:{})}:{}), ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
|
|
251
315
|
credentials: 'same-origin',
|
|
252
316
|
signal,
|
|
253
317
|
});
|
|
318
|
+
if (!exported) await assertDeploymentResponse(response, deploymentId);
|
|
254
319
|
let destination;
|
|
255
320
|
try { destination = exported ? url : flightResponseURL(response, url); }
|
|
256
321
|
catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
|
|
257
|
-
|
|
322
|
+
const delta = response.headers.get('content-type')?.startsWith(FLIGHT_DELTA_TYPE);
|
|
323
|
+
if (exported ? !response.ok : !(delta||response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component'))) {
|
|
258
324
|
await response.body?.cancel();
|
|
259
325
|
throw new Error('The navigation destination requires a document request');
|
|
260
326
|
}
|
|
261
|
-
onPartial?.(response, destination);
|
|
262
|
-
|
|
327
|
+
await onPartial?.(response, destination);
|
|
328
|
+
let model;
|
|
329
|
+
try { model = assertModel(await createFromFetch(Promise.resolve(exported?response:await flightTransport.response(response,base)), flightOptions)); }
|
|
330
|
+
catch(error) {
|
|
331
|
+
if(!delta||fullFlight||signal?.aborted)throw error;
|
|
332
|
+
flightTransport.clear();
|
|
333
|
+
return requestFlight(url,signal,routing,onPartial,{skipEnsure:true,fullFlight:true});
|
|
334
|
+
}
|
|
263
335
|
model.router.basePath = basePath;
|
|
264
336
|
const rewrite = readRewriteHeader(response);
|
|
265
337
|
if (rewrite) model.rewrite = rewrite;
|
|
266
338
|
return { model, url: destination };
|
|
267
339
|
}
|
|
268
340
|
|
|
269
|
-
|
|
341
|
+
const navigateFlight=(url,signal,...args)=>recoverNavigation(recoverySignal=>requestFlight(url,recoverySignal,...args),{signal});
|
|
342
|
+
|
|
343
|
+
const requestPartial = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? async function requestPartial(url, signal, full = false) {
|
|
270
344
|
const response = await fetch(url.href, { credentials: 'same-origin', signal,
|
|
271
|
-
headers: { RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
|
|
345
|
+
headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
|
|
346
|
+
...(partialPrefetching ? { 'x-prnext-prefetch-stage': full ? 'prefetch' : 'shell' } : {}),
|
|
272
347
|
...(partialSegments.size ? { 'x-prnext-prefetch-known': [...partialSegments.keys()].join(',') } : {}) } });
|
|
273
|
-
|
|
348
|
+
await assertDeploymentResponse(response, deploymentId);
|
|
349
|
+
let destination;
|
|
350
|
+
try { destination = flightResponseURL(response, url); }
|
|
351
|
+
catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
|
|
274
352
|
if (!response.headers.get('content-type')?.startsWith(PARTIAL_PREFETCH_TYPE)) {
|
|
275
353
|
await response.body?.cancel();
|
|
276
354
|
return null;
|
|
277
355
|
}
|
|
278
356
|
const value = await readPartialPrefetch(response);
|
|
357
|
+
if (signal.aborted) return null;
|
|
279
358
|
let segment = partialSegments.get(value.id);
|
|
280
359
|
if (!segment && value.flight) {
|
|
281
360
|
const bytes = value.flight.length;
|
|
@@ -294,11 +373,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
294
373
|
// No visitor data is decoded here. Explicit postponed errors become pending
|
|
295
374
|
// boundaries until the navigation's independently authorized Flight arrives.
|
|
296
375
|
const suspended = new Promise(() => {});
|
|
297
|
-
const
|
|
376
|
+
const decoded = assertModel(await segment.model);
|
|
377
|
+
if (signal.aborted) return null;
|
|
378
|
+
const model = partialModel(decoded, { keyMap: new Map(value.keys), suspended });
|
|
298
379
|
model.router = { ...value.router, basePath };
|
|
299
380
|
model.partialPreview = { unknownParams: value.unknownParams, suspended };
|
|
300
381
|
return { model, id: value.id, url: destination, partial: true };
|
|
301
|
-
}
|
|
382
|
+
} : null;
|
|
302
383
|
|
|
303
384
|
function dropPrefetch(key, invalidate = false) {
|
|
304
385
|
const entry = prefetches.get(key);
|
|
@@ -307,21 +388,36 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
307
388
|
clearTimeout(entry.timer);
|
|
308
389
|
if (invalidate) {
|
|
309
390
|
entry.abort.abort();
|
|
310
|
-
entry.
|
|
391
|
+
const callbacks = entry.invalidations; entry.invalidations = undefined;
|
|
392
|
+
// Callbacks may rearm prefetch. Deliver after cache/queue mutations so a
|
|
393
|
+
// replacement cannot be evicted inside the same invalidation transaction.
|
|
394
|
+
if (callbacks?.size) queueMicrotask(() => {
|
|
395
|
+
for (const callback of callbacks) {
|
|
396
|
+
notifyingPrefetch = true;
|
|
397
|
+
try { callback(); } catch (error) { console.error(error); }
|
|
398
|
+
finally { notifyingPrefetch = false; }
|
|
399
|
+
}
|
|
400
|
+
});
|
|
311
401
|
}
|
|
312
402
|
}
|
|
313
403
|
|
|
314
404
|
function invalidatePrefetches() {
|
|
315
|
-
partialSegments.clear(); segmentBytes = 0;
|
|
316
|
-
for (const key of prefetches.
|
|
405
|
+
if (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) { partialSegments.clear(); segmentBytes = 0; }
|
|
406
|
+
for (const [key, entry] of [...prefetches]) if (prefetches.get(key) === entry) dropPrefetch(key, true);
|
|
317
407
|
}
|
|
318
408
|
|
|
319
409
|
function callServer(id, args) {
|
|
320
410
|
if (!root) return Promise.reject(new Error('Server Actions cannot run during initial rendering'));
|
|
321
411
|
const token = ++navigation;
|
|
412
|
+
finishCommit?.();
|
|
322
413
|
const target = new URL(window.location.href);
|
|
414
|
+
saveScroll();
|
|
415
|
+
abandonPendingView();
|
|
323
416
|
activeRequest?.abort();
|
|
417
|
+
appStyleManager(document).cancel();
|
|
418
|
+
finishNavigation?.(); finishNavigation = undefined;
|
|
324
419
|
activeRequest = undefined;
|
|
420
|
+
purgeRetainedPages();
|
|
325
421
|
// Mutations are dispatched in order, including Set-Cookie effects required
|
|
326
422
|
// by a subsequent action. Capture their route priority when invoked: a
|
|
327
423
|
// queued action must not cancel a newer user navigation when it starts.
|
|
@@ -331,12 +427,30 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
331
427
|
}
|
|
332
428
|
|
|
333
429
|
async function dispatchAction(id, args, token, target) {
|
|
430
|
+
const release = scheduler.navigation();
|
|
431
|
+
try { return await performAction(id, args, token, target); } finally { release(); }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function waitForDevelopmentUpdate() {
|
|
435
|
+
// A generation updates the Flight loader before React Refresh registers
|
|
436
|
+
// the replacement component families. An already-running action may finish
|
|
437
|
+
// in that interval; rendering its model immediately remounts those client
|
|
438
|
+
// components and loses layout state. Wait only on development bookkeeping,
|
|
439
|
+
// never replay the mutation or cancel a newer user navigation.
|
|
440
|
+
let development = globalThis.__PRNEXT_DEV__;
|
|
441
|
+
while (development?.applying && development.applied) {
|
|
442
|
+
await development.applied;
|
|
443
|
+
development = globalThis.__PRNEXT_DEV__;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function performAction(id, args, token, target) {
|
|
334
448
|
invalidatePrefetches();
|
|
335
449
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
336
450
|
const body = await encodeReply(args, { temporaryReferences });
|
|
337
451
|
const response = await fetch(target.href, {
|
|
338
452
|
method: 'POST',
|
|
339
|
-
headers: { 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
453
|
+
headers: { ...deploymentHeaders(deploymentId), 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
340
454
|
credentials: 'same-origin',
|
|
341
455
|
// RPC redirects arrive as Flight data. Never replay a mutation through an
|
|
342
456
|
// unexpected HTTP 307/308 or redirect an authenticated POST off origin.
|
|
@@ -344,19 +458,30 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
344
458
|
body,
|
|
345
459
|
});
|
|
346
460
|
invalidatePrefetches();
|
|
461
|
+
await assertDeploymentResponse(response, deploymentId);
|
|
347
462
|
if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
|
|
348
463
|
await response.body?.cancel();
|
|
349
464
|
throw new Error(`Server Action request failed (${response.status})`);
|
|
350
465
|
}
|
|
351
466
|
const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
|
|
467
|
+
const outcome = () => {
|
|
468
|
+
if (model?.actionError) {
|
|
469
|
+
const error = new Error(model.actionError.message || 'The Server Action failed');
|
|
470
|
+
if (model.actionError.digest !== undefined) error.digest = model.actionError.digest;
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
return model?.actionResult;
|
|
474
|
+
};
|
|
475
|
+
if (process.env.NODE_ENV === 'development') await waitForDevelopmentUpdate();
|
|
476
|
+
controller._retentionEpoch++;
|
|
352
477
|
if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
|
|
353
478
|
if (token === navigation) window.location.replace(target.href);
|
|
354
|
-
return
|
|
479
|
+
return outcome();
|
|
355
480
|
}
|
|
356
481
|
if (model?.actionRedirect) {
|
|
357
482
|
const destination = navigationUrl(model.actionRedirect.url);
|
|
358
483
|
if (token === navigation) await navigate(destination, { replace: model.actionRedirect.type === 'replace' });
|
|
359
|
-
return
|
|
484
|
+
return outcome();
|
|
360
485
|
}
|
|
361
486
|
if (model && Object.hasOwn(model, 'tree') && token === navigation) {
|
|
362
487
|
assertModel(model);
|
|
@@ -364,37 +489,44 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
364
489
|
const rewrite = readRewriteHeader(response);
|
|
365
490
|
if (rewrite) model.rewrite = rewrite;
|
|
366
491
|
prepareLayoutCache(model, currentModel);
|
|
492
|
+
try { await appStyleManager(document).prepare(model.globalError ? [] : model.css || []); }
|
|
493
|
+
catch (error) { if (token !== navigation) return outcome(); throw error; }
|
|
494
|
+
if (process.env.NODE_ENV === 'development') await waitForDevelopmentUpdate();
|
|
495
|
+
if (token !== navigation) return outcome();
|
|
367
496
|
currentRouting = model.routing;
|
|
368
497
|
currentModel = model;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (model?.actionError) {
|
|
373
|
-
const error = new Error(model.actionError.message || 'The Server Action failed');
|
|
374
|
-
if (model.actionError.digest !== undefined) error.digest = model.actionError.digest;
|
|
375
|
-
throw error;
|
|
498
|
+
historyRouting = currentRouting;
|
|
499
|
+
appStyleManager(document).handoff(model.globalError ? [] : model.css || []);
|
|
500
|
+
renderView(model, target.href, { token });
|
|
376
501
|
}
|
|
502
|
+
if (model?.actionError) return outcome();
|
|
377
503
|
// Flight's JSON reviver removes object fields encoded as $undefined. A
|
|
378
504
|
// successful void action therefore has an updated tree but no own result.
|
|
379
505
|
if (!model || typeof model !== 'object' || (!Object.hasOwn(model, 'actionResult') && !Object.hasOwn(model, 'tree'))) {
|
|
380
506
|
throw new Error('The Server Action response did not contain a result');
|
|
381
507
|
}
|
|
382
|
-
return
|
|
508
|
+
return outcome();
|
|
383
509
|
}
|
|
384
510
|
|
|
385
511
|
async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
|
|
512
|
+
if (pageHidden) return;
|
|
386
513
|
const target = navigationUrl(href);
|
|
387
|
-
if (!isApplicationURL(target, window.location.origin, basePath)) {
|
|
514
|
+
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) {
|
|
388
515
|
if (replace) window.location.replace(target.href);
|
|
389
516
|
else window.location.assign(target.href);
|
|
390
517
|
return;
|
|
391
518
|
}
|
|
392
519
|
const token = ++navigation;
|
|
520
|
+
finishCommit?.();
|
|
521
|
+
saveScroll();
|
|
522
|
+
abandonPendingView();
|
|
393
523
|
activeRequest?.abort();
|
|
524
|
+
appStyleManager(document).cancel();
|
|
525
|
+
finishNavigation?.(); finishNavigation = undefined;
|
|
394
526
|
activeRequest = undefined;
|
|
395
|
-
saveScroll();
|
|
396
527
|
|
|
397
528
|
function updateHistory(destination) {
|
|
529
|
+
historyRouting = currentRouting;
|
|
398
530
|
if (!pop && !refresh) {
|
|
399
531
|
if (!replace) currentKey = nextKey++;
|
|
400
532
|
const position = scroll ? [0, 0] : [window.scrollX, window.scrollY];
|
|
@@ -408,38 +540,52 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
408
540
|
currentUrl = destination;
|
|
409
541
|
}
|
|
410
542
|
|
|
411
|
-
|
|
543
|
+
const committedUrl = new URL(committedView.url);
|
|
544
|
+
if (!refresh && target.pathname === currentUrl.pathname && target.search === currentUrl.search && target.hash !== currentUrl.hash &&
|
|
545
|
+
target.pathname === committedUrl.pathname && target.search === committedUrl.search) {
|
|
412
546
|
updateHistory(target);
|
|
413
547
|
if (scroll) scrollToTarget(target, savedScroll);
|
|
414
548
|
return;
|
|
415
549
|
}
|
|
416
550
|
|
|
551
|
+
const cacheKey = `${target.origin}${target.pathname}${target.search}`;
|
|
552
|
+
const candidate = !refresh && prefetches.get(cacheKey);
|
|
553
|
+
const prefetched = candidate && !candidate.abort.signal.aborted ? candidate : undefined;
|
|
554
|
+
if (candidate && !prefetched) dropPrefetch(cacheKey, true);
|
|
555
|
+
const releaseNavigation = scheduler.navigation(prefetched?.task);
|
|
556
|
+
finishNavigation = releaseNavigation;
|
|
417
557
|
try {
|
|
418
|
-
const cacheKey = `${target.origin}${target.pathname}${target.search}`;
|
|
419
|
-
const prefetched = !refresh && prefetches.get(cacheKey);
|
|
420
558
|
let result;
|
|
421
559
|
let historyUpdated = false;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
560
|
+
// Always authorize history navigation again. Mounted branches can retain
|
|
561
|
+
// UI state while fresh Flight verifies cookies, middleware and rewrites.
|
|
562
|
+
if (prefetched) {
|
|
425
563
|
activeRequest = prefetched.abort;
|
|
426
564
|
dropPrefetch(cacheKey);
|
|
427
|
-
result = await prefetched.promise;
|
|
565
|
+
try { result = await prefetched.promise; }
|
|
566
|
+
catch (error) { if (token !== navigation || (prefetched.abort.signal.aborted && !prefetched.failed)) throw error; result = null; }
|
|
428
567
|
if (result && !result.partial) {
|
|
429
|
-
|
|
430
|
-
|
|
568
|
+
// Full prefetch warms modules/data, not a future authorization decision.
|
|
569
|
+
// Static Flight has no authorizationKey, but its proxy checks can still
|
|
570
|
+
// change when an HttpOnly cookie is revoked before this click.
|
|
571
|
+
const abort = new AbortController(); activeRequest = abort;
|
|
572
|
+
result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : currentRouting);
|
|
431
573
|
} else {
|
|
432
574
|
const preview = result;
|
|
433
575
|
const abort = new AbortController();
|
|
434
576
|
activeRequest = abort;
|
|
435
|
-
result = await
|
|
577
|
+
result = await navigateFlight(target, abort.signal, currentRouting, async (response, destination) => {
|
|
436
578
|
if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
|
|
437
579
|
(preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
|
|
438
580
|
// Auth, middleware and rewrites ran again. A changed target or a
|
|
439
581
|
// redirect cannot reveal an obsolete protected prefetched segment.
|
|
440
582
|
if (destination.pathname !== preview.url.pathname || destination.search !== preview.url.search) return;
|
|
583
|
+
prepareLayoutCache(preview.model, currentModel);
|
|
584
|
+
await appStyleManager(document).prepare(preview.model.css || [], abort.signal);
|
|
585
|
+
if (token !== navigation || abort.signal.aborted) return;
|
|
441
586
|
updateHistory(destination); historyUpdated = true;
|
|
442
|
-
|
|
587
|
+
appStyleManager(document).handoff(preview.model.css || []);
|
|
588
|
+
renderView(preview.model, destination.href, { token, canonical: false });
|
|
443
589
|
});
|
|
444
590
|
}
|
|
445
591
|
} else {
|
|
@@ -448,54 +594,107 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
448
594
|
// A refresh is already inside the compiler's apply transaction. Calling
|
|
449
595
|
// ensureRoute from here can wait for that same transaction forever when
|
|
450
596
|
// this tree has no routing state (the ordinary single-route case).
|
|
451
|
-
result = await
|
|
597
|
+
result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
|
|
452
598
|
}
|
|
453
599
|
if (token !== navigation) return;
|
|
454
600
|
if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
|
|
455
601
|
if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
|
|
456
602
|
prepareLayoutCache(result.model, currentModel);
|
|
603
|
+
await appStyleManager(document).prepare(result.model.globalError ? [] : result.model.css || [], activeRequest?.signal);
|
|
604
|
+
if (token !== navigation) return;
|
|
457
605
|
currentRouting = result.model.routing;
|
|
458
606
|
currentModel = result.model;
|
|
607
|
+
historyRouting = currentRouting;
|
|
459
608
|
if (currentRouting) invalidatePrefetches();
|
|
460
609
|
if (!historyUpdated) updateHistory(result.url);
|
|
461
|
-
if (currentRouting) historyModels.set(currentKey, currentModel);
|
|
462
|
-
while (historyModels.size > 16) historyModels.delete(historyModels.keys().next().value);
|
|
463
610
|
const committed = () => {
|
|
464
|
-
if (token === navigation && scroll && !refresh)
|
|
611
|
+
if (token === navigation && scroll && !refresh) {
|
|
612
|
+
scrollToTarget(result.url, savedScroll);
|
|
613
|
+
const focus = pop && historyFocus.get(currentKey)?.deref();
|
|
614
|
+
if (focus?.isConnected && focus.getClientRects().length) focus.focus({ preventScroll: true });
|
|
615
|
+
}
|
|
465
616
|
};
|
|
466
|
-
|
|
467
|
-
|
|
617
|
+
appStyleManager(document).handoff(result.model.globalError ? [] : result.model.css || []);
|
|
618
|
+
if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>{
|
|
619
|
+
const finish=()=>{if(finishCommit===finish)finishCommit=undefined;resolve();};
|
|
620
|
+
finishCommit=finish;
|
|
621
|
+
renderView(result.model, result.url.href, { token, committed: () => { committed(); finish(); } });
|
|
622
|
+
});
|
|
623
|
+
else renderView(result.model, result.url.href, { token, committed });
|
|
468
624
|
} catch (error) {
|
|
469
625
|
if (token !== navigation || error?.name === 'AbortError') return;
|
|
626
|
+
if(error?.code==='PRNEXT_NAVIGATION_NETWORK'){
|
|
627
|
+
window.dispatchEvent(new CustomEvent('prnext:navigation-error',{detail:{message:error.message}}));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
470
630
|
// Pages Router routes, public files, and unavailable Flight endpoints use
|
|
471
631
|
// a normal document navigation, including the browser's redirect handling.
|
|
472
632
|
if (replace || pop || refresh) window.location.replace(target.href);
|
|
473
633
|
else window.location.assign(target.href);
|
|
634
|
+
} finally {
|
|
635
|
+
if (finishNavigation === releaseNavigation) finishNavigation = undefined;
|
|
636
|
+
releaseNavigation();
|
|
474
637
|
}
|
|
475
638
|
}
|
|
476
639
|
|
|
640
|
+
function purgeRetainedPages() {
|
|
641
|
+
saveScroll();
|
|
642
|
+
abandonPendingView();
|
|
643
|
+
controller._retentionEpoch++;
|
|
644
|
+
historyFocus.clear();
|
|
645
|
+
// This only updates retention for the already committed model. It must
|
|
646
|
+
// not become a new pending Flight view or a development commit barrier.
|
|
647
|
+
startTransition(() => root.render(appView({ model: currentModel, controller, url: committedView.url, globalErrorComponent, globalErrorCss })));
|
|
648
|
+
}
|
|
649
|
+
|
|
477
650
|
const controller = {
|
|
478
|
-
|
|
651
|
+
_retentionEpoch: 0,
|
|
652
|
+
_authorizationEpoch: 0,
|
|
653
|
+
_navigationCache: navigationCache,
|
|
654
|
+
partialPrefetch: (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__),
|
|
479
655
|
push: (href, options) => { void navigate(navigationUrl(href), options); },
|
|
480
656
|
replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
|
|
481
657
|
refresh: () => {
|
|
482
658
|
invalidatePrefetches();
|
|
659
|
+
purgeRetainedPages();
|
|
483
660
|
void navigate(new URL(window.location.href), { refresh: true });
|
|
484
661
|
},
|
|
485
662
|
back: () => { saveScroll(); window.history.back(); },
|
|
486
663
|
forward: () => { saveScroll(); window.history.forward(); },
|
|
487
664
|
prefetch(href, options = {}) {
|
|
665
|
+
if (pageHidden) return;
|
|
488
666
|
const target = navigationUrl(href);
|
|
489
|
-
if (!isApplicationURL(target, window.location.origin, basePath)) return;
|
|
667
|
+
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) return;
|
|
490
668
|
target.hash = '';
|
|
491
669
|
const key = target.href;
|
|
492
|
-
|
|
493
|
-
|
|
670
|
+
const priority = options.priority === 'viewport' ? 0 : 1;
|
|
671
|
+
const previous = prefetches.get(key);
|
|
672
|
+
if (previous && !previous.abort.signal.aborted && (!options.full || previous.full)) {
|
|
673
|
+
previous.priority = Math.max(previous.priority, priority);
|
|
674
|
+
previous.task.promote(options.priority);
|
|
675
|
+
if (typeof options.onInvalidate === 'function') previous.invalidations.add(options.onInvalidate);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (previous) dropPrefetch(key, true);
|
|
679
|
+
// Self-rearming callbacks may fill free slots, but cannot keep evicting
|
|
680
|
+
// each other forever when more warm routes compete for a bounded cache.
|
|
681
|
+
if (notifyingPrefetch && prefetches.size >= PREFETCH_LIMIT) return;
|
|
682
|
+
const victim = prefetches.size >= PREFETCH_LIMIT ? prefetchCacheVictim(prefetches, priority) : undefined;
|
|
683
|
+
if (prefetches.size >= PREFETCH_LIMIT && victim === undefined) return;
|
|
494
684
|
const abort = new AbortController();
|
|
495
|
-
const entry = { abort,
|
|
496
|
-
entry.
|
|
685
|
+
const entry = { abort, priority, full: options.full === true, invalidations: new Set(typeof options.onInvalidate === 'function' ? [options.onInvalidate] : []) };
|
|
686
|
+
entry.task = scheduler.schedule(() => (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) && !window.__PRNEXT_STATIC_EXPORT__
|
|
687
|
+
? options.full && !partialPrefetching ? requestFlight(target, abort.signal) : requestPartial(target, abort.signal, options.full) : requestFlight(target, abort.signal), { abort, priority: options.priority });
|
|
688
|
+
if (!entry.task) return;
|
|
689
|
+
if (victim !== undefined) dropPrefetch(victim, true);
|
|
690
|
+
entry.promise = entry.task.promise;
|
|
497
691
|
// A failed speculative fetch must never produce an unhandled rejection.
|
|
498
|
-
entry.promise.catch(
|
|
692
|
+
entry.promise.catch(error => {
|
|
693
|
+
// An intentional cancellation is not a cache miss to retry. In
|
|
694
|
+
// particular pagehide must never create a replacement Flight request.
|
|
695
|
+
entry.failed = !abort.signal.aborted && error?.name !== 'AbortError';
|
|
696
|
+
if (prefetches.get(key) === entry) dropPrefetch(key, true);
|
|
697
|
+
});
|
|
499
698
|
entry.timer = setTimeout(() => dropPrefetch(key, true), PREFETCH_LIFETIME);
|
|
500
699
|
prefetches.set(key, entry);
|
|
501
700
|
},
|
|
@@ -528,16 +727,36 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
528
727
|
window.addEventListener('popstate', event => {
|
|
529
728
|
void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
|
|
530
729
|
});
|
|
531
|
-
window.addEventListener('pagehide', saveScroll);
|
|
730
|
+
window.addEventListener('pagehide', () => { saveScroll(); pageHidden = true; navigation++; finishCommit?.(); abandonPendingView(); activeRequest?.abort(); appStyleManager(document).cancel(); finishNavigation?.(); finishNavigation = undefined; invalidatePrefetches(); });
|
|
731
|
+
window.addEventListener('pageshow', event => { pageHidden = false; if (event.persisted) controller.refresh(); });
|
|
732
|
+
window.addEventListener('prnext:invalidate-navigation', () => { controller._authorizationEpoch++; controller.refresh(); });
|
|
532
733
|
let scrollFrame;
|
|
533
734
|
window.addEventListener('scroll', () => {
|
|
534
735
|
if (scrollFrame !== undefined) return;
|
|
535
736
|
scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
|
|
536
737
|
}, { passive: true });
|
|
738
|
+
function developmentStyles(model, routes) {
|
|
739
|
+
const route = routes?.find(route => route.pattern === model.styleRoute);
|
|
740
|
+
if (!route) return model;
|
|
741
|
+
const scopes = routes.flatMap(route => route.cssScopes || []);
|
|
742
|
+
const previous = { routing:model.routing, cssScopes:(model.cssScopes || []).map(scope => scopes.find(current => current.path === scope.path && current.kind === scope.kind) || scope) };
|
|
743
|
+
const next = { ...model, css:route.css, cssScopes:route.cssScopes };
|
|
744
|
+
retainAppStyles(next, previous);
|
|
745
|
+
return next;
|
|
746
|
+
}
|
|
537
747
|
return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
|
|
538
748
|
devReady,
|
|
749
|
+
devStyles: routes => developmentStyles(currentModel, routes).css || [],
|
|
750
|
+
devPrepareStyles: routes => appStyleManager(document).prepare(developmentStyles(currentModel, routes).css || []),
|
|
751
|
+
devStylesApplied: (routes, { commit = true } = {}) => {
|
|
752
|
+
for (const model of new Set([currentModel, committedView.model, pendingView?.model].filter(Boolean))) {
|
|
753
|
+
const next = developmentStyles(model, routes);
|
|
754
|
+
model.css = next.css; model.cssScopes = next.cssScopes;
|
|
755
|
+
}
|
|
756
|
+
if (commit) appStyleManager(document).commit(currentModel.globalError ? [] : currentModel.css || []);
|
|
757
|
+
},
|
|
539
758
|
devPrepare: options => moduleLoader.updateModules(options.clientModules),
|
|
540
|
-
devCommit: () => {invalidatePrefetches();return navigate(new URL(window.location.href),{refresh:true});},
|
|
759
|
+
devCommit: () => {invalidatePrefetches();purgeRetainedPages();return navigate(new URL(window.location.href),{refresh:true});},
|
|
541
760
|
} : {}) };
|
|
542
761
|
}
|
|
543
762
|
|
|
@@ -545,6 +764,11 @@ export function bootstrapApp(options) {
|
|
|
545
764
|
if (process.env.NODE_ENV === 'development' && options.dev && globalThis.__PRNEXT_DEV__) {
|
|
546
765
|
return globalThis.__PRNEXT_DEV__.bootstrap('app', options, () => startApp(options));
|
|
547
766
|
}
|
|
548
|
-
|
|
767
|
+
if (!application) {
|
|
768
|
+
application = startApp(options);
|
|
769
|
+
// Generated entries intentionally do not await startup. Report a failed
|
|
770
|
+
// chunk once, while retaining the original rejection for explicit callers.
|
|
771
|
+
void application.catch(error => console.error('[PRNext] App startup failed:', error));
|
|
772
|
+
}
|
|
549
773
|
return application;
|
|
550
774
|
}
|