@thomas.f/prnext 0.1.18-alpha → 0.1.20-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/build/actions.mjs +26 -14
- package/build/app.mjs +90 -101
- package/build/build-lock.mjs +2 -71
- package/build/build-retention.mjs +119 -0
- package/build/bundle-runtime.mjs +9 -8
- package/build/cache-command.mjs +31 -0
- package/build/check.mjs +41 -3
- package/build/compilation-cache.mjs +32 -12
- package/build/compilation-storage.mjs +190 -0
- package/build/compiler.mjs +8 -7
- package/build/config.mjs +25 -2
- package/build/create.mjs +1 -1
- package/build/css-modules.mjs +4 -0
- package/build/dev-action-graph.mjs +21 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +14 -12
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dynamic.mjs +13 -6
- package/build/font-cache.mjs +9 -6
- package/build/font-files.mjs +1 -1
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +61 -15
- package/build/lazy-dynamic-imports.mjs +137 -0
- package/build/migrate.mjs +4 -4
- package/build/migration-audit.mjs +19 -31
- package/build/migration-imports.mjs +245 -0
- package/build/migration-scenarios-runner.mjs +73 -0
- package/build/migration-scenarios.mjs +72 -0
- package/build/native-discovery.mjs +65 -5
- package/build/native-packages.mjs +2 -0
- package/build/native-ppr.mjs +32 -0
- package/build/npm-packages.mjs +1 -0
- package/build/og.mjs +121 -1
- package/build/parallel-plan.mjs +55 -0
- package/build/persistent-cache.mjs +29 -24
- package/build/production-builder.mjs +1 -1
- package/build/production-check.mjs +34 -0
- package/build/react-compiler-report.mjs +31 -8
- package/build/react-compiler.mjs +101 -10
- package/build/server-config.mjs +35 -0
- package/build/source-analysis.mjs +54 -4
- package/build/standalone.mjs +21 -3
- package/build/static-adapter.mjs +7 -3
- package/build/transform.mjs +38 -6
- package/build/typescript-dev.mjs +3 -1
- package/build/webpack-graph.mjs +13 -3
- package/cli.mjs +24 -6
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +7 -3
- package/compat/cache-handlers.cjs +3 -1
- package/compat/flight-delta.cjs +50 -13
- package/compat/index.d.ts +24 -1
- package/compat/link.cjs +2 -2
- package/compat/navigation-authorization.cjs +8 -1
- package/docs/ai/compatibility.md +187 -7
- package/docs/ai/deployment.md +141 -0
- package/docs/ai/quickstart.md +5 -0
- package/docs/ai/routing.md +11 -0
- package/docs/ai/troubleshooting.md +79 -0
- package/package.json +26 -11
- package/release/fontkit.mjs +31 -0
- package/release/verify.mjs +3 -0
- package/runtime/api.mjs +5 -2
- package/runtime/app-client.mjs +254 -53
- package/runtime/app-partial.mjs +2 -10
- package/runtime/app-prefetch.mjs +2 -2
- package/runtime/app-render.mjs +11 -7
- package/runtime/app-stream-channel.mjs +3 -2
- package/runtime/app-styles.mjs +22 -4
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +3 -1
- package/runtime/compile-cache-retention.mjs +53 -0
- package/runtime/compile-cache.mjs +28 -1
- package/runtime/dev-client.mjs +13 -5
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-gateway.mjs +3 -2
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-route-selection.mjs +64 -0
- package/runtime/dev-runtime-pool.mjs +3 -2
- package/runtime/dev-runtime-spare.mjs +1 -1
- package/runtime/dev-session-retention.mjs +154 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +86 -26
- package/runtime/env.mjs +9 -0
- package/runtime/flight-delta.mjs +59 -9
- package/runtime/fs.mjs +15 -8
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/native-ppr.mjs +35 -0
- package/runtime/og/NOTICE.txt +1 -1
- package/runtime/og/index.node.mjs +76 -31216
- package/runtime/og/upstream.json +115 -0
- package/runtime/persistent/client.mjs +4 -2
- package/runtime/persistent/daemon.mjs +3 -11
- package/runtime/persistent/health.mjs +58 -0
- package/runtime/project-lock.mjs +71 -0
- package/runtime/request-transport.mjs +112 -32
- package/runtime/storage-path.mjs +36 -0
- package/runtime/trace.mjs +44 -4
- package/runtime/worker.mjs +9 -4
- package/vendor/fontkit/NOTICE.txt +5 -0
- package/vendor/fontkit/SOURCE-NOTICES.txt +209 -0
- package/vendor/fontkit/index.mjs +3 -0
- package/vendor/fontkit/licenses/@swc__helpers/LICENSE +201 -0
- package/vendor/fontkit/licenses/base64-js/LICENSE +21 -0
- package/vendor/fontkit/licenses/brotli/readme.md +61 -0
- package/vendor/fontkit/licenses/clone/LICENSE +18 -0
- package/vendor/fontkit/licenses/dfa/README.md +83 -0
- package/vendor/fontkit/licenses/fast-deep-equal/LICENSE +21 -0
- package/vendor/fontkit/licenses/fontkit/README.md +266 -0
- package/vendor/fontkit/licenses/restructure/LICENSE +21 -0
- package/vendor/fontkit/licenses/tiny-inflate/LICENSE +21 -0
- package/vendor/fontkit/licenses/tslib/LICENSE.txt +12 -0
- package/vendor/fontkit/licenses/unicode-properties/LICENSE +8 -0
- package/vendor/fontkit/licenses/unicode-trie/LICENSE +8 -0
- package/vendor/fontkit/upstream.json +215 -0
- package/vendor/nft/LICENSE +7 -0
- package/vendor/nft/README.md +16 -0
- package/vendor/nft/out/analyze.d.ts +8 -0
- package/vendor/nft/out/analyze.js +1216 -0
- package/vendor/nft/out/analyze.js.map +1 -0
- package/vendor/nft/out/cli.d.ts +2 -0
- package/vendor/nft/out/cli.js +94 -0
- package/vendor/nft/out/cli.js.map +1 -0
- package/vendor/nft/out/fs.d.ts +22 -0
- package/vendor/nft/out/fs.js +106 -0
- package/vendor/nft/out/fs.js.map +1 -0
- package/vendor/nft/out/index.d.ts +4 -0
- package/vendor/nft/out/index.js +25 -0
- package/vendor/nft/out/index.js.map +1 -0
- package/vendor/nft/out/node-file-trace.d.ts +41 -0
- package/vendor/nft/out/node-file-trace.js +380 -0
- package/vendor/nft/out/node-file-trace.js.map +1 -0
- package/vendor/nft/out/resolve-dependency.d.ts +6 -0
- package/vendor/nft/out/resolve-dependency.js +427 -0
- package/vendor/nft/out/resolve-dependency.js.map +1 -0
- package/vendor/nft/out/types.d.ts +66 -0
- package/vendor/nft/out/types.js +2 -0
- package/vendor/nft/out/types.js.map +1 -0
- package/vendor/nft/out/utils/ast-helpers.js +49 -0
- package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
- package/vendor/nft/out/utils/binary-locators.js +88 -0
- package/vendor/nft/out/utils/binary-locators.js.map +1 -0
- package/vendor/nft/out/utils/get-package-base.js +30 -0
- package/vendor/nft/out/utils/get-package-base.js.map +1 -0
- package/vendor/nft/out/utils/interop-require.js +24 -0
- package/vendor/nft/out/utils/interop-require.js.map +1 -0
- package/vendor/nft/out/utils/node-version.js +10 -0
- package/vendor/nft/out/utils/node-version.js.map +1 -0
- package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
- package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
- package/vendor/nft/out/utils/special-cases.js +347 -0
- package/vendor/nft/out/utils/special-cases.js.map +1 -0
- package/vendor/nft/out/utils/static-eval.js +579 -0
- package/vendor/nft/out/utils/static-eval.js.map +1 -0
- package/vendor/nft/out/utils/types.js +2 -0
- package/vendor/nft/out/utils/types.js.map +1 -0
- package/vendor/nft/out/utils/wrappers.js +659 -0
- package/vendor/nft/out/utils/wrappers.js.map +1 -0
- package/vendor/nft/package.json +4 -0
- package/vendor/nft/upstream.json +51 -0
package/runtime/app-client.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const PREFETCH_LIMIT = 8;
|
|
|
26
26
|
const PREFETCH_LIFETIME = 30_000;
|
|
27
27
|
let application;
|
|
28
28
|
let strictMode = true;
|
|
29
|
+
const navigationStreams = new WeakMap();
|
|
29
30
|
|
|
30
31
|
function loadClientModule(source) {
|
|
31
32
|
if (typeof source === 'function') return source();
|
|
@@ -39,7 +40,9 @@ function loadClientModule(source) {
|
|
|
39
40
|
// supplies ESM import functions so React and application contexts stay shared.
|
|
40
41
|
export function installFlightModuleLoader(clientModules) {
|
|
41
42
|
const loaded = new Map();
|
|
43
|
+
const loadedVersions = new Map();
|
|
42
44
|
const pending = new Map();
|
|
45
|
+
let version = 0;
|
|
43
46
|
const previousRequire = globalThis.__webpack_require__;
|
|
44
47
|
const previousLoad = globalThis.__webpack_chunk_load__;
|
|
45
48
|
globalThis.__webpack_require__ = id => {
|
|
@@ -52,32 +55,42 @@ export function installFlightModuleLoader(clientModules) {
|
|
|
52
55
|
return typeof source === 'string' ? source : source?.browserModule || String(id);
|
|
53
56
|
};
|
|
54
57
|
globalThis.__webpack_get_script_filename__ = id => globalThis.__webpack_require__.u(id);
|
|
55
|
-
|
|
56
|
-
if (loaded.has(id)) return Promise.resolve();
|
|
58
|
+
function loadChunk(id) {
|
|
57
59
|
if (pending.has(id)) return pending.get(id);
|
|
60
|
+
if (loaded.has(id) && loadedVersions.get(id) === version) return Promise.resolve();
|
|
58
61
|
if (!Object.hasOwn(clientModules, id)) {
|
|
59
62
|
if (previousLoad) return previousLoad(id);
|
|
60
63
|
return Promise.reject(new Error(`Unknown PRNext client module ${id}`));
|
|
61
64
|
}
|
|
62
65
|
const source = clientModules[id];
|
|
66
|
+
const requestedVersion = version;
|
|
67
|
+
const current = () => {
|
|
68
|
+
if (!Object.hasOwn(clientModules, id)) throw new Error(`Unknown PRNext client module ${id}`);
|
|
69
|
+
return loadChunk(id);
|
|
70
|
+
};
|
|
63
71
|
const promise = Promise.resolve().then(() => loadClientModule(source))
|
|
64
|
-
.then(module => {
|
|
65
|
-
|
|
72
|
+
.then(module => {
|
|
73
|
+
if (requestedVersion !== version) return current();
|
|
74
|
+
loaded.set(id, module); loadedVersions.set(id, version);
|
|
75
|
+
}, error => {
|
|
76
|
+
if (requestedVersion !== version) return current();
|
|
77
|
+
loaded.delete(id); loadedVersions.delete(id);
|
|
66
78
|
throw error;
|
|
79
|
+
}).finally(() => {
|
|
80
|
+
if (pending.get(id) === promise) pending.delete(id);
|
|
67
81
|
});
|
|
68
82
|
pending.set(id, promise);
|
|
69
83
|
return promise;
|
|
70
|
-
}
|
|
84
|
+
}
|
|
85
|
+
globalThis.__webpack_chunk_load__ = loadChunk;
|
|
71
86
|
return {
|
|
72
87
|
async updateModules(nextModules) {
|
|
73
|
-
const ids = [...loaded.keys()].filter(id => Object.hasOwn(nextModules, id));
|
|
88
|
+
const ids = [...new Set([...loaded.keys(), ...pending.keys()])].filter(id => Object.hasOwn(nextModules, id));
|
|
89
|
+
version++;
|
|
74
90
|
clientModules = nextModules;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
loaded.set(id, module);
|
|
79
|
-
}));
|
|
80
|
-
for (const id of loaded.keys()) if (!Object.hasOwn(nextModules, id)) loaded.delete(id);
|
|
91
|
+
pending.clear();
|
|
92
|
+
for (const id of loaded.keys()) if (!Object.hasOwn(nextModules, id)) { loaded.delete(id); loadedVersions.delete(id); }
|
|
93
|
+
await Promise.all(ids.map(loadChunk));
|
|
81
94
|
},
|
|
82
95
|
};
|
|
83
96
|
}
|
|
@@ -132,10 +145,11 @@ function NormalStyles({ css, fonts }) {
|
|
|
132
145
|
|
|
133
146
|
function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss, retentionEpoch, retentionAuthorizationEpoch }) {
|
|
134
147
|
const [hydrated, setHydrated] = React.useState(false);
|
|
148
|
+
const flight = navigationStreams.get(model);
|
|
135
149
|
const cache = React.useRef(null);
|
|
136
150
|
if (!cache.current) cache.current = createActivityCache({ limit: controller._navigationCache?.maxEntries, ttl: controller._navigationCache?.ttlMs });
|
|
137
151
|
const activityCache = React.useMemo(() => ({ cache: cache.current, epoch: retentionEpoch, authorizationEpoch: retentionAuthorizationEpoch,
|
|
138
|
-
authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey]);
|
|
152
|
+
authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined, flight }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey, flight]);
|
|
139
153
|
const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
|
|
140
154
|
useLayoutEffect(() => {
|
|
141
155
|
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' }));
|
|
@@ -238,12 +252,18 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
238
252
|
if (initialRewrite) initialModel.rewrite = initialRewrite;
|
|
239
253
|
let currentUrl = new URL(window.location.href);
|
|
240
254
|
let currentRouting = initialModel.routing;
|
|
255
|
+
// A pending destination may already own the history entry while React still
|
|
256
|
+
// shows the previous view. Rolling back its render must not rewrite that
|
|
257
|
+
// entry with the previous view's routing state on the next scroll event.
|
|
258
|
+
let historyRouting = currentRouting;
|
|
241
259
|
let currentModel = initialModel;
|
|
260
|
+
let committedView = { model: initialModel, routing: currentRouting, url: currentUrl.href, retentionEpoch: 0, authorizationEpoch: 0 };
|
|
261
|
+
let pendingView, pendingNavigation;
|
|
242
262
|
const historyFocus = new Map();
|
|
243
263
|
let currentKey = window.history.state?.__prnext?.key || 0;
|
|
244
264
|
let nextKey = currentKey + 1;
|
|
245
265
|
let navigation = 0;
|
|
246
|
-
let activeRequest, finishNavigation, pageHidden = false, notifyingPrefetch = false;
|
|
266
|
+
let activeRequest, finishNavigation, finishCommit, documentTimer, pageHidden = false, notifyingPrefetch = false, documentFallback = false;
|
|
247
267
|
const prefetches = new Map(), scheduler = createPrefetchScheduler();
|
|
248
268
|
const partialSegments = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? new Map() : null;
|
|
249
269
|
let segmentBytes = 0;
|
|
@@ -254,12 +274,102 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
254
274
|
function saveScroll() {
|
|
255
275
|
// popstate has already changed location; never overwrite its destination.
|
|
256
276
|
if (window.location.href === currentUrl.href) {
|
|
257
|
-
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY],
|
|
277
|
+
window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], historyRouting), '', currentUrl.href);
|
|
258
278
|
if (typeof WeakRef !== 'undefined' && document.activeElement && document.activeElement !== document.body) historyFocus.set(currentKey, new WeakRef(document.activeElement));
|
|
259
279
|
while (historyFocus.size > 16) historyFocus.delete(historyFocus.keys().next().value);
|
|
260
280
|
}
|
|
261
281
|
}
|
|
262
282
|
|
|
283
|
+
function renderView(model, url, { token = navigation, canonical = true, kind = 'navigation', committed } = {}) {
|
|
284
|
+
const flight = navigationStreams.get(model), view = { model, token, kind, committed: false };
|
|
285
|
+
pendingView = view;
|
|
286
|
+
const promote = () => {
|
|
287
|
+
if (token !== navigation || pendingView !== view || !view.committed || flight?.complete === false) return;
|
|
288
|
+
pendingView = undefined;
|
|
289
|
+
if (flight) flight.committed = undefined;
|
|
290
|
+
// A committed loading shell can still contain rows owned by a live
|
|
291
|
+
// fetch. Keep the last fully received view available before aborting it.
|
|
292
|
+
// Authorized PPR previews likewise never become the canonical model.
|
|
293
|
+
if (canonical) committedView = { model, routing: model.routing, url, retentionEpoch: controller._retentionEpoch, authorizationEpoch: controller._authorizationEpoch };
|
|
294
|
+
};
|
|
295
|
+
if (flight) flight.committed = promote;
|
|
296
|
+
startTransition(() => root.render(appView({ model, controller, url, globalErrorComponent, globalErrorCss,
|
|
297
|
+
committed: () => {
|
|
298
|
+
if (token !== navigation || pendingView !== view) return;
|
|
299
|
+
view.committed = true;
|
|
300
|
+
promote();
|
|
301
|
+
committed?.();
|
|
302
|
+
},
|
|
303
|
+
})));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function abandonPendingView() {
|
|
307
|
+
if (!pendingView) return true;
|
|
308
|
+
const flight = navigationStreams.get(pendingView.model);
|
|
309
|
+
const strictAuthorization = controller._navigationCache?.authCookies !== undefined;
|
|
310
|
+
if (flight) flight.committed = undefined;
|
|
311
|
+
if (pendingView.kind === 'action' && committedView.authorizationEpoch === controller._authorizationEpoch &&
|
|
312
|
+
(!strictAuthorization || committedView.model.authorizationKey === currentModel.authorizationKey)) {
|
|
313
|
+
// An action's received result can invoke a returned action before React
|
|
314
|
+
// commits its UI. This response is never owned/aborted by activeRequest;
|
|
315
|
+
// adopt its model instead of rolling back the prior mutation's epoch.
|
|
316
|
+
pendingView = undefined;
|
|
317
|
+
committedView = { model: currentModel, routing: currentRouting, url: currentUrl.href, retentionEpoch: controller._retentionEpoch, authorizationEpoch: controller._authorizationEpoch };
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
// A new authorized shell or explicit invalidation can make the last full
|
|
321
|
+
// model private to an obsolete session. Reauthorize through a document
|
|
322
|
+
// request instead of briefly restoring that model while C is pending.
|
|
323
|
+
if ((strictAuthorization && committedView.model.authorizationKey !== currentModel.authorizationKey) ||
|
|
324
|
+
committedView.retentionEpoch !== controller._retentionEpoch ||
|
|
325
|
+
committedView.authorizationEpoch !== controller._authorizationEpoch) return false;
|
|
326
|
+
pendingView = undefined;
|
|
327
|
+
currentModel = committedView.model;
|
|
328
|
+
currentRouting = committedView.routing;
|
|
329
|
+
// React can already be reading a lazy row after createFromFetch resolves
|
|
330
|
+
// the root model. Replace that suspended render before abort rejects its
|
|
331
|
+
// remaining rows; it must not poison the retained layout or page cache.
|
|
332
|
+
root.render(appView({ model: committedView.model, controller, url: committedView.url, globalErrorComponent, globalErrorCss }));
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function fallbackDocument(url, replace = true, defer = false) {
|
|
337
|
+
// The invalidated document stays blocked until its replacement arrives.
|
|
338
|
+
// Repeated synchronous actions must neither dispatch nor start reloads.
|
|
339
|
+
if (documentFallback) return;
|
|
340
|
+
documentFallback = true;
|
|
341
|
+
navigation++;
|
|
342
|
+
activeRequest?.abort();
|
|
343
|
+
appStyleManager(document).cancel();
|
|
344
|
+
finishNavigation?.(); finishNavigation = undefined;
|
|
345
|
+
invalidatePrefetches();
|
|
346
|
+
flightTransport.clear();
|
|
347
|
+
if (controller._navigationCache?.authCookies !== undefined) document.documentElement.style.visibility = 'hidden';
|
|
348
|
+
const request = () => {
|
|
349
|
+
documentTimer = undefined;
|
|
350
|
+
if (pageHidden) return;
|
|
351
|
+
if (replace) window.location.replace(url);
|
|
352
|
+
else window.location.assign(url);
|
|
353
|
+
};
|
|
354
|
+
// An executed action must settle its promise chain before document
|
|
355
|
+
// replacement suspends the old realm. Its POST guard is already latched.
|
|
356
|
+
if (defer) documentTimer = setTimeout(request, 0);
|
|
357
|
+
else request();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function requiresAuthorizedDocument(model) {
|
|
361
|
+
if (controller._navigationCache?.authCookies === undefined) return false;
|
|
362
|
+
const identity = model?.authorizationKey;
|
|
363
|
+
// Activity entries are only part of the retained tree: shared client
|
|
364
|
+
// layouts and server layout-cache props can also belong to the old user.
|
|
365
|
+
// An unidentified static model cannot prove that HttpOnly cookies stayed
|
|
366
|
+
// the same. Cross this boundary before adopting any cached layout state.
|
|
367
|
+
return typeof identity !== 'string' || !identity || identity !== currentModel.authorizationKey ||
|
|
368
|
+
committedView.authorizationEpoch !== controller._authorizationEpoch;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const actionReloadError = () => new Error('The session changed while this page was loading. Reload before repeating the action.');
|
|
372
|
+
|
|
263
373
|
const flightTransport = createFlightTransport();
|
|
264
374
|
async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false,fullFlight=false}={}) {
|
|
265
375
|
if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
|
|
@@ -282,12 +392,14 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
282
392
|
}
|
|
283
393
|
await onPartial?.(response, destination);
|
|
284
394
|
let model;
|
|
285
|
-
|
|
395
|
+
const flight = { complete: false };
|
|
396
|
+
try { model = assertModel(await createFromFetch(Promise.resolve(await flightTransport.response(response,base,{onEnd(){flight.complete = true;flight.committed?.();}})), flightOptions)); }
|
|
286
397
|
catch(error) {
|
|
287
398
|
if(!delta||fullFlight||signal?.aborted)throw error;
|
|
288
399
|
flightTransport.clear();
|
|
289
400
|
return requestFlight(url,signal,routing,onPartial,{skipEnsure:true,fullFlight:true});
|
|
290
401
|
}
|
|
402
|
+
navigationStreams.set(model, flight);
|
|
291
403
|
model.router.basePath = basePath;
|
|
292
404
|
const rewrite = readRewriteHeader(response);
|
|
293
405
|
if (rewrite) model.rewrite = rewrite;
|
|
@@ -364,12 +476,20 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
364
476
|
|
|
365
477
|
function callServer(id, args) {
|
|
366
478
|
if (!root) return Promise.reject(new Error('Server Actions cannot run during initial rendering'));
|
|
479
|
+
if (pageHidden || documentFallback) return Promise.reject(actionReloadError());
|
|
367
480
|
const token = ++navigation;
|
|
481
|
+
finishCommit?.();
|
|
368
482
|
const target = new URL(window.location.href);
|
|
483
|
+
saveScroll();
|
|
484
|
+
const restored = abandonPendingView();
|
|
369
485
|
activeRequest?.abort();
|
|
370
486
|
appStyleManager(document).cancel();
|
|
371
487
|
finishNavigation?.(); finishNavigation = undefined;
|
|
372
488
|
activeRequest = undefined;
|
|
489
|
+
if (!restored) {
|
|
490
|
+
fallbackDocument(target.href);
|
|
491
|
+
return Promise.reject(actionReloadError());
|
|
492
|
+
}
|
|
373
493
|
purgeRetainedPages();
|
|
374
494
|
// Mutations are dispatched in order, including Set-Cookie effects required
|
|
375
495
|
// by a subsequent action. Capture their route priority when invoked: a
|
|
@@ -380,14 +500,29 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
380
500
|
}
|
|
381
501
|
|
|
382
502
|
async function dispatchAction(id, args, token, target) {
|
|
503
|
+
if (pageHidden || documentFallback) throw actionReloadError();
|
|
383
504
|
const release = scheduler.navigation();
|
|
384
505
|
try { return await performAction(id, args, token, target); } finally { release(); }
|
|
385
506
|
}
|
|
386
507
|
|
|
508
|
+
async function waitForDevelopmentUpdate() {
|
|
509
|
+
// A generation updates the Flight loader before React Refresh registers
|
|
510
|
+
// the replacement component families. An already-running action may finish
|
|
511
|
+
// in that interval; rendering its model immediately remounts those client
|
|
512
|
+
// components and loses layout state. Wait only on development bookkeeping,
|
|
513
|
+
// never replay the mutation or cancel a newer user navigation.
|
|
514
|
+
let development = globalThis.__PRNEXT_DEV__;
|
|
515
|
+
while (development?.applying && development.applied) {
|
|
516
|
+
await development.applied;
|
|
517
|
+
development = globalThis.__PRNEXT_DEV__;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
387
521
|
async function performAction(id, args, token, target) {
|
|
388
522
|
invalidatePrefetches();
|
|
389
523
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
390
524
|
const body = await encodeReply(args, { temporaryReferences });
|
|
525
|
+
if (pageHidden || documentFallback) throw actionReloadError();
|
|
391
526
|
const response = await fetch(target.href, {
|
|
392
527
|
method: 'POST',
|
|
393
528
|
headers: { ...deploymentHeaders(deploymentId), 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
|
|
@@ -404,15 +539,37 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
404
539
|
throw new Error(`Server Action request failed (${response.status})`);
|
|
405
540
|
}
|
|
406
541
|
const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
|
|
542
|
+
const outcome = () => {
|
|
543
|
+
if (model?.actionError) {
|
|
544
|
+
const error = new Error(model.actionError.message || 'The Server Action failed');
|
|
545
|
+
if (model.actionError.digest !== undefined) error.digest = model.actionError.digest;
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
return model?.actionResult;
|
|
549
|
+
};
|
|
550
|
+
if (process.env.NODE_ENV === 'development') await waitForDevelopmentUpdate();
|
|
407
551
|
controller._retentionEpoch++;
|
|
552
|
+
const validOutcome = model && typeof model === 'object' && (Object.hasOwn(model, 'tree') || Object.hasOwn(model, 'actionResult') || model.actionError || model.actionRedirect || model.routingInvalid);
|
|
553
|
+
if (!validOutcome) throw new Error('The Server Action response did not contain a result');
|
|
554
|
+
if (Object.hasOwn(model, 'tree') && !model.routingInvalid) assertModel(model);
|
|
555
|
+
if (requiresAuthorizedDocument(model)) {
|
|
556
|
+
// A queued action or newer navigation can supersede the render token,
|
|
557
|
+
// but cannot make an observed account change safe to ignore. Complete
|
|
558
|
+
// this one POST and block the queue before any subsequent mutation.
|
|
559
|
+
const latest = pendingNavigation?.token === navigation ? pendingNavigation : undefined;
|
|
560
|
+
const redirected = token === navigation && model.actionRedirect ? navigationUrl(model.actionRedirect.url) : undefined;
|
|
561
|
+
const destination = latest?.url || redirected?.href || window.location.href;
|
|
562
|
+
fallbackDocument(destination, latest ? latest.replace : redirected ? model.actionRedirect.type === 'replace' : true, true);
|
|
563
|
+
return outcome();
|
|
564
|
+
}
|
|
408
565
|
if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
|
|
409
|
-
if (token === navigation)
|
|
410
|
-
return
|
|
566
|
+
if (token === navigation) fallbackDocument(target.href);
|
|
567
|
+
return outcome();
|
|
411
568
|
}
|
|
412
569
|
if (model?.actionRedirect) {
|
|
413
570
|
const destination = navigationUrl(model.actionRedirect.url);
|
|
414
571
|
if (token === navigation) await navigate(destination, { replace: model.actionRedirect.type === 'replace' });
|
|
415
|
-
return
|
|
572
|
+
return outcome();
|
|
416
573
|
}
|
|
417
574
|
if (model && Object.hasOwn(model, 'tree') && token === navigation) {
|
|
418
575
|
assertModel(model);
|
|
@@ -421,42 +578,48 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
421
578
|
if (rewrite) model.rewrite = rewrite;
|
|
422
579
|
prepareLayoutCache(model, currentModel);
|
|
423
580
|
try { await appStyleManager(document).prepare(model.globalError ? [] : model.css || []); }
|
|
424
|
-
catch (error) { if (token !== navigation) return
|
|
425
|
-
if (
|
|
581
|
+
catch (error) { if (token !== navigation) return outcome(); throw error; }
|
|
582
|
+
if (process.env.NODE_ENV === 'development') await waitForDevelopmentUpdate();
|
|
583
|
+
if (token !== navigation) return outcome();
|
|
426
584
|
currentRouting = model.routing;
|
|
427
585
|
currentModel = model;
|
|
586
|
+
historyRouting = currentRouting;
|
|
428
587
|
appStyleManager(document).handoff(model.globalError ? [] : model.css || []);
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
if (model?.actionError) {
|
|
432
|
-
const error = new Error(model.actionError.message || 'The Server Action failed');
|
|
433
|
-
if (model.actionError.digest !== undefined) error.digest = model.actionError.digest;
|
|
434
|
-
throw error;
|
|
588
|
+
renderView(model, target.href, { token, kind: 'action' });
|
|
435
589
|
}
|
|
590
|
+
if (model?.actionError) return outcome();
|
|
436
591
|
// Flight's JSON reviver removes object fields encoded as $undefined. A
|
|
437
592
|
// successful void action therefore has an updated tree but no own result.
|
|
438
593
|
if (!model || typeof model !== 'object' || (!Object.hasOwn(model, 'actionResult') && !Object.hasOwn(model, 'tree'))) {
|
|
439
594
|
throw new Error('The Server Action response did not contain a result');
|
|
440
595
|
}
|
|
441
|
-
return
|
|
596
|
+
return outcome();
|
|
442
597
|
}
|
|
443
598
|
|
|
444
599
|
async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
|
|
445
|
-
if (pageHidden) return;
|
|
600
|
+
if (pageHidden || documentFallback) return;
|
|
446
601
|
const target = navigationUrl(href);
|
|
447
602
|
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) {
|
|
448
|
-
|
|
449
|
-
else window.location.assign(target.href);
|
|
603
|
+
fallbackDocument(target.href, replace);
|
|
450
604
|
return;
|
|
451
605
|
}
|
|
452
606
|
const token = ++navigation;
|
|
607
|
+
const intent = { token, url: target.href, replace: replace || pop || refresh };
|
|
608
|
+
pendingNavigation = intent;
|
|
609
|
+
finishCommit?.();
|
|
610
|
+
saveScroll();
|
|
611
|
+
const restored = abandonPendingView();
|
|
453
612
|
activeRequest?.abort();
|
|
454
613
|
appStyleManager(document).cancel();
|
|
455
614
|
finishNavigation?.(); finishNavigation = undefined;
|
|
456
615
|
activeRequest = undefined;
|
|
457
|
-
|
|
616
|
+
if (!restored) {
|
|
617
|
+
fallbackDocument(target.href, replace || pop || refresh);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
458
620
|
|
|
459
621
|
function updateHistory(destination) {
|
|
622
|
+
historyRouting = currentRouting;
|
|
460
623
|
if (!pop && !refresh) {
|
|
461
624
|
if (!replace) currentKey = nextKey++;
|
|
462
625
|
const position = scroll ? [0, 0] : [window.scrollX, window.scrollY];
|
|
@@ -470,9 +633,12 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
470
633
|
currentUrl = destination;
|
|
471
634
|
}
|
|
472
635
|
|
|
473
|
-
|
|
636
|
+
const committedUrl = new URL(committedView.url);
|
|
637
|
+
if (!refresh && target.pathname === currentUrl.pathname && target.search === currentUrl.search && target.hash !== currentUrl.hash &&
|
|
638
|
+
target.pathname === committedUrl.pathname && target.search === committedUrl.search) {
|
|
474
639
|
updateHistory(target);
|
|
475
640
|
if (scroll) scrollToTarget(target, savedScroll);
|
|
641
|
+
if (pendingNavigation === intent) pendingNavigation = undefined;
|
|
476
642
|
return;
|
|
477
643
|
}
|
|
478
644
|
|
|
@@ -493,20 +659,20 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
493
659
|
try { result = await prefetched.promise; }
|
|
494
660
|
catch (error) { if (token !== navigation || (prefetched.abort.signal.aborted && !prefetched.failed)) throw error; result = null; }
|
|
495
661
|
if (result && !result.partial) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
} else {
|
|
502
|
-
result = { ...result, url: new URL(result.url.href) };
|
|
503
|
-
if (!result.url.hash) result.url.hash = target.hash;
|
|
504
|
-
}
|
|
662
|
+
// Full prefetch warms modules/data, not a future authorization decision.
|
|
663
|
+
// Static Flight has no authorizationKey, but its proxy checks can still
|
|
664
|
+
// change when an HttpOnly cookie is revoked before this click.
|
|
665
|
+
const abort = new AbortController(); activeRequest = abort;
|
|
666
|
+
result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : currentRouting);
|
|
505
667
|
} else {
|
|
506
668
|
const preview = result;
|
|
507
669
|
const abort = new AbortController();
|
|
508
670
|
activeRequest = abort;
|
|
509
671
|
result = await navigateFlight(target, abort.signal, currentRouting, async (response, destination) => {
|
|
672
|
+
// A cached shell (including any cached authorization key) cannot
|
|
673
|
+
// establish this request's live session identity. Strict account
|
|
674
|
+
// isolation waits for the freshly authorized Flight model instead.
|
|
675
|
+
if (controller._navigationCache?.authCookies !== undefined) return;
|
|
510
676
|
if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
|
|
511
677
|
(preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
|
|
512
678
|
// Auth, middleware and rewrites ran again. A changed target or a
|
|
@@ -517,7 +683,7 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
517
683
|
if (token !== navigation || abort.signal.aborted) return;
|
|
518
684
|
updateHistory(destination); historyUpdated = true;
|
|
519
685
|
appStyleManager(document).handoff(preview.model.css || []);
|
|
520
|
-
|
|
686
|
+
renderView(preview.model, destination.href, { token, canonical: false });
|
|
521
687
|
});
|
|
522
688
|
}
|
|
523
689
|
} else {
|
|
@@ -531,11 +697,17 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
531
697
|
if (token !== navigation) return;
|
|
532
698
|
if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
|
|
533
699
|
if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
|
|
700
|
+
if (requiresAuthorizedDocument(result.model)) {
|
|
701
|
+
activeRequest?.abort();
|
|
702
|
+
fallbackDocument(result.url.href, replace || pop || refresh);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
534
705
|
prepareLayoutCache(result.model, currentModel);
|
|
535
706
|
await appStyleManager(document).prepare(result.model.globalError ? [] : result.model.css || [], activeRequest?.signal);
|
|
536
707
|
if (token !== navigation) return;
|
|
537
708
|
currentRouting = result.model.routing;
|
|
538
709
|
currentModel = result.model;
|
|
710
|
+
historyRouting = currentRouting;
|
|
539
711
|
if (currentRouting) invalidatePrefetches();
|
|
540
712
|
if (!historyUpdated) updateHistory(result.url);
|
|
541
713
|
const committed = () => {
|
|
@@ -546,8 +718,12 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
546
718
|
}
|
|
547
719
|
};
|
|
548
720
|
appStyleManager(document).handoff(result.model.globalError ? [] : result.model.css || []);
|
|
549
|
-
if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>
|
|
550
|
-
|
|
721
|
+
if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>{
|
|
722
|
+
const finish=()=>{if(finishCommit===finish)finishCommit=undefined;resolve();};
|
|
723
|
+
finishCommit=finish;
|
|
724
|
+
renderView(result.model, result.url.href, { token, committed: () => { committed(); finish(); } });
|
|
725
|
+
});
|
|
726
|
+
else renderView(result.model, result.url.href, { token, committed });
|
|
551
727
|
} catch (error) {
|
|
552
728
|
if (token !== navigation || error?.name === 'AbortError') return;
|
|
553
729
|
if(error?.code==='PRNEXT_NAVIGATION_NETWORK'){
|
|
@@ -556,18 +732,29 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
556
732
|
}
|
|
557
733
|
// Pages Router routes, public files, and unavailable Flight endpoints use
|
|
558
734
|
// a normal document navigation, including the browser's redirect handling.
|
|
559
|
-
|
|
560
|
-
else window.location.assign(target.href);
|
|
735
|
+
fallbackDocument(target.href, replace || pop || refresh);
|
|
561
736
|
} finally {
|
|
737
|
+
if (pendingNavigation === intent) pendingNavigation = undefined;
|
|
562
738
|
if (finishNavigation === releaseNavigation) finishNavigation = undefined;
|
|
563
739
|
releaseNavigation();
|
|
564
740
|
}
|
|
565
741
|
}
|
|
566
742
|
|
|
567
743
|
function purgeRetainedPages() {
|
|
744
|
+
if (pageHidden || documentFallback) return false;
|
|
745
|
+
saveScroll();
|
|
746
|
+
if (!abandonPendingView()) {
|
|
747
|
+
activeRequest?.abort();
|
|
748
|
+
fallbackDocument(currentUrl.href);
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
568
751
|
controller._retentionEpoch++;
|
|
569
752
|
historyFocus.clear();
|
|
570
|
-
|
|
753
|
+
if (committedView.model === currentModel && committedView.authorizationEpoch === controller._authorizationEpoch) committedView = { ...committedView, retentionEpoch: controller._retentionEpoch };
|
|
754
|
+
// This only updates retention for the already committed model. It must
|
|
755
|
+
// not become a new pending Flight view or a development commit barrier.
|
|
756
|
+
startTransition(() => root.render(appView({ model: currentModel, controller, url: committedView.url, globalErrorComponent, globalErrorCss })));
|
|
757
|
+
return true;
|
|
571
758
|
}
|
|
572
759
|
|
|
573
760
|
const controller = {
|
|
@@ -579,13 +766,13 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
579
766
|
replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
|
|
580
767
|
refresh: () => {
|
|
581
768
|
invalidatePrefetches();
|
|
582
|
-
purgeRetainedPages();
|
|
769
|
+
if (!purgeRetainedPages()) return;
|
|
583
770
|
void navigate(new URL(window.location.href), { refresh: true });
|
|
584
771
|
},
|
|
585
772
|
back: () => { saveScroll(); window.history.back(); },
|
|
586
773
|
forward: () => { saveScroll(); window.history.forward(); },
|
|
587
774
|
prefetch(href, options = {}) {
|
|
588
|
-
if (pageHidden) return;
|
|
775
|
+
if (pageHidden || documentFallback) return;
|
|
589
776
|
const target = navigationUrl(href);
|
|
590
777
|
if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) return;
|
|
591
778
|
target.hash = '';
|
|
@@ -650,8 +837,22 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
650
837
|
window.addEventListener('popstate', event => {
|
|
651
838
|
void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
|
|
652
839
|
});
|
|
653
|
-
window.addEventListener('pagehide', () => {
|
|
654
|
-
|
|
840
|
+
window.addEventListener('pagehide', () => {
|
|
841
|
+
saveScroll(); pageHidden = true; navigation++; finishCommit?.(); abandonPendingView();
|
|
842
|
+
clearTimeout(documentTimer); documentTimer = undefined;
|
|
843
|
+
activeRequest?.abort(); appStyleManager(document).cancel(); finishNavigation?.(); finishNavigation = undefined; invalidatePrefetches();
|
|
844
|
+
// A cached document may contain an account's shared layout state. Keep it
|
|
845
|
+
// hidden if restored from BFCache until the next document authorizes it.
|
|
846
|
+
if (controller._navigationCache?.authCookies !== undefined) document.documentElement.style.visibility = 'hidden';
|
|
847
|
+
});
|
|
848
|
+
window.addEventListener('pageshow', event => {
|
|
849
|
+
pageHidden = false;
|
|
850
|
+
if (!event.persisted) return;
|
|
851
|
+
if (controller._navigationCache?.authCookies !== undefined) {
|
|
852
|
+
documentFallback = false;
|
|
853
|
+
fallbackDocument(window.location.href);
|
|
854
|
+
} else controller.refresh();
|
|
855
|
+
});
|
|
655
856
|
window.addEventListener('prnext:invalidate-navigation', () => { controller._authorizationEpoch++; controller.refresh(); });
|
|
656
857
|
let scrollFrame;
|
|
657
858
|
window.addEventListener('scroll', () => {
|
|
@@ -672,7 +873,7 @@ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, base
|
|
|
672
873
|
devStyles: routes => developmentStyles(currentModel, routes).css || [],
|
|
673
874
|
devPrepareStyles: routes => appStyleManager(document).prepare(developmentStyles(currentModel, routes).css || []),
|
|
674
875
|
devStylesApplied: (routes, { commit = true } = {}) => {
|
|
675
|
-
for (const model of [currentModel]) {
|
|
876
|
+
for (const model of new Set([currentModel, committedView.model, pendingView?.model].filter(Boolean))) {
|
|
676
877
|
const next = developmentStyles(model, routes);
|
|
677
878
|
model.css = next.css; model.cssScopes = next.cssScopes;
|
|
678
879
|
}
|
package/runtime/app-partial.mjs
CHANGED
|
@@ -8,9 +8,10 @@ import { appContent } from './app-content.mjs';
|
|
|
8
8
|
import { runRequestContext, currentRequest } from '../compat/headers.cjs';
|
|
9
9
|
import { cachedValue, cacheGeneration, flushCacheWork } from '../compat/data-cache.cjs';
|
|
10
10
|
import { partialModel, preparePartialModel } from './app-partial-model.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { escapeHtml, MAX_RESPONSE_BYTES } from './http.mjs';
|
|
12
12
|
import { fontPreloads } from './font-preload.mjs';
|
|
13
13
|
import { PartialArtifactCache } from './partial-artifact-cache.mjs';
|
|
14
|
+
export {partialShell} from './native-ppr.mjs';
|
|
14
15
|
|
|
15
16
|
const artifacts = new Map();
|
|
16
17
|
const parsedArtifacts = new PartialArtifactCache();
|
|
@@ -32,7 +33,6 @@ async function readArtifact(file) {
|
|
|
32
33
|
}
|
|
33
34
|
return value;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
36
|
async function bytes(stream) {
|
|
37
37
|
const chunks = [];
|
|
38
38
|
let length = 0;
|
|
@@ -120,11 +120,3 @@ export async function partialArtifact({ request, route, manifest, filename, iden
|
|
|
120
120
|
} finally { await flushCacheWork(context); }
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
export function partialShell(artifact, route) {
|
|
125
|
-
const bootstrap = '<script>self.__PRNEXT_FLIGHT_STREAM__=self.__PRNEXT_FLIGHT_STREAM__||[];</script>' +
|
|
126
|
-
(route.client ? `<script type="module" async src="${escapeHtml(route.client)}"></script>` : '');
|
|
127
|
-
const end = artifact.shell.lastIndexOf('</body>');
|
|
128
|
-
if (end < 0 || !/<\/body><\/html>$/i.test(artifact.shell)) throw new Error('Invalid partial prerender document');
|
|
129
|
-
return artifact.shell.slice(0, end) + bootstrap + artifact.shell.slice(end);
|
|
130
|
-
}
|
package/runtime/app-prefetch.mjs
CHANGED
|
@@ -1,6 +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
3
|
import { PARTIAL_PREFETCH_BYTES } from './app-prefetch-constants.mjs';
|
|
4
|
+
import { decodeBase64 } from '../compat/base64.cjs';
|
|
4
5
|
export { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_BYTES, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
|
|
5
6
|
|
|
6
7
|
export async function readPartialPrefetch(response) {
|
|
@@ -27,7 +28,6 @@ export async function readPartialPrefetch(response) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function partialFlightStream(base64) {
|
|
30
|
-
const
|
|
31
|
-
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
31
|
+
const bytes = decodeBase64(base64);
|
|
32
32
|
return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
|
|
33
33
|
}
|