@thomas.f/prnext 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/build/action-exports.mjs +107 -0
- package/build/actions.mjs +214 -0
- package/build/app-config.mjs +138 -0
- package/build/app-routing.mjs +119 -0
- package/build/app-static.mjs +158 -0
- package/build/app.mjs +536 -0
- package/build/cache-components.mjs +91 -0
- package/build/cache-handlers.mjs +54 -0
- package/build/check.mjs +40 -0
- package/build/compiler.mjs +222 -0
- package/build/config.mjs +193 -0
- package/build/css-modules.mjs +114 -0
- package/build/custom-routes.mjs +363 -0
- package/build/dev-worker.mjs +18 -0
- package/build/dev.mjs +76 -0
- package/build/directory-cache.mjs +29 -0
- package/build/dynamic.mjs +125 -0
- package/build/edge.mjs +141 -0
- package/build/font-data.LICENSE.txt +25 -0
- package/build/font-data.json +1 -0
- package/build/font-files.mjs +221 -0
- package/build/fonts.mjs +131 -0
- package/build/framework-imports.mjs +17 -0
- package/build/i18n.mjs +39 -0
- package/build/images-config.mjs +62 -0
- package/build/images.mjs +86 -0
- package/build/incremental.mjs +76 -0
- package/build/index.mjs +356 -0
- package/build/loader-cache.mjs +69 -0
- package/build/metadata-images.mjs +72 -0
- package/build/metadata-routes.mjs +77 -0
- package/build/middleware.mjs +231 -0
- package/build/migrate.mjs +289 -0
- package/build/module-resolution.mjs +78 -0
- package/build/native-discovery.mjs +134 -0
- package/build/native-packages.mjs +105 -0
- package/build/npm-packages.mjs +110 -0
- package/build/og.mjs +14 -0
- package/build/page-extensions.mjs +12 -0
- package/build/passthrough-loader.cjs +4 -0
- package/build/precompress.mjs +122 -0
- package/build/scan.mjs +178 -0
- package/build/script-workers.mjs +25 -0
- package/build/server-actions-config.mjs +27 -0
- package/build/source-maps.mjs +34 -0
- package/build/standalone-config.mjs +19 -0
- package/build/standalone-runner.mjs +21 -0
- package/build/standalone-worker.mjs +5 -0
- package/build/standalone.mjs +240 -0
- package/build/static-export.mjs +93 -0
- package/build/styles.mjs +174 -0
- package/build/transform-cache.mjs +50 -0
- package/build/transform.mjs +155 -0
- package/build/turbopack-conditions.mjs +59 -0
- package/build/webpack-bridge.cjs +3 -0
- package/build/webpack-graph.mjs +256 -0
- package/build/webpack-loader.cjs +9 -0
- package/cli.mjs +210 -0
- package/compat/app-client-page.cjs +32 -0
- package/compat/app-context.cjs +40 -0
- package/compat/app-context.d.cts +20 -0
- package/compat/app-dynamic.cjs +34 -0
- package/compat/app-error-boundary.cjs +53 -0
- package/compat/app-global-error-boundary.cjs +34 -0
- package/compat/app-layout-context.cjs +27 -0
- package/compat/app-navigation-boundary.cjs +47 -0
- package/compat/app.cjs +33 -0
- package/compat/app.d.cts +9 -0
- package/compat/cache-handlers.cjs +223 -0
- package/compat/cache-life.cjs +34 -0
- package/compat/cache.cjs +99 -0
- package/compat/cache.d.cts +36 -0
- package/compat/compat/router.d.cts +2 -0
- package/compat/compat-router.cjs +7 -0
- package/compat/constants.cjs +8 -0
- package/compat/constants.d.cts +7 -0
- package/compat/cookies.cjs +129 -0
- package/compat/data-cache.cjs +389 -0
- package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
- package/compat/dist/shared/lib/router-context.d.cts +1 -0
- package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
- package/compat/document.cjs +73 -0
- package/compat/document.d.cts +41 -0
- package/compat/draft.cjs +28 -0
- package/compat/dynamic-bailout.cjs +13 -0
- package/compat/dynamic.cjs +119 -0
- package/compat/dynamic.d.cts +32 -0
- package/compat/error.cjs +36 -0
- package/compat/error.d.cts +15 -0
- package/compat/fetch.d.cts +10 -0
- package/compat/font/google.d.cts +1 -0
- package/compat/font/local.d.cts +2 -0
- package/compat/font-google.cjs +2 -0
- package/compat/font-google.d.cts +1944 -0
- package/compat/font-local.cjs +5 -0
- package/compat/font-local.d.cts +3 -0
- package/compat/font-types.d.cts +25 -0
- package/compat/head.cjs +180 -0
- package/compat/head.d.cts +3 -0
- package/compat/headers.cjs +89 -0
- package/compat/headers.d.cts +26 -0
- package/compat/image-config.cjs +3 -0
- package/compat/image-imports.d.ts +12 -0
- package/compat/image-shared.cjs +62 -0
- package/compat/image.cjs +64 -0
- package/compat/image.d.cts +35 -0
- package/compat/incremental-cache.cjs +186 -0
- package/compat/index.d.ts +159 -0
- package/compat/instant-samples.cjs +39 -0
- package/compat/instant.cjs +13 -0
- package/compat/link.cjs +68 -0
- package/compat/link.d.cts +17 -0
- package/compat/locale.cjs +30 -0
- package/compat/metadata.d.cts +77 -0
- package/compat/navigation-controls.cjs +27 -0
- package/compat/navigation-server.cjs +25 -0
- package/compat/navigation.cjs +52 -0
- package/compat/navigation.d.cts +28 -0
- package/compat/next-app-router-context.cjs +6 -0
- package/compat/next-router-context.cjs +3 -0
- package/compat/og.cjs +32 -0
- package/compat/og.d.cts +1 -0
- package/compat/paths.cjs +33 -0
- package/compat/preview.cjs +41 -0
- package/compat/rewrite.cjs +41 -0
- package/compat/route-request.cjs +64 -0
- package/compat/router.cjs +137 -0
- package/compat/router.d.cts +58 -0
- package/compat/script-context.cjs +5 -0
- package/compat/script-loader.cjs +135 -0
- package/compat/script.cjs +69 -0
- package/compat/script.d.cts +17 -0
- package/compat/server-primitives.cjs +109 -0
- package/compat/server.cjs +13 -0
- package/compat/server.d.cts +37 -0
- package/compat/static-generation.cjs +108 -0
- package/compat/use-cache.cjs +129 -0
- package/index.mjs +1 -0
- package/native/platforms.json +6 -0
- package/native/resolve.mjs +81 -0
- package/package.json +127 -0
- package/release/verify.mjs +59 -0
- package/runtime/action-client.mjs +19 -0
- package/runtime/action-crypto.mjs +138 -0
- package/runtime/action-reference.mjs +2 -0
- package/runtime/action-server.mjs +161 -0
- package/runtime/action-ssr.mjs +5 -0
- package/runtime/api.mjs +326 -0
- package/runtime/app-client.mjs +539 -0
- package/runtime/app-content.mjs +8 -0
- package/runtime/app-errors.mjs +12 -0
- package/runtime/app-instant.mjs +30 -0
- package/runtime/app-metadata.mjs +410 -0
- package/runtime/app-partial-model.mjs +228 -0
- package/runtime/app-partial.mjs +130 -0
- package/runtime/app-prefetch.mjs +34 -0
- package/runtime/app-recovery.mjs +87 -0
- package/runtime/app-render.mjs +544 -0
- package/runtime/app-routing-access.mjs +143 -0
- package/runtime/app-routing-context.mjs +35 -0
- package/runtime/app-routing-state.mjs +61 -0
- package/runtime/app-routing.mjs +323 -0
- package/runtime/app-segments.mjs +22 -0
- package/runtime/app-static-params.mjs +120 -0
- package/runtime/app-static.mjs +86 -0
- package/runtime/app-stream-channel.mjs +92 -0
- package/runtime/app-stream-html.mjs +279 -0
- package/runtime/build-directory.mjs +48 -0
- package/runtime/cache-arguments.mjs +33 -0
- package/runtime/cache-components-rsc.mjs +64 -0
- package/runtime/cache-request.mjs +23 -0
- package/runtime/client-navigation.mjs +40 -0
- package/runtime/dev-client.mjs +129 -0
- package/runtime/dev-file-changes.mjs +110 -0
- package/runtime/document.mjs +96 -0
- package/runtime/edge.mjs +105 -0
- package/runtime/env.mjs +68 -0
- package/runtime/fetch-cache.mjs +379 -0
- package/runtime/font-preload.mjs +4 -0
- package/runtime/http.mjs +190 -0
- package/runtime/incremental-cache.mjs +83 -0
- package/runtime/metadata-route.mjs +83 -0
- package/runtime/middleware.mjs +186 -0
- package/runtime/module-loader.mjs +21 -0
- package/runtime/navigation.mjs +16 -0
- package/runtime/og-loader.mjs +17 -0
- package/runtime/pages-client.mjs +197 -0
- package/runtime/pages-error-props.mjs +6 -0
- package/runtime/pages-errors.mjs +85 -0
- package/runtime/pages-initial-props.mjs +43 -0
- package/runtime/pages-navigation.mjs +565 -0
- package/runtime/pages-paths.mjs +80 -0
- package/runtime/pages-revalidate.mjs +33 -0
- package/runtime/partial-artifact-cache.mjs +37 -0
- package/runtime/partial-template.mjs +174 -0
- package/runtime/profile.mjs +9 -0
- package/runtime/profiles.json +47 -0
- package/runtime/render.mjs +304 -0
- package/runtime/request-transport.mjs +41 -0
- package/runtime/request-work.mjs +7 -0
- package/runtime/route-static.mjs +117 -0
- package/runtime/rsc-worker.mjs +426 -0
- package/runtime/script-html.mjs +61 -0
- package/runtime/stream-select.mjs +25 -0
- package/runtime/stream-utils.mjs +127 -0
- package/runtime/transport.mjs +108 -0
- package/runtime/watch-project.mjs +130 -0
- package/runtime/worker-artifacts.mjs +22 -0
- package/runtime/worker-metadata.mjs +31 -0
- package/runtime/worker.mjs +197 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import React, { startTransition, useLayoutEffect } from 'react';
|
|
2
|
+
import { preload } from 'react-dom';
|
|
3
|
+
import { partialModel } from './app-partial-model.mjs';
|
|
4
|
+
import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES, readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
|
|
5
|
+
import { isDynamicBailout } from '../compat/dynamic-bailout.cjs';
|
|
6
|
+
import { createRoot, hydrateRoot } from 'react-dom/client';
|
|
7
|
+
import { AppRouterProvider } from '../compat/app-context.cjs';
|
|
8
|
+
import { appContent } from './app-content.mjs';
|
|
9
|
+
import { configureServerActions } from './action-client.mjs';
|
|
10
|
+
import { getNavigationControl } from '../compat/app-navigation-boundary.cjs';
|
|
11
|
+
import { readRewriteHeader, readRewriteMarker } from '../compat/rewrite.cjs';
|
|
12
|
+
import { flightResponseURL, publicNavigationURL, isApplicationURL } from './client-navigation.mjs';
|
|
13
|
+
import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
|
|
14
|
+
import GlobalErrorBoundary, { DefaultGlobalError, GlobalErrorTrigger } from '../compat/app-global-error-boundary.cjs';
|
|
15
|
+
import { loadBeforeInteractive } from '../compat/script-loader.cjs';
|
|
16
|
+
|
|
17
|
+
const PREFETCH_LIMIT = 8;
|
|
18
|
+
const PREFETCH_LIFETIME = 30_000;
|
|
19
|
+
let application;
|
|
20
|
+
let strictMode = true;
|
|
21
|
+
|
|
22
|
+
function loadClientModule(source) {
|
|
23
|
+
if (typeof source === 'function') return source();
|
|
24
|
+
// Keep the argument separate: esbuild splits import(condition ? a : b)
|
|
25
|
+
// into two imports and moves webpackIgnore outside both calls.
|
|
26
|
+
const url = typeof source === 'string' ? source : source.browserModule;
|
|
27
|
+
return import(/* webpackIgnore: true */ url);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// React Flight's webpack adapter only needs these two operations. The compiler
|
|
31
|
+
// supplies ESM import functions so React and application contexts stay shared.
|
|
32
|
+
export function installFlightModuleLoader(clientModules) {
|
|
33
|
+
const loaded = new Map();
|
|
34
|
+
const pending = new Map();
|
|
35
|
+
const previousRequire = globalThis.__webpack_require__;
|
|
36
|
+
const previousLoad = globalThis.__webpack_chunk_load__;
|
|
37
|
+
globalThis.__webpack_require__ = id => {
|
|
38
|
+
if (loaded.has(id)) return loaded.get(id);
|
|
39
|
+
if (!Object.hasOwn(clientModules, id) && previousRequire) return previousRequire(id);
|
|
40
|
+
throw new Error(`Client module ${id} has not been loaded`);
|
|
41
|
+
};
|
|
42
|
+
globalThis.__webpack_require__.u = id => {
|
|
43
|
+
const source = clientModules[id];
|
|
44
|
+
return typeof source === 'string' ? source : source?.browserModule || String(id);
|
|
45
|
+
};
|
|
46
|
+
globalThis.__webpack_get_script_filename__ = id => globalThis.__webpack_require__.u(id);
|
|
47
|
+
globalThis.__webpack_chunk_load__ = id => {
|
|
48
|
+
if (loaded.has(id)) return Promise.resolve();
|
|
49
|
+
if (pending.has(id)) return pending.get(id);
|
|
50
|
+
if (!Object.hasOwn(clientModules, id)) {
|
|
51
|
+
if (previousLoad) return previousLoad(id);
|
|
52
|
+
return Promise.reject(new Error(`Unknown PRNext client module ${id}`));
|
|
53
|
+
}
|
|
54
|
+
const source = clientModules[id];
|
|
55
|
+
const promise = Promise.resolve().then(() => loadClientModule(source))
|
|
56
|
+
.then(module => { loaded.set(id, module); pending.delete(id); }, error => {
|
|
57
|
+
pending.delete(id);
|
|
58
|
+
throw error;
|
|
59
|
+
});
|
|
60
|
+
pending.set(id, promise);
|
|
61
|
+
return promise;
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
async updateModules(nextModules) {
|
|
65
|
+
const ids = [...loaded.keys()].filter(id => Object.hasOwn(nextModules, id));
|
|
66
|
+
clientModules = nextModules;
|
|
67
|
+
await Promise.all(ids.map(async id => {
|
|
68
|
+
const source = clientModules[id];
|
|
69
|
+
const module = await loadClientModule(source);
|
|
70
|
+
loaded.set(id, module);
|
|
71
|
+
}));
|
|
72
|
+
for (const id of loaded.keys()) if (!Object.hasOwn(nextModules, id)) loaded.delete(id);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function embeddedFlight() {
|
|
78
|
+
const queue = globalThis.__PRNEXT_FLIGHT_STREAM__;
|
|
79
|
+
if (queue) {
|
|
80
|
+
let closed = false;
|
|
81
|
+
let onComplete;
|
|
82
|
+
return new ReadableStream({
|
|
83
|
+
start(controller) {
|
|
84
|
+
const push = value => {
|
|
85
|
+
if (closed) return;
|
|
86
|
+
if (value === null) {
|
|
87
|
+
closed = true;
|
|
88
|
+
document.removeEventListener('DOMContentLoaded', onComplete);
|
|
89
|
+
controller.close();
|
|
90
|
+
} else {
|
|
91
|
+
const binary = atob(value);
|
|
92
|
+
controller.enqueue(Uint8Array.from(binary, character => character.charCodeAt(0)));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
onComplete = () => {
|
|
96
|
+
if (!closed) { closed = true; controller.error(new Error('The initial Flight stream ended before completion')); }
|
|
97
|
+
};
|
|
98
|
+
const initial = queue.splice(0);
|
|
99
|
+
queue.push = push;
|
|
100
|
+
for (const value of initial) push(value);
|
|
101
|
+
if (!closed) {
|
|
102
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', onComplete, { once: true });
|
|
103
|
+
else queueMicrotask(onComplete);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
cancel() { closed = true; document.removeEventListener('DOMContentLoaded', onComplete); queue.push = () => {}; },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const element = document.getElementById('__PRNEXT_FLIGHT__');
|
|
110
|
+
if (!element) throw new Error('PRNext App Router Flight payload is missing');
|
|
111
|
+
const binary = atob(element.textContent.trim());
|
|
112
|
+
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
113
|
+
element.remove();
|
|
114
|
+
return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function NormalStyles({ css, fonts }) {
|
|
118
|
+
const [styles, setStyles] = React.useState([]);
|
|
119
|
+
useLayoutEffect(() => {
|
|
120
|
+
for (const font of fonts || []) preload(font.href, { as: 'font', type: font.type, crossOrigin: 'anonymous' });
|
|
121
|
+
// Fatal global renders never commit this branch. Wait for a successful
|
|
122
|
+
// normal/local-error mount, then restore sheets omitted from its empty SSR
|
|
123
|
+
// document. Existing plain SSR links are already loaded and need no copy.
|
|
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])]);
|
|
127
|
+
}, [css, fonts]);
|
|
128
|
+
return styles.map(href => React.createElement('link', { key: href, href, rel: 'stylesheet', precedence: 'prnext-app' }));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss }) {
|
|
132
|
+
const [hydrated, setHydrated] = React.useState(false);
|
|
133
|
+
const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
|
|
134
|
+
useLayoutEffect(() => {
|
|
135
|
+
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' }));
|
|
136
|
+
}, [model.instantDiagnostics]);
|
|
137
|
+
useLayoutEffect(() => { setHydrated(true); committed?.(); }, [model, committed]);
|
|
138
|
+
// Cached Flight describes a canonical path without one visitor's query.
|
|
139
|
+
// Preserve its server snapshot for hydration, then expose the actual browser
|
|
140
|
+
// URL. On navigation, update it in the same render as the new component tree.
|
|
141
|
+
const location = hydrated ? new URL(url || window.location.href) : null;
|
|
142
|
+
const router = location ? { ...model.router, pathname: removeBasePath(location.pathname, model.router.basePath || ''), search: location.search,
|
|
143
|
+
...(model.rewrite ? { pageSearch: new URL(model.rewrite.url, location).search, params: model.rewrite.params } : {}),
|
|
144
|
+
} : model.router;
|
|
145
|
+
return React.createElement(AppRouterProvider, { router, controller, ...(model.partialPreview ? { prerenderSearch: previewDynamic, ...(model.partialPreview.unknownParams ? { prerenderParams: previewDynamic } : {}) } : {}) },
|
|
146
|
+
React.createElement(GlobalErrorBoundary, { component: globalErrorComponent, css: globalErrorCss, resetKey: model },
|
|
147
|
+
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 }) })));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function prepareLayoutCache(model, previous) {
|
|
152
|
+
if (!model.routing || model.layoutCache) return model;
|
|
153
|
+
const active = new Set(Object.keys(model.routing.slots));
|
|
154
|
+
model.layoutCache = {
|
|
155
|
+
slots: Object.fromEntries(Object.entries(previous?.layoutCache?.slots || {}).filter(([key]) => active.has(key))),
|
|
156
|
+
segments: Object.fromEntries(Object.entries(previous?.layoutCache?.segments || {}).filter(([key]) => [...active].some(slot => slot.startsWith(`${key}::`)))),
|
|
157
|
+
};
|
|
158
|
+
return model;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function AppView(props) {
|
|
162
|
+
// The custom fallback still has router context. A separate final boundary
|
|
163
|
+
// catches errors in that fallback or in the router/provider itself.
|
|
164
|
+
return React.createElement(GlobalErrorBoundary, { component: DefaultGlobalError, resetKey: props.model,
|
|
165
|
+
refresh: props.controller.refresh }, React.createElement(AppRoot, props));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function appView(props) {
|
|
169
|
+
return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, props));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function historyState(key, scroll, routing) {
|
|
173
|
+
const state = window.history.state;
|
|
174
|
+
return { ...(state && typeof state === 'object' ? state : {}), __prnext: { key, scroll, ...(routing ? { routing } : {}) } };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function scrollToTarget(url, saved) {
|
|
178
|
+
if (saved) { window.scrollTo(saved[0], saved[1]); return; }
|
|
179
|
+
if (url.hash) {
|
|
180
|
+
let name = url.hash.slice(1);
|
|
181
|
+
try { name = decodeURIComponent(name); } catch { /* A literal malformed escape can be an element ID. */ }
|
|
182
|
+
const anchor = document.getElementById(name) || document.getElementsByName(name)[0];
|
|
183
|
+
if (anchor) { anchor.scrollIntoView(); return; }
|
|
184
|
+
}
|
|
185
|
+
window.scrollTo(0, 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function assertModel(model) {
|
|
189
|
+
if (!model || typeof model !== 'object' || model.routingInvalid || !Object.hasOwn(model, 'tree') ||
|
|
190
|
+
typeof model.router?.pathname !== 'string') throw new Error('Invalid PRNext Flight response');
|
|
191
|
+
return model;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function startApp({ clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false } = {}) {
|
|
195
|
+
strictMode = enabledStrictMode;
|
|
196
|
+
await loadBeforeInteractive();
|
|
197
|
+
basePath = normalizeBasePath(basePath);
|
|
198
|
+
const navigationUrl = href => publicNavigationURL(href, window.location.href, basePath, { trailingSlash, skipTrailingSlashRedirect });
|
|
199
|
+
const moduleLoader = installFlightModuleLoader(clientModules);
|
|
200
|
+
const globalErrorComponent = globalErrorId ? React.lazy(async () => {
|
|
201
|
+
await globalThis.__webpack_chunk_load__(globalErrorId);
|
|
202
|
+
return { default: globalThis.__webpack_require__(globalErrorId).default };
|
|
203
|
+
}) : DefaultGlobalError;
|
|
204
|
+
// The Flight adapter reads __webpack_require__.u during module evaluation.
|
|
205
|
+
const flightClient = await import('react-server-dom-webpack/client.browser');
|
|
206
|
+
const { createFromFetch, createFromReadableStream, createServerReference, createTemporaryReferenceSet, encodeReply } = flightClient.default || flightClient;
|
|
207
|
+
let root;
|
|
208
|
+
let actionQueue = Promise.resolve();
|
|
209
|
+
configureServerActions({ createServerReference, callServer });
|
|
210
|
+
const flightOptions = { replayConsoleLogs: false, callServer };
|
|
211
|
+
let initialModel;
|
|
212
|
+
try { initialModel = assertModel(await createFromReadableStream(embeddedFlight(), flightOptions)); }
|
|
213
|
+
catch (error) {
|
|
214
|
+
initialModel = { tree: null, globalError: error, router: {
|
|
215
|
+
pathname: removeBasePath(window.location.pathname, basePath), search: window.location.search,
|
|
216
|
+
} };
|
|
217
|
+
}
|
|
218
|
+
initialModel.router.basePath = basePath;
|
|
219
|
+
prepareLayoutCache(initialModel);
|
|
220
|
+
const initialRewrite = readRewriteMarker(document);
|
|
221
|
+
if (initialRewrite) initialModel.rewrite = initialRewrite;
|
|
222
|
+
let currentUrl = new URL(window.location.href);
|
|
223
|
+
let currentRouting = initialModel.routing;
|
|
224
|
+
let currentModel = initialModel;
|
|
225
|
+
const historyModels = new Map();
|
|
226
|
+
let currentKey = window.history.state?.__prnext?.key || 0;
|
|
227
|
+
let nextKey = currentKey + 1;
|
|
228
|
+
let navigation = 0;
|
|
229
|
+
let activeRequest;
|
|
230
|
+
const prefetches = new Map();
|
|
231
|
+
const partialSegments = new Map();
|
|
232
|
+
let segmentBytes = 0;
|
|
233
|
+
|
|
234
|
+
if (currentRouting) historyModels.set(currentKey, initialModel);
|
|
235
|
+
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
|
|
236
|
+
window.history.scrollRestoration = 'manual';
|
|
237
|
+
|
|
238
|
+
function saveScroll() {
|
|
239
|
+
// popstate has already changed location; never overwrite its destination.
|
|
240
|
+
if (window.location.href === currentUrl.href) {
|
|
241
|
+
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function requestFlight(url, signal, routing = currentRouting, onPartial) {
|
|
246
|
+
const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
|
|
247
|
+
const target = exported ? new URL(basePath + '/_prnext/flight' + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
|
|
248
|
+
const response = await fetch(target.href, {
|
|
249
|
+
headers: { RSC: '1', Accept: 'text/x-component', ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
|
|
250
|
+
credentials: 'same-origin',
|
|
251
|
+
signal,
|
|
252
|
+
});
|
|
253
|
+
let destination;
|
|
254
|
+
try { destination = exported ? url : flightResponseURL(response, url); }
|
|
255
|
+
catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
|
|
256
|
+
if (exported ? !response.ok : !response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
|
|
257
|
+
await response.body?.cancel();
|
|
258
|
+
throw new Error('The navigation destination requires a document request');
|
|
259
|
+
}
|
|
260
|
+
onPartial?.(response, destination);
|
|
261
|
+
const model = assertModel(await createFromFetch(Promise.resolve(response), flightOptions));
|
|
262
|
+
model.router.basePath = basePath;
|
|
263
|
+
const rewrite = readRewriteHeader(response);
|
|
264
|
+
if (rewrite) model.rewrite = rewrite;
|
|
265
|
+
return { model, url: destination };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function requestPartial(url, signal) {
|
|
269
|
+
const response = await fetch(url.href, { credentials: 'same-origin', signal,
|
|
270
|
+
headers: { RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
|
|
271
|
+
...(partialSegments.size ? { 'x-prnext-prefetch-known': [...partialSegments.keys()].join(',') } : {}) } });
|
|
272
|
+
const destination = flightResponseURL(response, url);
|
|
273
|
+
if (!response.headers.get('content-type')?.startsWith(PARTIAL_PREFETCH_TYPE)) {
|
|
274
|
+
await response.body?.cancel();
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const value = await readPartialPrefetch(response);
|
|
278
|
+
let segment = partialSegments.get(value.id);
|
|
279
|
+
if (!segment && value.flight) {
|
|
280
|
+
const bytes = value.flight.length;
|
|
281
|
+
while (partialSegments.size >= 8 || segmentBytes + bytes > PARTIAL_PREFETCH_CACHE_BYTES) {
|
|
282
|
+
const id = partialSegments.keys().next().value;
|
|
283
|
+
if (id === undefined) return null;
|
|
284
|
+
segmentBytes -= partialSegments.get(id).bytes;
|
|
285
|
+
partialSegments.delete(id);
|
|
286
|
+
}
|
|
287
|
+
const model = createFromReadableStream(partialFlightStream(value.flight), flightOptions);
|
|
288
|
+
segment = { bytes, model };
|
|
289
|
+
partialSegments.set(value.id, segment);
|
|
290
|
+
segmentBytes += bytes;
|
|
291
|
+
}
|
|
292
|
+
if (!segment) return null; // A concurrent eviction is a harmless prefetch miss.
|
|
293
|
+
// No visitor data is decoded here. Explicit postponed errors become pending
|
|
294
|
+
// boundaries until the navigation's independently authorized Flight arrives.
|
|
295
|
+
const suspended = new Promise(() => {});
|
|
296
|
+
const model = partialModel(assertModel(await segment.model), { keyMap: new Map(value.keys), suspended });
|
|
297
|
+
model.router = { ...value.router, basePath };
|
|
298
|
+
model.partialPreview = { unknownParams: value.unknownParams, suspended };
|
|
299
|
+
return { model, id: value.id, url: destination, partial: true };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function dropPrefetch(key, invalidate = false) {
|
|
303
|
+
const entry = prefetches.get(key);
|
|
304
|
+
if (!entry) return;
|
|
305
|
+
prefetches.delete(key);
|
|
306
|
+
clearTimeout(entry.timer);
|
|
307
|
+
if (invalidate) {
|
|
308
|
+
entry.abort.abort();
|
|
309
|
+
entry.onInvalidate?.();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function invalidatePrefetches() {
|
|
314
|
+
partialSegments.clear(); segmentBytes = 0;
|
|
315
|
+
for (const key of prefetches.keys()) dropPrefetch(key, true);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function callServer(id, args) {
|
|
319
|
+
if (!root) return Promise.reject(new Error('Server Actions cannot run during initial rendering'));
|
|
320
|
+
const token = ++navigation;
|
|
321
|
+
const target = new URL(window.location.href);
|
|
322
|
+
activeRequest?.abort();
|
|
323
|
+
activeRequest = undefined;
|
|
324
|
+
// Mutations are dispatched in order, including Set-Cookie effects required
|
|
325
|
+
// by a subsequent action. Capture their route priority when invoked: a
|
|
326
|
+
// queued action must not cancel a newer user navigation when it starts.
|
|
327
|
+
const result = actionQueue.then(() => dispatchAction(id, args, token, target));
|
|
328
|
+
actionQueue = result.catch(() => {});
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function dispatchAction(id, args, token, target) {
|
|
333
|
+
invalidatePrefetches();
|
|
334
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
335
|
+
const body = await encodeReply(args, { temporaryReferences });
|
|
336
|
+
const response = await fetch(target.href, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
headers: { 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
339
|
+
credentials: 'same-origin',
|
|
340
|
+
// RPC redirects arrive as Flight data. Never replay a mutation through an
|
|
341
|
+
// unexpected HTTP 307/308 or redirect an authenticated POST off origin.
|
|
342
|
+
redirect: 'error',
|
|
343
|
+
body,
|
|
344
|
+
});
|
|
345
|
+
invalidatePrefetches();
|
|
346
|
+
if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
|
|
347
|
+
await response.body?.cancel();
|
|
348
|
+
throw new Error(`Server Action request failed (${response.status})`);
|
|
349
|
+
}
|
|
350
|
+
const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
|
|
351
|
+
if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
|
|
352
|
+
if (token === navigation) window.location.replace(target.href);
|
|
353
|
+
return model.actionResult;
|
|
354
|
+
}
|
|
355
|
+
if (model?.actionRedirect) {
|
|
356
|
+
const destination = navigationUrl(model.actionRedirect.url);
|
|
357
|
+
if (token === navigation) await navigate(destination, { replace: model.actionRedirect.type === 'replace' });
|
|
358
|
+
return model.actionResult;
|
|
359
|
+
}
|
|
360
|
+
if (model && Object.hasOwn(model, 'tree') && token === navigation) {
|
|
361
|
+
assertModel(model);
|
|
362
|
+
model.router.basePath = basePath;
|
|
363
|
+
const rewrite = readRewriteHeader(response);
|
|
364
|
+
if (rewrite) model.rewrite = rewrite;
|
|
365
|
+
prepareLayoutCache(model, currentModel);
|
|
366
|
+
currentRouting = model.routing;
|
|
367
|
+
currentModel = model;
|
|
368
|
+
if (currentRouting) historyModels.set(currentKey, model);
|
|
369
|
+
startTransition(() => root.render(appView({ model, controller, url: target.href, globalErrorComponent, globalErrorCss })));
|
|
370
|
+
}
|
|
371
|
+
if (model?.actionError) {
|
|
372
|
+
const error = new Error(model.actionError.message || 'The Server Action failed');
|
|
373
|
+
if (model.actionError.digest !== undefined) error.digest = model.actionError.digest;
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
// Flight's JSON reviver removes object fields encoded as $undefined. A
|
|
377
|
+
// successful void action therefore has an updated tree but no own result.
|
|
378
|
+
if (!model || typeof model !== 'object' || (!Object.hasOwn(model, 'actionResult') && !Object.hasOwn(model, 'tree'))) {
|
|
379
|
+
throw new Error('The Server Action response did not contain a result');
|
|
380
|
+
}
|
|
381
|
+
return model.actionResult;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
|
|
385
|
+
const target = navigationUrl(href);
|
|
386
|
+
if (!isApplicationURL(target, window.location.origin, basePath)) {
|
|
387
|
+
if (replace) window.location.replace(target.href);
|
|
388
|
+
else window.location.assign(target.href);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const token = ++navigation;
|
|
392
|
+
activeRequest?.abort();
|
|
393
|
+
activeRequest = undefined;
|
|
394
|
+
saveScroll();
|
|
395
|
+
|
|
396
|
+
function updateHistory(destination) {
|
|
397
|
+
if (!pop && !refresh) {
|
|
398
|
+
if (!replace) currentKey = nextKey++;
|
|
399
|
+
const position = scroll ? [0, 0] : [window.scrollX, window.scrollY];
|
|
400
|
+
window.history[replace ? 'replaceState' : 'pushState'](historyState(currentKey, position, currentRouting), '', destination.href);
|
|
401
|
+
} else if (pop) {
|
|
402
|
+
currentKey = window.history.state?.__prnext?.key ?? nextKey++;
|
|
403
|
+
}
|
|
404
|
+
if ((pop || refresh) && destination.href !== window.location.href) {
|
|
405
|
+
window.history.replaceState(historyState(currentKey, savedScroll || [0, 0], currentRouting), '', destination.href);
|
|
406
|
+
}
|
|
407
|
+
currentUrl = destination;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!refresh && target.pathname === currentUrl.pathname && target.search === currentUrl.search && target.hash !== currentUrl.hash) {
|
|
411
|
+
updateHistory(target);
|
|
412
|
+
if (scroll) scrollToTarget(target, savedScroll);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
const cacheKey = `${target.origin}${target.pathname}${target.search}`;
|
|
418
|
+
const prefetched = !refresh && prefetches.get(cacheKey);
|
|
419
|
+
let result;
|
|
420
|
+
let historyUpdated = false;
|
|
421
|
+
const historyModel = pop && savedRouting && historyModels.get(window.history.state?.__prnext?.key);
|
|
422
|
+
if (historyModel) result = { model: historyModel, url: target };
|
|
423
|
+
else if (prefetched) {
|
|
424
|
+
activeRequest = prefetched.abort;
|
|
425
|
+
dropPrefetch(cacheKey);
|
|
426
|
+
result = await prefetched.promise;
|
|
427
|
+
if (result && !result.partial) {
|
|
428
|
+
result = { ...result, url: new URL(result.url.href) };
|
|
429
|
+
if (!result.url.hash) result.url.hash = target.hash;
|
|
430
|
+
} else {
|
|
431
|
+
const preview = result;
|
|
432
|
+
const abort = new AbortController();
|
|
433
|
+
activeRequest = abort;
|
|
434
|
+
result = await requestFlight(target, abort.signal, currentRouting, (response, destination) => {
|
|
435
|
+
if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
|
|
436
|
+
(preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
|
|
437
|
+
// Auth, middleware and rewrites ran again. A changed target or a
|
|
438
|
+
// redirect cannot reveal an obsolete protected prefetched segment.
|
|
439
|
+
if (destination.pathname !== preview.url.pathname || destination.search !== preview.url.search) return;
|
|
440
|
+
updateHistory(destination); historyUpdated = true;
|
|
441
|
+
startTransition(() => root.render(appView({ model: preview.model, controller, url: destination.href, globalErrorComponent, globalErrorCss })));
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
const abort = new AbortController();
|
|
446
|
+
activeRequest = abort;
|
|
447
|
+
result = await requestFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting);
|
|
448
|
+
}
|
|
449
|
+
if (token !== navigation) return;
|
|
450
|
+
if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
|
|
451
|
+
if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
|
|
452
|
+
prepareLayoutCache(result.model, currentModel);
|
|
453
|
+
currentRouting = result.model.routing;
|
|
454
|
+
currentModel = result.model;
|
|
455
|
+
if (currentRouting) invalidatePrefetches();
|
|
456
|
+
if (!historyUpdated) updateHistory(result.url);
|
|
457
|
+
if (currentRouting) historyModels.set(currentKey, currentModel);
|
|
458
|
+
while (historyModels.size > 16) historyModels.delete(historyModels.keys().next().value);
|
|
459
|
+
const committed = () => {
|
|
460
|
+
if (token === navigation && scroll && !refresh) scrollToTarget(result.url, savedScroll);
|
|
461
|
+
};
|
|
462
|
+
startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
|
|
463
|
+
} catch (error) {
|
|
464
|
+
if (token !== navigation || error?.name === 'AbortError') return;
|
|
465
|
+
// Pages Router routes, public files, and unavailable Flight endpoints use
|
|
466
|
+
// a normal document navigation, including the browser's redirect handling.
|
|
467
|
+
if (replace || pop || refresh) window.location.replace(target.href);
|
|
468
|
+
else window.location.assign(target.href);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const controller = {
|
|
473
|
+
partialPrefetch: cacheComponents,
|
|
474
|
+
push: (href, options) => { void navigate(navigationUrl(href), options); },
|
|
475
|
+
replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
|
|
476
|
+
refresh: () => {
|
|
477
|
+
invalidatePrefetches();
|
|
478
|
+
void navigate(new URL(window.location.href), { refresh: true });
|
|
479
|
+
},
|
|
480
|
+
back: () => { saveScroll(); window.history.back(); },
|
|
481
|
+
forward: () => { saveScroll(); window.history.forward(); },
|
|
482
|
+
prefetch(href, options = {}) {
|
|
483
|
+
const target = navigationUrl(href);
|
|
484
|
+
if (!isApplicationURL(target, window.location.origin, basePath)) return;
|
|
485
|
+
target.hash = '';
|
|
486
|
+
const key = target.href;
|
|
487
|
+
if (prefetches.has(key)) return;
|
|
488
|
+
while (prefetches.size >= PREFETCH_LIMIT) dropPrefetch(prefetches.keys().next().value, true);
|
|
489
|
+
const abort = new AbortController();
|
|
490
|
+
const entry = { abort, onInvalidate: options.onInvalidate };
|
|
491
|
+
entry.promise = cacheComponents && !window.__PRNEXT_STATIC_EXPORT__ ? requestPartial(target, abort.signal) : requestFlight(target, abort.signal);
|
|
492
|
+
// A failed speculative fetch must never produce an unhandled rejection.
|
|
493
|
+
entry.promise.catch(() => { if (prefetches.get(key) === entry) dropPrefetch(key, true); });
|
|
494
|
+
entry.timer = setTimeout(() => dropPrefetch(key, true), PREFETCH_LIFETIME);
|
|
495
|
+
prefetches.set(key, entry);
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const initialView = appView({ model: initialModel, controller, globalErrorComponent, globalErrorCss });
|
|
500
|
+
const rootOptions = {
|
|
501
|
+
formState: initialModel.formState ?? null,
|
|
502
|
+
onCaughtError(error) {
|
|
503
|
+
if (!getNavigationControl(error)) {
|
|
504
|
+
console.error(error);
|
|
505
|
+
if (process.env.NODE_ENV === 'development') globalThis.__PRNEXT_DEV__?.reportError(error);
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
onRecoverableError(error) {
|
|
509
|
+
if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause)) console.error(error);
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
if (document.documentElement.id === '__prnext_error__') {
|
|
513
|
+
// A fatal server render intentionally sends an empty document. Its Flight
|
|
514
|
+
// failure is retried on the client, so there is no server tree to hydrate.
|
|
515
|
+
root = createRoot(document, rootOptions);
|
|
516
|
+
root.render(initialView);
|
|
517
|
+
} else root = hydrateRoot(document, initialView, rootOptions);
|
|
518
|
+
window.addEventListener('popstate', event => {
|
|
519
|
+
void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
|
|
520
|
+
});
|
|
521
|
+
window.addEventListener('pagehide', saveScroll);
|
|
522
|
+
let scrollFrame;
|
|
523
|
+
window.addEventListener('scroll', () => {
|
|
524
|
+
if (scrollFrame !== undefined) return;
|
|
525
|
+
scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
|
|
526
|
+
}, { passive: true });
|
|
527
|
+
return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
|
|
528
|
+
devPrepare: options => moduleLoader.updateModules(options.clientModules),
|
|
529
|
+
devCommit: () => controller.refresh(),
|
|
530
|
+
} : {}) };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function bootstrapApp(options) {
|
|
534
|
+
if (process.env.NODE_ENV === 'development' && options.dev && globalThis.__PRNEXT_DEV__) {
|
|
535
|
+
return globalThis.__PRNEXT_DEV__.bootstrap('app', options, () => startApp(options));
|
|
536
|
+
}
|
|
537
|
+
application ||= startApp(options);
|
|
538
|
+
return application;
|
|
539
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LayoutCacheContext } from '../compat/app-layout-context.cjs';
|
|
3
|
+
|
|
4
|
+
// Preserve the same sibling positions during SSR, PPR and hydration. Even an
|
|
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(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles);
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function appErrorDigest(error) {
|
|
4
|
+
return typeof error?.digest === 'string' ? error.digest
|
|
5
|
+
: createHash('sha256').update(String(error?.stack || error?.message || error)).digest('hex').slice(0, 16);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function appTimeoutError(phase, milliseconds) {
|
|
9
|
+
const error = new Error(`App Router ${phase} timed out after ${milliseconds / 1000} seconds`);
|
|
10
|
+
error.statusCode = 504;
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderFlight, decodeFlight } from './app-render.mjs';
|
|
3
|
+
import { requestOptions } from './app-static.mjs';
|
|
4
|
+
import { prerenderPartialHtml } from './app-partial.mjs';
|
|
5
|
+
|
|
6
|
+
export async function validateInstantRoute(options) {
|
|
7
|
+
const request = requestOptions(options);
|
|
8
|
+
if (!request.cacheComponents || options.route.internal) return [];
|
|
9
|
+
const warnings = [];
|
|
10
|
+
for (const [index, sample] of (options.route.instantSamples || [undefined]).entries()) {
|
|
11
|
+
const sampled = sample ? sampleRequest(request, sample) : request;
|
|
12
|
+
const result = await renderFlight({ ...sampled, instantValidation: true, staticGeneration: { mode: 'auto', partial: true } });
|
|
13
|
+
const model = await decodeFlight(result.body, request.clientModules, request.distDir, { production: request.production });
|
|
14
|
+
for (const segment of model.instantTrees || []) {
|
|
15
|
+
const shell = await prerenderPartialHtml({ router: model.router, tree: React.createElement('html', null,
|
|
16
|
+
React.createElement('head'), React.createElement('body', null, segment.tree)) }, {}, { navigation: true });
|
|
17
|
+
if (!shell) warnings.push(`Route ${request.routePattern}${sample ? ` (sample ${index + 1})` : ''}: instant navigation below shared layout ${segment.path} would block on uncached data. Add a loading.js or a Suspense boundary below that layout, or cache the data with "use cache".`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (warnings.length && request.production) throw Object.assign(new Error(warnings.join('\n')), { code: 'PRNEXT_INSTANT_BLOCKING' });
|
|
21
|
+
return warnings;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sampleRequest(request, sample) {
|
|
25
|
+
const url = new URL(request.url);
|
|
26
|
+
for (const [name, value] of Object.entries(sample.searchParams || {})) if (value !== null) for (const item of Array.isArray(value) ? value : [value]) url.searchParams.append(name, item);
|
|
27
|
+
const headers = Object.fromEntries((sample.headers || []).filter(([,value]) => value !== null));
|
|
28
|
+
if (sample.cookies) headers.cookie = sample.cookies.filter(item=>item.value !== null).map(item=>`${item.name}=${encodeURIComponent(item.value)}`).join('; ');
|
|
29
|
+
return {...request, url:url.href, headers, params:{...request.params,...sample.params}, instantSample:sample};
|
|
30
|
+
}
|