@thomas.f/prnext 0.1.16-alpha → 0.1.18-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/AI.md +63 -0
- package/README.md +35 -503
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +6 -1
- package/build/analyze.mjs +340 -0
- package/build/app-config.mjs +20 -8
- package/build/app-static.mjs +41 -14
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +99 -31
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +39 -11
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/check.mjs +56 -11
- 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 +51 -4
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +50 -13
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +65 -10
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +85 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +64 -6
- package/build/dynamic.mjs +1 -1
- package/build/edge.mjs +5 -4
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +50 -0
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +140 -112
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/instrumentation.mjs +30 -0
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +3 -4
- package/build/migrate.mjs +60 -57
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +112 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +100 -0
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +129 -0
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-export.mjs +4 -4
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +17 -3
- package/build/transform-cache.mjs +37 -4
- package/build/transform.mjs +1 -1
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +95 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +49 -0
- 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-watch.mjs +22 -0
- package/build/typescript.mjs +11 -8
- package/build/webpack-graph.mjs +170 -30
- package/build/worker-memory.mjs +9 -0
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +162 -122
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- 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 +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +89 -10
- package/compat/link.cjs +12 -9
- package/compat/link.d.cts +4 -3
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/navigation.d.cts +4 -3
- 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/router.d.cts +4 -3
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/typed-route.d.cts +17 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +275 -0
- package/docs/ai/data.md +288 -0
- package/docs/ai/deployment.md +269 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +171 -0
- package/docs/ai/routing.md +214 -0
- package/docs/ai/troubleshooting.md +155 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +44 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +226 -68
- 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 +100 -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/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +245 -46
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +191 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +34 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +324 -0
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/env.mjs +2 -1
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- 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/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +24 -8
- package/runtime/pages-navigation.mjs +54 -64
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/preload.mjs +12 -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 +41 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +29 -18
- package/runtime/worker.mjs +102 -24
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;
|
|
@@ -88,8 +96,7 @@ function embeddedFlight() {
|
|
|
88
96
|
document.removeEventListener('DOMContentLoaded', onComplete);
|
|
89
97
|
controller.close();
|
|
90
98
|
} else {
|
|
91
|
-
|
|
92
|
-
controller.enqueue(Uint8Array.from(binary, character => character.charCodeAt(0)));
|
|
99
|
+
controller.enqueue(decodeBase64(value));
|
|
93
100
|
}
|
|
94
101
|
};
|
|
95
102
|
onComplete = () => {
|
|
@@ -108,28 +115,27 @@ function embeddedFlight() {
|
|
|
108
115
|
}
|
|
109
116
|
const element = document.getElementById('__PRNEXT_FLIGHT__');
|
|
110
117
|
if (!element) throw new Error('PRNext App Router Flight payload is missing');
|
|
111
|
-
const
|
|
112
|
-
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
118
|
+
const bytes = decodeBase64(element.textContent.trim());
|
|
113
119
|
element.remove();
|
|
114
120
|
return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
function NormalStyles({ css, fonts }) {
|
|
118
|
-
const [styles, setStyles] = React.useState([]);
|
|
119
124
|
useLayoutEffect(() => {
|
|
120
125
|
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])]);
|
|
126
|
+
const styles = appStyleManager(document);
|
|
127
|
+
styles.commit(css || []);
|
|
128
|
+
return () => styles.release();
|
|
127
129
|
}, [css, fonts]);
|
|
128
|
-
return
|
|
130
|
+
return null;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss }) {
|
|
133
|
+
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss, retentionEpoch, retentionAuthorizationEpoch }) {
|
|
132
134
|
const [hydrated, setHydrated] = React.useState(false);
|
|
135
|
+
const cache = React.useRef(null);
|
|
136
|
+
if (!cache.current) cache.current = createActivityCache({ limit: controller._navigationCache?.maxEntries, ttl: controller._navigationCache?.ttlMs });
|
|
137
|
+
const activityCache = React.useMemo(() => ({ cache: cache.current, epoch: retentionEpoch, authorizationEpoch: retentionAuthorizationEpoch,
|
|
138
|
+
authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey]);
|
|
133
139
|
const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
|
|
134
140
|
useLayoutEffect(() => {
|
|
135
141
|
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 +151,11 @@ function AppRoot({ model, controller, committed, url, globalErrorComponent, glob
|
|
|
145
151
|
return React.createElement(AppRouterProvider, { router, controller, ...(model.partialPreview ? { prerenderSearch: previewDynamic, ...(model.partialPreview.unknownParams ? { prerenderParams: previewDynamic } : {}) } : {}) },
|
|
146
152
|
React.createElement(GlobalErrorBoundary, { component: globalErrorComponent, css: globalErrorCss, resetKey: model },
|
|
147
153
|
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 }) })));
|
|
154
|
+
: appContent(model.tree, { layoutCache: model.layoutCache || null, activityCache, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
function prepareLayoutCache(model, previous) {
|
|
158
|
+
retainAppStyles(model, previous);
|
|
152
159
|
if (!model.routing || model.layoutCache) return model;
|
|
153
160
|
const active = new Set(Object.keys(model.routing.slots));
|
|
154
161
|
model.layoutCache = {
|
|
@@ -166,7 +173,7 @@ function AppView(props) {
|
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
function appView(props) {
|
|
169
|
-
return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, props));
|
|
176
|
+
return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, { ...props, retentionEpoch: props.controller?._retentionEpoch || 0, retentionAuthorizationEpoch: props.controller?._authorizationEpoch || 0 }));
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
function historyState(key, scroll, routing) {
|
|
@@ -179,7 +186,7 @@ function scrollToTarget(url, saved) {
|
|
|
179
186
|
if (url.hash) {
|
|
180
187
|
let name = url.hash.slice(1);
|
|
181
188
|
try { name = decodeURIComponent(name); } catch { /* A literal malformed escape can be an element ID. */ }
|
|
182
|
-
const anchor = document.
|
|
189
|
+
const anchor = [...document.querySelectorAll('[id], [name]')].find(element => (element.id === name || element.getAttribute('name') === name) && element.getClientRects().length);
|
|
183
190
|
if (anchor) { anchor.scrollIntoView(); return; }
|
|
184
191
|
}
|
|
185
192
|
window.scrollTo(0, 0);
|
|
@@ -191,7 +198,7 @@ function assertModel(model) {
|
|
|
191
198
|
return model;
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
async function startApp({ clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false } = {}) {
|
|
201
|
+
async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false, partialPrefetching = false, navigationCache } = {}) {
|
|
195
202
|
strictMode = enabledStrictMode;
|
|
196
203
|
await loadBeforeInteractive();
|
|
197
204
|
basePath = normalizeBasePath(basePath);
|
|
@@ -201,8 +208,18 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
201
208
|
await globalThis.__webpack_chunk_load__(globalErrorId);
|
|
202
209
|
return { default: globalThis.__webpack_require__(globalErrorId).default };
|
|
203
210
|
}) : DefaultGlobalError;
|
|
211
|
+
// The production browser graph fixes this feature at build time. The
|
|
212
|
+
// fallback also keeps direct, unbundled runtime consumers functional.
|
|
213
|
+
const partialRuntime = typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined'
|
|
214
|
+
? cacheComponents ? import('./app-partial-client.mjs') : null
|
|
215
|
+
: __PRNEXT_CACHE_COMPONENTS__ ? import('./app-partial-client.mjs') : null;
|
|
204
216
|
// The Flight adapter reads __webpack_require__.u during module evaluation.
|
|
205
|
-
|
|
217
|
+
// Both imports start concurrently and receive rejection handlers in this
|
|
218
|
+
// turn. A rejected PPR chunk must not wait unhandled for a slow Flight chunk.
|
|
219
|
+
const [flightClient, partial] = await Promise.all([import('react-server-dom-webpack/client.browser'), partialRuntime]);
|
|
220
|
+
// Ordinary App routes do not need the resumable PPR model. Keep it out of
|
|
221
|
+
// their initial module graph; Cache Components still loads it before use.
|
|
222
|
+
const { partialModel, readPartialPrefetch, partialFlightStream } = partial || {};
|
|
206
223
|
const { createFromFetch, createFromReadableStream, createServerReference, createTemporaryReferenceSet, encodeReply } = flightClient.default || flightClient;
|
|
207
224
|
let root;
|
|
208
225
|
let actionQueue = Promise.resolve();
|
|
@@ -222,16 +239,15 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
222
239
|
let currentUrl = new URL(window.location.href);
|
|
223
240
|
let currentRouting = initialModel.routing;
|
|
224
241
|
let currentModel = initialModel;
|
|
225
|
-
const
|
|
242
|
+
const historyFocus = new Map();
|
|
226
243
|
let currentKey = window.history.state?.__prnext?.key || 0;
|
|
227
244
|
let nextKey = currentKey + 1;
|
|
228
245
|
let navigation = 0;
|
|
229
|
-
let activeRequest;
|
|
230
|
-
const prefetches = new Map();
|
|
231
|
-
const partialSegments = new Map();
|
|
246
|
+
let activeRequest, finishNavigation, pageHidden = false, notifyingPrefetch = false;
|
|
247
|
+
const prefetches = new Map(), scheduler = createPrefetchScheduler();
|
|
248
|
+
const partialSegments = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? new Map() : null;
|
|
232
249
|
let segmentBytes = 0;
|
|
233
250
|
|
|
234
|
-
if (currentRouting) historyModels.set(currentKey, initialModel);
|
|
235
251
|
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
|
|
236
252
|
window.history.scrollRestoration = 'manual';
|
|
237
253
|
|
|
@@ -239,42 +255,62 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
239
255
|
// popstate has already changed location; never overwrite its destination.
|
|
240
256
|
if (window.location.href === currentUrl.href) {
|
|
241
257
|
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
|
|
258
|
+
if (typeof WeakRef !== 'undefined' && document.activeElement && document.activeElement !== document.body) historyFocus.set(currentKey, new WeakRef(document.activeElement));
|
|
259
|
+
while (historyFocus.size > 16) historyFocus.delete(historyFocus.keys().next().value);
|
|
242
260
|
}
|
|
243
261
|
}
|
|
244
262
|
|
|
245
|
-
|
|
263
|
+
const flightTransport = createFlightTransport();
|
|
264
|
+
async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false,fullFlight=false}={}) {
|
|
265
|
+
if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
|
|
246
266
|
const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
|
|
247
|
-
const
|
|
267
|
+
const base = !exported && !fullFlight ? flightTransport.snapshot() : undefined;
|
|
268
|
+
const target = exported ? new URL(basePath + '/_prnext/flight' + (deploymentId ? '/' + encodeURIComponent(deploymentId) : '') + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
|
|
248
269
|
const response = await fetch(target.href, {
|
|
249
|
-
headers: { RSC: '1', Accept: 'text/x-component', ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
|
|
270
|
+
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)) } : {}) },
|
|
250
271
|
credentials: 'same-origin',
|
|
251
272
|
signal,
|
|
252
273
|
});
|
|
274
|
+
if (!exported) await assertDeploymentResponse(response, deploymentId);
|
|
253
275
|
let destination;
|
|
254
276
|
try { destination = exported ? url : flightResponseURL(response, url); }
|
|
255
277
|
catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
|
|
256
|
-
|
|
278
|
+
const delta = response.headers.get('content-type')?.startsWith(FLIGHT_DELTA_TYPE);
|
|
279
|
+
if (exported ? !response.ok : !(delta||response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component'))) {
|
|
257
280
|
await response.body?.cancel();
|
|
258
281
|
throw new Error('The navigation destination requires a document request');
|
|
259
282
|
}
|
|
260
|
-
onPartial?.(response, destination);
|
|
261
|
-
|
|
283
|
+
await onPartial?.(response, destination);
|
|
284
|
+
let model;
|
|
285
|
+
try { model = assertModel(await createFromFetch(Promise.resolve(exported?response:await flightTransport.response(response,base)), flightOptions)); }
|
|
286
|
+
catch(error) {
|
|
287
|
+
if(!delta||fullFlight||signal?.aborted)throw error;
|
|
288
|
+
flightTransport.clear();
|
|
289
|
+
return requestFlight(url,signal,routing,onPartial,{skipEnsure:true,fullFlight:true});
|
|
290
|
+
}
|
|
262
291
|
model.router.basePath = basePath;
|
|
263
292
|
const rewrite = readRewriteHeader(response);
|
|
264
293
|
if (rewrite) model.rewrite = rewrite;
|
|
265
294
|
return { model, url: destination };
|
|
266
295
|
}
|
|
267
296
|
|
|
268
|
-
|
|
297
|
+
const navigateFlight=(url,signal,...args)=>recoverNavigation(recoverySignal=>requestFlight(url,recoverySignal,...args),{signal});
|
|
298
|
+
|
|
299
|
+
const requestPartial = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? async function requestPartial(url, signal, full = false) {
|
|
269
300
|
const response = await fetch(url.href, { credentials: 'same-origin', signal,
|
|
270
|
-
headers: { RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
|
|
301
|
+
headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
|
|
302
|
+
...(partialPrefetching ? { 'x-prnext-prefetch-stage': full ? 'prefetch' : 'shell' } : {}),
|
|
271
303
|
...(partialSegments.size ? { 'x-prnext-prefetch-known': [...partialSegments.keys()].join(',') } : {}) } });
|
|
272
|
-
|
|
304
|
+
await assertDeploymentResponse(response, deploymentId);
|
|
305
|
+
let destination;
|
|
306
|
+
try { destination = flightResponseURL(response, url); }
|
|
307
|
+
catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
|
|
273
308
|
if (!response.headers.get('content-type')?.startsWith(PARTIAL_PREFETCH_TYPE)) {
|
|
274
309
|
await response.body?.cancel();
|
|
275
310
|
return null;
|
|
276
311
|
}
|
|
277
312
|
const value = await readPartialPrefetch(response);
|
|
313
|
+
if (signal.aborted) return null;
|
|
278
314
|
let segment = partialSegments.get(value.id);
|
|
279
315
|
if (!segment && value.flight) {
|
|
280
316
|
const bytes = value.flight.length;
|
|
@@ -293,11 +329,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
293
329
|
// No visitor data is decoded here. Explicit postponed errors become pending
|
|
294
330
|
// boundaries until the navigation's independently authorized Flight arrives.
|
|
295
331
|
const suspended = new Promise(() => {});
|
|
296
|
-
const
|
|
332
|
+
const decoded = assertModel(await segment.model);
|
|
333
|
+
if (signal.aborted) return null;
|
|
334
|
+
const model = partialModel(decoded, { keyMap: new Map(value.keys), suspended });
|
|
297
335
|
model.router = { ...value.router, basePath };
|
|
298
336
|
model.partialPreview = { unknownParams: value.unknownParams, suspended };
|
|
299
337
|
return { model, id: value.id, url: destination, partial: true };
|
|
300
|
-
}
|
|
338
|
+
} : null;
|
|
301
339
|
|
|
302
340
|
function dropPrefetch(key, invalidate = false) {
|
|
303
341
|
const entry = prefetches.get(key);
|
|
@@ -306,13 +344,22 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
306
344
|
clearTimeout(entry.timer);
|
|
307
345
|
if (invalidate) {
|
|
308
346
|
entry.abort.abort();
|
|
309
|
-
entry.
|
|
347
|
+
const callbacks = entry.invalidations; entry.invalidations = undefined;
|
|
348
|
+
// Callbacks may rearm prefetch. Deliver after cache/queue mutations so a
|
|
349
|
+
// replacement cannot be evicted inside the same invalidation transaction.
|
|
350
|
+
if (callbacks?.size) queueMicrotask(() => {
|
|
351
|
+
for (const callback of callbacks) {
|
|
352
|
+
notifyingPrefetch = true;
|
|
353
|
+
try { callback(); } catch (error) { console.error(error); }
|
|
354
|
+
finally { notifyingPrefetch = false; }
|
|
355
|
+
}
|
|
356
|
+
});
|
|
310
357
|
}
|
|
311
358
|
}
|
|
312
359
|
|
|
313
360
|
function invalidatePrefetches() {
|
|
314
|
-
partialSegments.clear(); segmentBytes = 0;
|
|
315
|
-
for (const key of prefetches.
|
|
361
|
+
if (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) { partialSegments.clear(); segmentBytes = 0; }
|
|
362
|
+
for (const [key, entry] of [...prefetches]) if (prefetches.get(key) === entry) dropPrefetch(key, true);
|
|
316
363
|
}
|
|
317
364
|
|
|
318
365
|
function callServer(id, args) {
|
|
@@ -320,7 +367,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
320
367
|
const token = ++navigation;
|
|
321
368
|
const target = new URL(window.location.href);
|
|
322
369
|
activeRequest?.abort();
|
|
370
|
+
appStyleManager(document).cancel();
|
|
371
|
+
finishNavigation?.(); finishNavigation = undefined;
|
|
323
372
|
activeRequest = undefined;
|
|
373
|
+
purgeRetainedPages();
|
|
324
374
|
// Mutations are dispatched in order, including Set-Cookie effects required
|
|
325
375
|
// by a subsequent action. Capture their route priority when invoked: a
|
|
326
376
|
// queued action must not cancel a newer user navigation when it starts.
|
|
@@ -330,12 +380,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
330
380
|
}
|
|
331
381
|
|
|
332
382
|
async function dispatchAction(id, args, token, target) {
|
|
383
|
+
const release = scheduler.navigation();
|
|
384
|
+
try { return await performAction(id, args, token, target); } finally { release(); }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function performAction(id, args, token, target) {
|
|
333
388
|
invalidatePrefetches();
|
|
334
389
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
335
390
|
const body = await encodeReply(args, { temporaryReferences });
|
|
336
391
|
const response = await fetch(target.href, {
|
|
337
392
|
method: 'POST',
|
|
338
|
-
headers: { 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
393
|
+
headers: { ...deploymentHeaders(deploymentId), 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
339
394
|
credentials: 'same-origin',
|
|
340
395
|
// RPC redirects arrive as Flight data. Never replay a mutation through an
|
|
341
396
|
// unexpected HTTP 307/308 or redirect an authenticated POST off origin.
|
|
@@ -343,11 +398,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
343
398
|
body,
|
|
344
399
|
});
|
|
345
400
|
invalidatePrefetches();
|
|
401
|
+
await assertDeploymentResponse(response, deploymentId);
|
|
346
402
|
if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
|
|
347
403
|
await response.body?.cancel();
|
|
348
404
|
throw new Error(`Server Action request failed (${response.status})`);
|
|
349
405
|
}
|
|
350
406
|
const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
|
|
407
|
+
controller._retentionEpoch++;
|
|
351
408
|
if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
|
|
352
409
|
if (token === navigation) window.location.replace(target.href);
|
|
353
410
|
return model.actionResult;
|
|
@@ -363,9 +420,12 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
363
420
|
const rewrite = readRewriteHeader(response);
|
|
364
421
|
if (rewrite) model.rewrite = rewrite;
|
|
365
422
|
prepareLayoutCache(model, currentModel);
|
|
423
|
+
try { await appStyleManager(document).prepare(model.globalError ? [] : model.css || []); }
|
|
424
|
+
catch (error) { if (token !== navigation) return model.actionResult; throw error; }
|
|
425
|
+
if (token !== navigation) return model.actionResult;
|
|
366
426
|
currentRouting = model.routing;
|
|
367
427
|
currentModel = model;
|
|
368
|
-
|
|
428
|
+
appStyleManager(document).handoff(model.globalError ? [] : model.css || []);
|
|
369
429
|
startTransition(() => root.render(appView({ model, controller, url: target.href, globalErrorComponent, globalErrorCss })));
|
|
370
430
|
}
|
|
371
431
|
if (model?.actionError) {
|
|
@@ -382,14 +442,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
382
442
|
}
|
|
383
443
|
|
|
384
444
|
async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
|
|
445
|
+
if (pageHidden) return;
|
|
385
446
|
const target = navigationUrl(href);
|
|
386
|
-
if (!isApplicationURL(target, window.location.origin, basePath)) {
|
|
447
|
+
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) {
|
|
387
448
|
if (replace) window.location.replace(target.href);
|
|
388
449
|
else window.location.assign(target.href);
|
|
389
450
|
return;
|
|
390
451
|
}
|
|
391
452
|
const token = ++navigation;
|
|
392
453
|
activeRequest?.abort();
|
|
454
|
+
appStyleManager(document).cancel();
|
|
455
|
+
finishNavigation?.(); finishNavigation = undefined;
|
|
393
456
|
activeRequest = undefined;
|
|
394
457
|
saveScroll();
|
|
395
458
|
|
|
@@ -413,90 +476,156 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
413
476
|
return;
|
|
414
477
|
}
|
|
415
478
|
|
|
479
|
+
const cacheKey = `${target.origin}${target.pathname}${target.search}`;
|
|
480
|
+
const candidate = !refresh && prefetches.get(cacheKey);
|
|
481
|
+
const prefetched = candidate && !candidate.abort.signal.aborted ? candidate : undefined;
|
|
482
|
+
if (candidate && !prefetched) dropPrefetch(cacheKey, true);
|
|
483
|
+
const releaseNavigation = scheduler.navigation(prefetched?.task);
|
|
484
|
+
finishNavigation = releaseNavigation;
|
|
416
485
|
try {
|
|
417
|
-
const cacheKey = `${target.origin}${target.pathname}${target.search}`;
|
|
418
|
-
const prefetched = !refresh && prefetches.get(cacheKey);
|
|
419
486
|
let result;
|
|
420
487
|
let historyUpdated = false;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
488
|
+
// Always authorize history navigation again. Mounted branches can retain
|
|
489
|
+
// UI state while fresh Flight verifies cookies, middleware and rewrites.
|
|
490
|
+
if (prefetched) {
|
|
424
491
|
activeRequest = prefetched.abort;
|
|
425
492
|
dropPrefetch(cacheKey);
|
|
426
|
-
result = await prefetched.promise;
|
|
493
|
+
try { result = await prefetched.promise; }
|
|
494
|
+
catch (error) { if (token !== navigation || (prefetched.abort.signal.aborted && !prefetched.failed)) throw error; result = null; }
|
|
427
495
|
if (result && !result.partial) {
|
|
428
|
-
|
|
429
|
-
|
|
496
|
+
if (result.model.authorizationKey) {
|
|
497
|
+
// A full private prefetch warmed modules/data, not authorization.
|
|
498
|
+
// HttpOnly cookies may have changed since it was produced.
|
|
499
|
+
const abort = new AbortController(); activeRequest = abort;
|
|
500
|
+
result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : currentRouting);
|
|
501
|
+
} else {
|
|
502
|
+
result = { ...result, url: new URL(result.url.href) };
|
|
503
|
+
if (!result.url.hash) result.url.hash = target.hash;
|
|
504
|
+
}
|
|
430
505
|
} else {
|
|
431
506
|
const preview = result;
|
|
432
507
|
const abort = new AbortController();
|
|
433
508
|
activeRequest = abort;
|
|
434
|
-
result = await
|
|
509
|
+
result = await navigateFlight(target, abort.signal, currentRouting, async (response, destination) => {
|
|
435
510
|
if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
|
|
436
511
|
(preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
|
|
437
512
|
// Auth, middleware and rewrites ran again. A changed target or a
|
|
438
513
|
// redirect cannot reveal an obsolete protected prefetched segment.
|
|
439
514
|
if (destination.pathname !== preview.url.pathname || destination.search !== preview.url.search) return;
|
|
515
|
+
prepareLayoutCache(preview.model, currentModel);
|
|
516
|
+
await appStyleManager(document).prepare(preview.model.css || [], abort.signal);
|
|
517
|
+
if (token !== navigation || abort.signal.aborted) return;
|
|
440
518
|
updateHistory(destination); historyUpdated = true;
|
|
519
|
+
appStyleManager(document).handoff(preview.model.css || []);
|
|
441
520
|
startTransition(() => root.render(appView({ model: preview.model, controller, url: destination.href, globalErrorComponent, globalErrorCss })));
|
|
442
521
|
});
|
|
443
522
|
}
|
|
444
523
|
} else {
|
|
445
524
|
const abort = new AbortController();
|
|
446
525
|
activeRequest = abort;
|
|
447
|
-
|
|
526
|
+
// A refresh is already inside the compiler's apply transaction. Calling
|
|
527
|
+
// ensureRoute from here can wait for that same transaction forever when
|
|
528
|
+
// this tree has no routing state (the ordinary single-route case).
|
|
529
|
+
result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
|
|
448
530
|
}
|
|
449
531
|
if (token !== navigation) return;
|
|
450
532
|
if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
|
|
451
533
|
if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
|
|
452
534
|
prepareLayoutCache(result.model, currentModel);
|
|
535
|
+
await appStyleManager(document).prepare(result.model.globalError ? [] : result.model.css || [], activeRequest?.signal);
|
|
536
|
+
if (token !== navigation) return;
|
|
453
537
|
currentRouting = result.model.routing;
|
|
454
538
|
currentModel = result.model;
|
|
455
539
|
if (currentRouting) invalidatePrefetches();
|
|
456
540
|
if (!historyUpdated) updateHistory(result.url);
|
|
457
|
-
if (currentRouting) historyModels.set(currentKey, currentModel);
|
|
458
|
-
while (historyModels.size > 16) historyModels.delete(historyModels.keys().next().value);
|
|
459
541
|
const committed = () => {
|
|
460
|
-
if (token === navigation && scroll && !refresh)
|
|
542
|
+
if (token === navigation && scroll && !refresh) {
|
|
543
|
+
scrollToTarget(result.url, savedScroll);
|
|
544
|
+
const focus = pop && historyFocus.get(currentKey)?.deref();
|
|
545
|
+
if (focus?.isConnected && focus.getClientRects().length) focus.focus({ preventScroll: true });
|
|
546
|
+
}
|
|
461
547
|
};
|
|
462
|
-
|
|
548
|
+
appStyleManager(document).handoff(result.model.globalError ? [] : result.model.css || []);
|
|
549
|
+
if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>startTransition(()=>root.render(appView({model:result.model,controller,committed:()=>{committed();resolve();},url:result.url.href,globalErrorComponent,globalErrorCss}))));
|
|
550
|
+
else startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
|
|
463
551
|
} catch (error) {
|
|
464
552
|
if (token !== navigation || error?.name === 'AbortError') return;
|
|
553
|
+
if(error?.code==='PRNEXT_NAVIGATION_NETWORK'){
|
|
554
|
+
window.dispatchEvent(new CustomEvent('prnext:navigation-error',{detail:{message:error.message}}));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
465
557
|
// Pages Router routes, public files, and unavailable Flight endpoints use
|
|
466
558
|
// a normal document navigation, including the browser's redirect handling.
|
|
467
559
|
if (replace || pop || refresh) window.location.replace(target.href);
|
|
468
560
|
else window.location.assign(target.href);
|
|
561
|
+
} finally {
|
|
562
|
+
if (finishNavigation === releaseNavigation) finishNavigation = undefined;
|
|
563
|
+
releaseNavigation();
|
|
469
564
|
}
|
|
470
565
|
}
|
|
471
566
|
|
|
567
|
+
function purgeRetainedPages() {
|
|
568
|
+
controller._retentionEpoch++;
|
|
569
|
+
historyFocus.clear();
|
|
570
|
+
startTransition(() => root?.render(appView({ model: currentModel, controller, url: currentUrl.href, globalErrorComponent, globalErrorCss })));
|
|
571
|
+
}
|
|
572
|
+
|
|
472
573
|
const controller = {
|
|
473
|
-
|
|
574
|
+
_retentionEpoch: 0,
|
|
575
|
+
_authorizationEpoch: 0,
|
|
576
|
+
_navigationCache: navigationCache,
|
|
577
|
+
partialPrefetch: (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__),
|
|
474
578
|
push: (href, options) => { void navigate(navigationUrl(href), options); },
|
|
475
579
|
replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
|
|
476
580
|
refresh: () => {
|
|
477
581
|
invalidatePrefetches();
|
|
582
|
+
purgeRetainedPages();
|
|
478
583
|
void navigate(new URL(window.location.href), { refresh: true });
|
|
479
584
|
},
|
|
480
585
|
back: () => { saveScroll(); window.history.back(); },
|
|
481
586
|
forward: () => { saveScroll(); window.history.forward(); },
|
|
482
587
|
prefetch(href, options = {}) {
|
|
588
|
+
if (pageHidden) return;
|
|
483
589
|
const target = navigationUrl(href);
|
|
484
|
-
if (!isApplicationURL(target, window.location.origin, basePath)) return;
|
|
590
|
+
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) return;
|
|
485
591
|
target.hash = '';
|
|
486
592
|
const key = target.href;
|
|
487
|
-
|
|
488
|
-
|
|
593
|
+
const priority = options.priority === 'viewport' ? 0 : 1;
|
|
594
|
+
const previous = prefetches.get(key);
|
|
595
|
+
if (previous && !previous.abort.signal.aborted && (!options.full || previous.full)) {
|
|
596
|
+
previous.priority = Math.max(previous.priority, priority);
|
|
597
|
+
previous.task.promote(options.priority);
|
|
598
|
+
if (typeof options.onInvalidate === 'function') previous.invalidations.add(options.onInvalidate);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (previous) dropPrefetch(key, true);
|
|
602
|
+
// Self-rearming callbacks may fill free slots, but cannot keep evicting
|
|
603
|
+
// each other forever when more warm routes compete for a bounded cache.
|
|
604
|
+
if (notifyingPrefetch && prefetches.size >= PREFETCH_LIMIT) return;
|
|
605
|
+
const victim = prefetches.size >= PREFETCH_LIMIT ? prefetchCacheVictim(prefetches, priority) : undefined;
|
|
606
|
+
if (prefetches.size >= PREFETCH_LIMIT && victim === undefined) return;
|
|
489
607
|
const abort = new AbortController();
|
|
490
|
-
const entry = { abort,
|
|
491
|
-
entry.
|
|
608
|
+
const entry = { abort, priority, full: options.full === true, invalidations: new Set(typeof options.onInvalidate === 'function' ? [options.onInvalidate] : []) };
|
|
609
|
+
entry.task = scheduler.schedule(() => (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) && !window.__PRNEXT_STATIC_EXPORT__
|
|
610
|
+
? options.full && !partialPrefetching ? requestFlight(target, abort.signal) : requestPartial(target, abort.signal, options.full) : requestFlight(target, abort.signal), { abort, priority: options.priority });
|
|
611
|
+
if (!entry.task) return;
|
|
612
|
+
if (victim !== undefined) dropPrefetch(victim, true);
|
|
613
|
+
entry.promise = entry.task.promise;
|
|
492
614
|
// A failed speculative fetch must never produce an unhandled rejection.
|
|
493
|
-
entry.promise.catch(
|
|
615
|
+
entry.promise.catch(error => {
|
|
616
|
+
// An intentional cancellation is not a cache miss to retry. In
|
|
617
|
+
// particular pagehide must never create a replacement Flight request.
|
|
618
|
+
entry.failed = !abort.signal.aborted && error?.name !== 'AbortError';
|
|
619
|
+
if (prefetches.get(key) === entry) dropPrefetch(key, true);
|
|
620
|
+
});
|
|
494
621
|
entry.timer = setTimeout(() => dropPrefetch(key, true), PREFETCH_LIFETIME);
|
|
495
622
|
prefetches.set(key, entry);
|
|
496
623
|
},
|
|
497
624
|
};
|
|
498
625
|
|
|
499
|
-
|
|
626
|
+
let resolveDevReady;
|
|
627
|
+
const devReady=process.env.NODE_ENV==='development'?new Promise(resolve=>{resolveDevReady=resolve;}):undefined;
|
|
628
|
+
const initialView = appView({ model: initialModel, controller, globalErrorComponent, globalErrorCss, ...(devReady?{committed:resolveDevReady}:{}) });
|
|
500
629
|
const rootOptions = {
|
|
501
630
|
formState: initialModel.formState ?? null,
|
|
502
631
|
onCaughtError(error) {
|
|
@@ -506,7 +635,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
506
635
|
}
|
|
507
636
|
},
|
|
508
637
|
onRecoverableError(error) {
|
|
509
|
-
if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause))
|
|
638
|
+
if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause)) {
|
|
639
|
+
console.error(error);
|
|
640
|
+
if (process.env.NODE_ENV === 'development') globalThis.__PRNEXT_DEV__?.reportRecoverableError(error);
|
|
641
|
+
}
|
|
510
642
|
},
|
|
511
643
|
};
|
|
512
644
|
if (document.documentElement.id === '__prnext_error__') {
|
|
@@ -518,15 +650,36 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
518
650
|
window.addEventListener('popstate', event => {
|
|
519
651
|
void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
|
|
520
652
|
});
|
|
521
|
-
window.addEventListener('pagehide', saveScroll);
|
|
653
|
+
window.addEventListener('pagehide', () => { saveScroll(); pageHidden = true; navigation++; activeRequest?.abort(); appStyleManager(document).cancel(); finishNavigation?.(); finishNavigation = undefined; invalidatePrefetches(); });
|
|
654
|
+
window.addEventListener('pageshow', event => { pageHidden = false; if (event.persisted) controller.refresh(); });
|
|
655
|
+
window.addEventListener('prnext:invalidate-navigation', () => { controller._authorizationEpoch++; controller.refresh(); });
|
|
522
656
|
let scrollFrame;
|
|
523
657
|
window.addEventListener('scroll', () => {
|
|
524
658
|
if (scrollFrame !== undefined) return;
|
|
525
659
|
scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
|
|
526
660
|
}, { passive: true });
|
|
661
|
+
function developmentStyles(model, routes) {
|
|
662
|
+
const route = routes?.find(route => route.pattern === model.styleRoute);
|
|
663
|
+
if (!route) return model;
|
|
664
|
+
const scopes = routes.flatMap(route => route.cssScopes || []);
|
|
665
|
+
const previous = { routing:model.routing, cssScopes:(model.cssScopes || []).map(scope => scopes.find(current => current.path === scope.path && current.kind === scope.kind) || scope) };
|
|
666
|
+
const next = { ...model, css:route.css, cssScopes:route.cssScopes };
|
|
667
|
+
retainAppStyles(next, previous);
|
|
668
|
+
return next;
|
|
669
|
+
}
|
|
527
670
|
return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
|
|
671
|
+
devReady,
|
|
672
|
+
devStyles: routes => developmentStyles(currentModel, routes).css || [],
|
|
673
|
+
devPrepareStyles: routes => appStyleManager(document).prepare(developmentStyles(currentModel, routes).css || []),
|
|
674
|
+
devStylesApplied: (routes, { commit = true } = {}) => {
|
|
675
|
+
for (const model of [currentModel]) {
|
|
676
|
+
const next = developmentStyles(model, routes);
|
|
677
|
+
model.css = next.css; model.cssScopes = next.cssScopes;
|
|
678
|
+
}
|
|
679
|
+
if (commit) appStyleManager(document).commit(currentModel.globalError ? [] : currentModel.css || []);
|
|
680
|
+
},
|
|
528
681
|
devPrepare: options => moduleLoader.updateModules(options.clientModules),
|
|
529
|
-
devCommit: () =>
|
|
682
|
+
devCommit: () => {invalidatePrefetches();purgeRetainedPages();return navigate(new URL(window.location.href),{refresh:true});},
|
|
530
683
|
} : {}) };
|
|
531
684
|
}
|
|
532
685
|
|
|
@@ -534,6 +687,11 @@ export function bootstrapApp(options) {
|
|
|
534
687
|
if (process.env.NODE_ENV === 'development' && options.dev && globalThis.__PRNEXT_DEV__) {
|
|
535
688
|
return globalThis.__PRNEXT_DEV__.bootstrap('app', options, () => startApp(options));
|
|
536
689
|
}
|
|
537
|
-
|
|
690
|
+
if (!application) {
|
|
691
|
+
application = startApp(options);
|
|
692
|
+
// Generated entries intentionally do not await startup. Report a failed
|
|
693
|
+
// chunk once, while retaining the original rejection for explicit callers.
|
|
694
|
+
void application.catch(error => console.error('[PRNext] App startup failed:', error));
|
|
695
|
+
}
|
|
538
696
|
return application;
|
|
539
697
|
}
|
package/runtime/app-content.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { LayoutCacheContext } from '../compat/app-layout-context.cjs';
|
|
2
|
+
import { LayoutCacheContext, ActivityCacheContext } from '../compat/app-layout-context.cjs';
|
|
3
3
|
|
|
4
4
|
// Preserve the same sibling positions during SSR, PPR and hydration. Even an
|
|
5
5
|
// empty style slot changes React's useId tree encoding when it is omitted.
|
|
6
|
-
export function appContent(tree, { layoutCache = null, styles = null } = {}) {
|
|
7
|
-
return React.createElement(
|
|
6
|
+
export function appContent(tree, { layoutCache = null, styles = null, activityCache = null } = {}) {
|
|
7
|
+
return React.createElement(ActivityCacheContext.Provider, { value: activityCache },
|
|
8
|
+
React.createElement(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles));
|
|
8
9
|
}
|
package/runtime/app-partial.mjs
CHANGED
|
@@ -74,7 +74,7 @@ export async function prerenderPartialHtml(model, route, { strictMode = true, un
|
|
|
74
74
|
if (!/<html(?:\s|>)/i.test(shell) || !/<body(?:\s|>)/i.test(shell) || !/<\/head>/i.test(shell)) {
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
|
-
const styles = fontPreloads(route.fonts) + (route.css
|
|
77
|
+
const styles = fontPreloads(route.fonts) + (model.css ?? route.css ?? []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
|
|
78
78
|
if (styles) shell = shell.replace(/<\/head>/i, `${styles}</head>`);
|
|
79
79
|
return { shell, postponed: rendered.postponed, clientDynamic };
|
|
80
80
|
} catch (error) {
|
package/runtime/app-prefetch.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Prefetch contains only prerendered segments. Request-dependent holes are
|
|
2
2
|
// resolved by a fresh, authenticated navigation; they never enter this cache.
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export const PARTIAL_PREFETCH_CACHE_BYTES = 2 * 1024 * 1024;
|
|
3
|
+
import { PARTIAL_PREFETCH_BYTES } from './app-prefetch-constants.mjs';
|
|
4
|
+
export { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_BYTES, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
|
|
6
5
|
|
|
7
6
|
export async function readPartialPrefetch(response) {
|
|
8
7
|
const reader = response.body.getReader();
|