@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bf1b128c
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 +126 -38
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +2018 -732
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +7 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +28 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +119 -45
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +192 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +55 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +13 -1
- package/src/__internal.ts +1 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +88 -9
- package/src/browser/navigation-client.ts +167 -59
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +109 -15
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/rsc-router.tsx +168 -65
- package/src/browser/scroll-restoration.ts +36 -17
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +49 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +455 -15
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +84 -230
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- package/src/route-definition/helpers-types.ts +73 -20
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +101 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +127 -192
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +104 -10
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +8 -30
- package/src/router/middleware.ts +58 -13
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +15 -1
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +198 -20
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +452 -307
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +10 -4
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +60 -8
- package/src/rsc/handler.ts +478 -374
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +16 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +17 -1
- package/src/rsc/server-action.ts +10 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +166 -17
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +194 -60
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +145 -68
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -0
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +36 -4
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +175 -74
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +13 -6
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +51 -79
- package/src/vite/plugins/cjs-to-esm.ts +5 -0
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +16 -4
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +53 -31
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +563 -316
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +56 -43
- package/src/vite/plugins/version-injector.ts +37 -11
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +732 -94
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -10,7 +10,11 @@ import type { ReactNode } from "react";
|
|
|
10
10
|
import { invariant } from "../../errors";
|
|
11
11
|
import { revalidate } from "../loader-resolution.js";
|
|
12
12
|
import { evaluateRevalidation } from "../revalidation.js";
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
getParallelEntries,
|
|
15
|
+
getParallelSlotEntries,
|
|
16
|
+
type EntryData,
|
|
17
|
+
} from "../../server/context";
|
|
14
18
|
import type {
|
|
15
19
|
HandlerContext,
|
|
16
20
|
InternalHandlerContext,
|
|
@@ -37,7 +41,11 @@ import {
|
|
|
37
41
|
} from "./helpers.js";
|
|
38
42
|
import { getRouterContext } from "../router-context.js";
|
|
39
43
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
40
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
track,
|
|
46
|
+
RSCRouterContext,
|
|
47
|
+
runInsideLoaderScope,
|
|
48
|
+
} from "../../server/context.js";
|
|
41
49
|
|
|
42
50
|
// ---------------------------------------------------------------------------
|
|
43
51
|
// Telemetry helpers
|
|
@@ -81,6 +89,27 @@ function observeStreamedHandler(
|
|
|
81
89
|
});
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Trace a parallel slot that's being force-rendered on a full refetch (client
|
|
94
|
+
* has no cached state). User revalidate fns are bypassed in this case — see
|
|
95
|
+
* the call sites for the load-bearing rationale.
|
|
96
|
+
*/
|
|
97
|
+
function traceFullRefetchedParallelSlot(
|
|
98
|
+
parallelId: string,
|
|
99
|
+
belongsToRoute: boolean,
|
|
100
|
+
): void {
|
|
101
|
+
if (!isTraceActive()) return;
|
|
102
|
+
pushRevalidationTraceEntry({
|
|
103
|
+
segmentId: parallelId,
|
|
104
|
+
segmentType: "parallel",
|
|
105
|
+
belongsToRoute,
|
|
106
|
+
source: "parallel",
|
|
107
|
+
defaultShouldRevalidate: true,
|
|
108
|
+
finalShouldRevalidate: true,
|
|
109
|
+
reason: "full-refetch",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
84
113
|
// ---------------------------------------------------------------------------
|
|
85
114
|
// Revalidation telemetry helper
|
|
86
115
|
// ---------------------------------------------------------------------------
|
|
@@ -228,7 +257,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
228
257
|
params: ctx.params,
|
|
229
258
|
loaderId: loader.$$id,
|
|
230
259
|
loaderData: deps.wrapLoaderPromise(
|
|
231
|
-
|
|
260
|
+
runInsideLoaderScope(() =>
|
|
261
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
262
|
+
),
|
|
232
263
|
entry,
|
|
233
264
|
segmentId,
|
|
234
265
|
ctx.pathname,
|
|
@@ -258,26 +289,95 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
258
289
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
259
290
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
260
291
|
const allMatchedIds: string[] = [];
|
|
292
|
+
const seenIds = new Set<string>();
|
|
293
|
+
|
|
294
|
+
async function collectEntryLoaders(
|
|
295
|
+
entry: EntryData,
|
|
296
|
+
belongsToRoute: boolean,
|
|
297
|
+
shortCodeOverride?: string,
|
|
298
|
+
): Promise<void> {
|
|
299
|
+
// Skip if all loaders from this entry have already been resolved
|
|
300
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
301
|
+
const loaderEntries = entry.loader ?? [];
|
|
302
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
303
|
+
const allAlreadySeen =
|
|
304
|
+
loaderEntries.length > 0 &&
|
|
305
|
+
loaderEntries.every((le, i) =>
|
|
306
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
307
|
+
);
|
|
308
|
+
if (!allAlreadySeen) {
|
|
309
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
310
|
+
entry,
|
|
311
|
+
context,
|
|
312
|
+
belongsToRoute,
|
|
313
|
+
clientSegmentIds,
|
|
314
|
+
prevParams,
|
|
315
|
+
request,
|
|
316
|
+
prevUrl,
|
|
317
|
+
nextUrl,
|
|
318
|
+
routeKey,
|
|
319
|
+
deps,
|
|
320
|
+
actionContext,
|
|
321
|
+
shortCodeOverride,
|
|
322
|
+
stale,
|
|
323
|
+
);
|
|
324
|
+
for (const seg of segments) {
|
|
325
|
+
if (!seenIds.has(seg.id)) {
|
|
326
|
+
seenIds.add(seg.id);
|
|
327
|
+
allLoaderSegments.push(seg);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
allMatchedIds.push(...matchedIds);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const seenParallelEntryIds = new Set<string>();
|
|
334
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
335
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
336
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
337
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
341
|
+
for (const layoutEntry of entry.layout) {
|
|
342
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
343
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
344
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
345
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
346
|
+
if (
|
|
347
|
+
entry.type === "route" &&
|
|
348
|
+
entry.loader &&
|
|
349
|
+
entry.loader.length > 0 &&
|
|
350
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
351
|
+
) {
|
|
352
|
+
const inherited = await resolveLoadersWithRevalidation(
|
|
353
|
+
entry,
|
|
354
|
+
context,
|
|
355
|
+
childBelongsToRoute,
|
|
356
|
+
clientSegmentIds,
|
|
357
|
+
prevParams,
|
|
358
|
+
request,
|
|
359
|
+
prevUrl,
|
|
360
|
+
nextUrl,
|
|
361
|
+
routeKey,
|
|
362
|
+
deps,
|
|
363
|
+
actionContext,
|
|
364
|
+
layoutEntry.shortCode,
|
|
365
|
+
stale,
|
|
366
|
+
);
|
|
367
|
+
for (const seg of inherited.segments) {
|
|
368
|
+
if (!seenIds.has(seg.id)) {
|
|
369
|
+
seenIds.add(seg.id);
|
|
370
|
+
seg._inherited = true;
|
|
371
|
+
allLoaderSegments.push(seg);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
261
378
|
|
|
262
379
|
for (const entry of entries) {
|
|
263
|
-
|
|
264
|
-
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
265
|
-
entry,
|
|
266
|
-
context,
|
|
267
|
-
belongsToRoute,
|
|
268
|
-
clientSegmentIds,
|
|
269
|
-
prevParams,
|
|
270
|
-
request,
|
|
271
|
-
prevUrl,
|
|
272
|
-
nextUrl,
|
|
273
|
-
routeKey,
|
|
274
|
-
deps,
|
|
275
|
-
actionContext,
|
|
276
|
-
undefined, // shortCodeOverride
|
|
277
|
-
stale,
|
|
278
|
-
);
|
|
279
|
-
allLoaderSegments.push(...segments);
|
|
280
|
-
allMatchedIds.push(...matchedIds);
|
|
380
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
281
381
|
}
|
|
282
382
|
|
|
283
383
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -301,22 +401,20 @@ export function buildEntryRevalidateMap(
|
|
|
301
401
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
302
402
|
|
|
303
403
|
if (entry.type !== "parallel") {
|
|
304
|
-
for (const parallelEntry of
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}
|
|
404
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
405
|
+
entry.parallel,
|
|
406
|
+
)) {
|
|
407
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
408
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
409
|
+
map.set(parallelId, {
|
|
410
|
+
entry: parallelEntry,
|
|
411
|
+
revalidate: parallelEntry.revalidate,
|
|
412
|
+
});
|
|
315
413
|
}
|
|
316
414
|
}
|
|
317
415
|
|
|
318
416
|
for (const layoutEntry of entry.layout) {
|
|
319
|
-
processEntry(layoutEntry);
|
|
417
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
320
418
|
}
|
|
321
419
|
}
|
|
322
420
|
|
|
@@ -348,7 +446,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
348
446
|
const segments: ResolvedSegment[] = [];
|
|
349
447
|
const matchedIds: string[] = [];
|
|
350
448
|
|
|
351
|
-
|
|
449
|
+
const resolvedParallelEntries = new Set<string>();
|
|
450
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
451
|
+
entry.parallel,
|
|
452
|
+
)) {
|
|
352
453
|
invariant(
|
|
353
454
|
parallelEntry.type === "parallel",
|
|
354
455
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -359,141 +460,47 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
359
460
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
360
461
|
| ReactNode
|
|
361
462
|
>;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
return true;
|
|
396
|
-
}
|
|
397
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
398
|
-
const result = belongsToRoute || isNewParent;
|
|
399
|
-
if (isTraceActive()) {
|
|
400
|
-
pushRevalidationTraceEntry({
|
|
401
|
-
segmentId: parallelId,
|
|
402
|
-
segmentType: "parallel",
|
|
403
|
-
belongsToRoute,
|
|
404
|
-
source: "parallel",
|
|
405
|
-
defaultShouldRevalidate: result,
|
|
406
|
-
finalShouldRevalidate: result,
|
|
407
|
-
reason: result ? "new-segment" : "skip-parent-chain",
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
return result;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const dummySegment: ResolvedSegment = {
|
|
414
|
-
id: parallelId,
|
|
415
|
-
namespace: parallelEntry.id,
|
|
416
|
-
type: "parallel",
|
|
417
|
-
index: 0,
|
|
418
|
-
component: null as any,
|
|
419
|
-
params,
|
|
420
|
-
slot,
|
|
421
|
-
belongsToRoute,
|
|
422
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
423
|
-
...(parallelEntry.mountPath
|
|
424
|
-
? { mountPath: parallelEntry.mountPath }
|
|
425
|
-
: {}),
|
|
463
|
+
// In production, static handler bodies are evicted and the slot value
|
|
464
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
465
|
+
// We defer the handler check until after tryStaticSlot.
|
|
466
|
+
const handler = slots[slot];
|
|
467
|
+
|
|
468
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
469
|
+
|
|
470
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
471
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
472
|
+
// Always announce the slot in matchedIds — it's unconditionally appended
|
|
473
|
+
// to `segments` below, and a segment present in segments but missing from
|
|
474
|
+
// matched lets the client prune it (then it's missing from clientSegmentIds
|
|
475
|
+
// on the next request, perpetuating the staleness).
|
|
476
|
+
matchedIds.push(parallelId);
|
|
477
|
+
|
|
478
|
+
let shouldResolve: boolean;
|
|
479
|
+
if (isFullRefetch) {
|
|
480
|
+
// Client has nothing cached — slot MUST render. User revalidate fns are
|
|
481
|
+
// bypassed here because returning false would leave the segment blank
|
|
482
|
+
// with no client-side fallback.
|
|
483
|
+
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
484
|
+
shouldResolve = true;
|
|
485
|
+
} else {
|
|
486
|
+
// For non-empty client sets, consult user revalidate fns. When the slot
|
|
487
|
+
// is unknown to the client, override the type-derived default so the
|
|
488
|
+
// soft chain seeds with the right "new segment" / "parent-chain" value.
|
|
489
|
+
let defaultOverride: { value: boolean; reason: string } | undefined;
|
|
490
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
491
|
+
const value = belongsToRoute || isNewParent;
|
|
492
|
+
defaultOverride = {
|
|
493
|
+
value,
|
|
494
|
+
reason: value ? "new-segment" : "skip-parent-chain",
|
|
426
495
|
};
|
|
427
|
-
|
|
428
|
-
return await evaluateRevalidation({
|
|
429
|
-
segment: dummySegment,
|
|
430
|
-
prevParams,
|
|
431
|
-
getPrevSegment: null,
|
|
432
|
-
request,
|
|
433
|
-
prevUrl,
|
|
434
|
-
nextUrl,
|
|
435
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
436
|
-
name: `revalidate${i}`,
|
|
437
|
-
fn,
|
|
438
|
-
})),
|
|
439
|
-
routeKey,
|
|
440
|
-
context,
|
|
441
|
-
actionContext,
|
|
442
|
-
stale,
|
|
443
|
-
traceSource: "parallel",
|
|
444
|
-
});
|
|
445
|
-
})();
|
|
446
|
-
emitRevalidationDecision(
|
|
447
|
-
parallelId,
|
|
448
|
-
context.pathname,
|
|
449
|
-
routeKey,
|
|
450
|
-
shouldResolve,
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
let component: ReactNode | undefined;
|
|
454
|
-
if (shouldResolve) {
|
|
455
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
456
|
-
}
|
|
457
|
-
if (component === undefined) {
|
|
458
|
-
const hasLoadingFallback =
|
|
459
|
-
parallelEntry.loading !== undefined &&
|
|
460
|
-
parallelEntry.loading !== false;
|
|
461
|
-
if (!shouldResolve) {
|
|
462
|
-
component = null;
|
|
463
|
-
} else if (hasLoadingFallback) {
|
|
464
|
-
const result =
|
|
465
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
466
|
-
if (result instanceof Promise) {
|
|
467
|
-
const tracked = deps.trackHandler(result, {
|
|
468
|
-
segmentId: parallelId,
|
|
469
|
-
segmentType: "parallel",
|
|
470
|
-
});
|
|
471
|
-
observeStreamedHandler(
|
|
472
|
-
tracked,
|
|
473
|
-
parallelId,
|
|
474
|
-
"parallel",
|
|
475
|
-
context.pathname,
|
|
476
|
-
routeKey,
|
|
477
|
-
params,
|
|
478
|
-
);
|
|
479
|
-
component = tracked as ReactNode;
|
|
480
|
-
} else {
|
|
481
|
-
component = result as ReactNode;
|
|
482
|
-
}
|
|
483
|
-
} else {
|
|
484
|
-
component =
|
|
485
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
486
|
-
}
|
|
487
496
|
}
|
|
488
497
|
|
|
489
|
-
|
|
498
|
+
const dummySegment: ResolvedSegment = {
|
|
490
499
|
id: parallelId,
|
|
491
500
|
namespace: parallelEntry.id,
|
|
492
501
|
type: "parallel",
|
|
493
502
|
index: 0,
|
|
494
|
-
component,
|
|
495
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
496
|
-
transition: parallelEntry.transition,
|
|
503
|
+
component: null as any,
|
|
497
504
|
params,
|
|
498
505
|
slot,
|
|
499
506
|
belongsToRoute,
|
|
@@ -501,28 +508,112 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
501
508
|
...(parallelEntry.mountPath
|
|
502
509
|
? { mountPath: parallelEntry.mountPath }
|
|
503
510
|
: {}),
|
|
504
|
-
}
|
|
505
|
-
}
|
|
511
|
+
};
|
|
506
512
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
parallelEntry,
|
|
510
|
-
context,
|
|
511
|
-
belongsToRoute,
|
|
512
|
-
clientSegmentIds,
|
|
513
|
+
shouldResolve = await evaluateRevalidation({
|
|
514
|
+
segment: dummySegment,
|
|
513
515
|
prevParams,
|
|
516
|
+
getPrevSegment: null,
|
|
514
517
|
request,
|
|
515
518
|
prevUrl,
|
|
516
519
|
nextUrl,
|
|
520
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
521
|
+
name: `revalidate${i}`,
|
|
522
|
+
fn,
|
|
523
|
+
})),
|
|
517
524
|
routeKey,
|
|
518
|
-
|
|
525
|
+
context,
|
|
519
526
|
actionContext,
|
|
520
|
-
entry.shortCode,
|
|
521
527
|
stale,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
528
|
+
traceSource: "parallel",
|
|
529
|
+
defaultOverride,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
emitRevalidationDecision(
|
|
533
|
+
parallelId,
|
|
534
|
+
context.pathname,
|
|
535
|
+
routeKey,
|
|
536
|
+
shouldResolve,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
let component: ReactNode | undefined;
|
|
540
|
+
if (shouldResolve) {
|
|
541
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
542
|
+
}
|
|
543
|
+
if (component === undefined) {
|
|
544
|
+
const hasLoadingFallback =
|
|
545
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
546
|
+
if (!shouldResolve) {
|
|
547
|
+
component = null;
|
|
548
|
+
} else if (handler === undefined) {
|
|
549
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
550
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
551
|
+
component = null;
|
|
552
|
+
} else if (hasLoadingFallback) {
|
|
553
|
+
const result =
|
|
554
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
555
|
+
if (result instanceof Promise) {
|
|
556
|
+
const tracked = deps.trackHandler(result, {
|
|
557
|
+
segmentId: parallelId,
|
|
558
|
+
segmentType: "parallel",
|
|
559
|
+
});
|
|
560
|
+
observeStreamedHandler(
|
|
561
|
+
tracked,
|
|
562
|
+
parallelId,
|
|
563
|
+
"parallel",
|
|
564
|
+
context.pathname,
|
|
565
|
+
routeKey,
|
|
566
|
+
params,
|
|
567
|
+
);
|
|
568
|
+
component = tracked as ReactNode;
|
|
569
|
+
} else {
|
|
570
|
+
component = result as ReactNode;
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
component =
|
|
574
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
575
|
+
}
|
|
525
576
|
}
|
|
577
|
+
|
|
578
|
+
segments.push({
|
|
579
|
+
id: parallelId,
|
|
580
|
+
namespace: parallelEntry.id,
|
|
581
|
+
type: "parallel",
|
|
582
|
+
index: 0,
|
|
583
|
+
component,
|
|
584
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
585
|
+
transition: parallelEntry.transition,
|
|
586
|
+
params,
|
|
587
|
+
slot,
|
|
588
|
+
belongsToRoute,
|
|
589
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
590
|
+
...(parallelEntry.mountPath
|
|
591
|
+
? { mountPath: parallelEntry.mountPath }
|
|
592
|
+
: {}),
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
600
|
+
parallelEntry,
|
|
601
|
+
context,
|
|
602
|
+
belongsToRoute,
|
|
603
|
+
clientSegmentIds,
|
|
604
|
+
prevParams,
|
|
605
|
+
request,
|
|
606
|
+
prevUrl,
|
|
607
|
+
nextUrl,
|
|
608
|
+
routeKey,
|
|
609
|
+
deps,
|
|
610
|
+
actionContext,
|
|
611
|
+
entry.shortCode,
|
|
612
|
+
stale,
|
|
613
|
+
);
|
|
614
|
+
segments.push(...loaderResult.segments);
|
|
615
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
616
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
526
617
|
}
|
|
527
618
|
|
|
528
619
|
return { segments, matchedIds };
|
|
@@ -608,6 +699,8 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
608
699
|
context,
|
|
609
700
|
actionContext,
|
|
610
701
|
stale,
|
|
702
|
+
traceSource:
|
|
703
|
+
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
611
704
|
});
|
|
612
705
|
emitRevalidationDecision(
|
|
613
706
|
entry.shortCode,
|
|
@@ -636,13 +729,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
636
729
|
return staticComponent;
|
|
637
730
|
}
|
|
638
731
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
732
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
733
|
+
// the build handler. At build time (context.build === true), always
|
|
734
|
+
// use the build handler from routeEntry.handler.
|
|
735
|
+
const handler =
|
|
736
|
+
!context.build && routeEntry.liveHandler
|
|
737
|
+
? routeEntry.liveHandler
|
|
738
|
+
: routeEntry.handler;
|
|
639
739
|
if (!routeEntry.loading) {
|
|
640
|
-
const result = handleHandlerResult(await
|
|
740
|
+
const result = handleHandlerResult(await handler(context));
|
|
641
741
|
doneHandler();
|
|
642
742
|
return result;
|
|
643
743
|
}
|
|
644
744
|
if (!actionContext) {
|
|
645
|
-
const result = handleHandlerResult(
|
|
745
|
+
const result = handleHandlerResult(handler(context));
|
|
646
746
|
if (result instanceof Promise) {
|
|
647
747
|
result.finally(doneHandler).catch(() => {});
|
|
648
748
|
const tracked = deps.trackHandler(result, {
|
|
@@ -665,9 +765,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
665
765
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
666
766
|
entryId: entry.id,
|
|
667
767
|
});
|
|
668
|
-
const actionResult = handleHandlerResult(
|
|
669
|
-
await routeEntry.handler(context),
|
|
670
|
-
);
|
|
768
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
671
769
|
doneHandler();
|
|
672
770
|
return {
|
|
673
771
|
content: Promise.resolve(actionResult),
|
|
@@ -676,10 +774,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
676
774
|
() => null,
|
|
677
775
|
);
|
|
678
776
|
|
|
777
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
778
|
+
// component === null checks work consistently for both void and explicit null.
|
|
679
779
|
const resolvedComponent =
|
|
680
780
|
component && typeof component === "object" && "content" in component
|
|
681
|
-
? (component as { content: ReactNode }).content
|
|
682
|
-
: component;
|
|
781
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
782
|
+
: (component ?? null);
|
|
683
783
|
|
|
684
784
|
const segment: ResolvedSegment = {
|
|
685
785
|
id: entry.shortCode,
|
|
@@ -776,11 +876,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
776
876
|
prevUrl,
|
|
777
877
|
nextUrl,
|
|
778
878
|
routeKey,
|
|
779
|
-
loaderPromises,
|
|
780
879
|
true,
|
|
781
880
|
deps,
|
|
782
881
|
actionContext,
|
|
783
882
|
stale,
|
|
883
|
+
entry,
|
|
784
884
|
);
|
|
785
885
|
segments.push(...orphanResult.segments);
|
|
786
886
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -860,7 +960,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
860
960
|
prevUrl,
|
|
861
961
|
nextUrl,
|
|
862
962
|
routeKey,
|
|
863
|
-
loaderPromises,
|
|
864
963
|
false,
|
|
865
964
|
deps,
|
|
866
965
|
actionContext,
|
|
@@ -887,11 +986,12 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
887
986
|
prevUrl: URL,
|
|
888
987
|
nextUrl: URL,
|
|
889
988
|
routeKey: string,
|
|
890
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
891
989
|
belongsToRoute: boolean,
|
|
892
990
|
deps: SegmentResolutionDeps<TEnv>,
|
|
893
991
|
actionContext?: ActionContext,
|
|
894
992
|
stale?: boolean,
|
|
993
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
994
|
+
parentRouteEntry?: EntryData,
|
|
895
995
|
): Promise<SegmentRevalidationResult> {
|
|
896
996
|
invariant(
|
|
897
997
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -919,6 +1019,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
919
1019
|
segments.push(...loaderResult.segments);
|
|
920
1020
|
matchedIds.push(...loaderResult.matchedIds);
|
|
921
1021
|
|
|
1022
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1023
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1024
|
+
if (
|
|
1025
|
+
parentRouteEntry &&
|
|
1026
|
+
parentRouteEntry.loader &&
|
|
1027
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1028
|
+
Object.keys(orphan.parallel).length > 0
|
|
1029
|
+
) {
|
|
1030
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1031
|
+
parentRouteEntry,
|
|
1032
|
+
context,
|
|
1033
|
+
belongsToRoute,
|
|
1034
|
+
clientSegmentIds,
|
|
1035
|
+
prevParams,
|
|
1036
|
+
request,
|
|
1037
|
+
prevUrl,
|
|
1038
|
+
nextUrl,
|
|
1039
|
+
routeKey,
|
|
1040
|
+
deps,
|
|
1041
|
+
actionContext,
|
|
1042
|
+
orphan.shortCode,
|
|
1043
|
+
stale,
|
|
1044
|
+
);
|
|
1045
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1046
|
+
for (const s of inheritedResult.segments) {
|
|
1047
|
+
s._inherited = true;
|
|
1048
|
+
}
|
|
1049
|
+
segments.push(...inheritedResult.segments);
|
|
1050
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
922
1053
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
923
1054
|
// so ctx.set() values are visible to parallel children.
|
|
924
1055
|
matchedIds.push(orphan.shortCode);
|
|
@@ -995,143 +1126,72 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
995
1126
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
996
1127
|
});
|
|
997
1128
|
|
|
998
|
-
|
|
1129
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1130
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1131
|
+
orphan.parallel,
|
|
1132
|
+
)) {
|
|
999
1133
|
invariant(
|
|
1000
1134
|
parallelEntry.type === "parallel",
|
|
1001
1135
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
1002
1136
|
);
|
|
1003
1137
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1138
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1139
|
+
// shortCodeOverride must match the parent layout, not the parallel entry.
|
|
1140
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1141
|
+
parallelEntry,
|
|
1142
|
+
context,
|
|
1143
|
+
belongsToRoute,
|
|
1144
|
+
clientSegmentIds,
|
|
1145
|
+
prevParams,
|
|
1146
|
+
request,
|
|
1147
|
+
prevUrl,
|
|
1148
|
+
nextUrl,
|
|
1149
|
+
routeKey,
|
|
1150
|
+
deps,
|
|
1151
|
+
actionContext,
|
|
1152
|
+
orphan.shortCode,
|
|
1153
|
+
stale,
|
|
1154
|
+
);
|
|
1155
|
+
segments.push(...loaderResult.segments);
|
|
1156
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1157
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1158
|
+
}
|
|
1021
1159
|
|
|
1022
1160
|
const slots = parallelEntry.handler as Record<
|
|
1023
1161
|
`@${string}`,
|
|
1024
1162
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1025
1163
|
| ReactNode
|
|
1026
1164
|
>;
|
|
1165
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1166
|
+
const handler = slots[slot];
|
|
1167
|
+
|
|
1168
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1169
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1170
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1171
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1172
|
+
matchedIds.push(parallelId);
|
|
1173
|
+
|
|
1174
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
1175
|
+
let shouldResolve: boolean;
|
|
1176
|
+
if (isFullRefetch) {
|
|
1177
|
+
// Same load-bearing rationale as the main parallel path: full refetch
|
|
1178
|
+
// means the client has nothing to fall back to, so the slot must render.
|
|
1179
|
+
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
1180
|
+
shouldResolve = true;
|
|
1181
|
+
} else {
|
|
1182
|
+
// When slot is unknown to the client, seed the soft chain with `true`
|
|
1183
|
+
// (orphan parallels always belong to the route — we want them rendered
|
|
1184
|
+
// unless the user explicitly opts out via revalidate()).
|
|
1185
|
+
const defaultOverride = clientSegmentIds.has(parallelId)
|
|
1186
|
+
? undefined
|
|
1187
|
+
: { value: true, reason: "new-segment" };
|
|
1027
1188
|
|
|
1028
|
-
|
|
1029
|
-
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1030
|
-
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1031
|
-
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1032
|
-
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1033
|
-
matchedIds.push(parallelId);
|
|
1034
|
-
|
|
1035
|
-
const shouldResolve = await (async () => {
|
|
1036
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
1037
|
-
if (isTraceActive()) {
|
|
1038
|
-
pushRevalidationTraceEntry({
|
|
1039
|
-
segmentId: parallelId,
|
|
1040
|
-
segmentType: "parallel",
|
|
1041
|
-
belongsToRoute,
|
|
1042
|
-
source: "parallel",
|
|
1043
|
-
defaultShouldRevalidate: true,
|
|
1044
|
-
finalShouldRevalidate: true,
|
|
1045
|
-
reason: "new-segment",
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
return true;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
const dummySegment: ResolvedSegment = {
|
|
1052
|
-
id: parallelId,
|
|
1053
|
-
namespace: parallelEntry.id,
|
|
1054
|
-
type: "parallel",
|
|
1055
|
-
index: 0,
|
|
1056
|
-
component: null as any,
|
|
1057
|
-
params,
|
|
1058
|
-
slot,
|
|
1059
|
-
belongsToRoute,
|
|
1060
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1061
|
-
...(parallelEntry.mountPath
|
|
1062
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1063
|
-
: {}),
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
return await evaluateRevalidation({
|
|
1067
|
-
segment: dummySegment,
|
|
1068
|
-
prevParams,
|
|
1069
|
-
getPrevSegment: null,
|
|
1070
|
-
request,
|
|
1071
|
-
prevUrl,
|
|
1072
|
-
nextUrl,
|
|
1073
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1074
|
-
name: `revalidate${i}`,
|
|
1075
|
-
fn,
|
|
1076
|
-
})),
|
|
1077
|
-
routeKey,
|
|
1078
|
-
context,
|
|
1079
|
-
actionContext,
|
|
1080
|
-
stale,
|
|
1081
|
-
traceSource: "parallel",
|
|
1082
|
-
});
|
|
1083
|
-
})();
|
|
1084
|
-
emitRevalidationDecision(
|
|
1085
|
-
parallelId,
|
|
1086
|
-
context.pathname,
|
|
1087
|
-
routeKey,
|
|
1088
|
-
shouldResolve,
|
|
1089
|
-
);
|
|
1090
|
-
|
|
1091
|
-
let component: ReactNode | undefined;
|
|
1092
|
-
if (shouldResolve) {
|
|
1093
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1094
|
-
}
|
|
1095
|
-
if (component === undefined) {
|
|
1096
|
-
const hasLoadingFallback =
|
|
1097
|
-
parallelEntry.loading !== undefined &&
|
|
1098
|
-
parallelEntry.loading !== false;
|
|
1099
|
-
if (!shouldResolve) {
|
|
1100
|
-
component = null;
|
|
1101
|
-
} else if (hasLoadingFallback) {
|
|
1102
|
-
const result =
|
|
1103
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1104
|
-
if (result instanceof Promise) {
|
|
1105
|
-
const tracked = deps.trackHandler(result, {
|
|
1106
|
-
segmentId: parallelId,
|
|
1107
|
-
segmentType: "parallel",
|
|
1108
|
-
});
|
|
1109
|
-
observeStreamedHandler(
|
|
1110
|
-
tracked,
|
|
1111
|
-
parallelId,
|
|
1112
|
-
"parallel",
|
|
1113
|
-
context.pathname,
|
|
1114
|
-
routeKey,
|
|
1115
|
-
params,
|
|
1116
|
-
);
|
|
1117
|
-
component = tracked as ReactNode;
|
|
1118
|
-
} else {
|
|
1119
|
-
component = result as ReactNode;
|
|
1120
|
-
}
|
|
1121
|
-
} else {
|
|
1122
|
-
component =
|
|
1123
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
segments.push({
|
|
1189
|
+
const dummySegment: ResolvedSegment = {
|
|
1128
1190
|
id: parallelId,
|
|
1129
1191
|
namespace: parallelEntry.id,
|
|
1130
1192
|
type: "parallel",
|
|
1131
1193
|
index: 0,
|
|
1132
|
-
component,
|
|
1133
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1134
|
-
transition: parallelEntry.transition,
|
|
1194
|
+
component: null as any,
|
|
1135
1195
|
params,
|
|
1136
1196
|
slot,
|
|
1137
1197
|
belongsToRoute,
|
|
@@ -1139,8 +1199,88 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1139
1199
|
...(parallelEntry.mountPath
|
|
1140
1200
|
? { mountPath: parallelEntry.mountPath }
|
|
1141
1201
|
: {}),
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
shouldResolve = await evaluateRevalidation({
|
|
1205
|
+
segment: dummySegment,
|
|
1206
|
+
prevParams,
|
|
1207
|
+
getPrevSegment: null,
|
|
1208
|
+
request,
|
|
1209
|
+
prevUrl,
|
|
1210
|
+
nextUrl,
|
|
1211
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1212
|
+
name: `revalidate${i}`,
|
|
1213
|
+
fn,
|
|
1214
|
+
})),
|
|
1215
|
+
routeKey,
|
|
1216
|
+
context,
|
|
1217
|
+
actionContext,
|
|
1218
|
+
stale,
|
|
1219
|
+
traceSource: "parallel",
|
|
1220
|
+
defaultOverride,
|
|
1142
1221
|
});
|
|
1143
1222
|
}
|
|
1223
|
+
emitRevalidationDecision(
|
|
1224
|
+
parallelId,
|
|
1225
|
+
context.pathname,
|
|
1226
|
+
routeKey,
|
|
1227
|
+
shouldResolve,
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
let component: ReactNode | undefined;
|
|
1231
|
+
if (shouldResolve) {
|
|
1232
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1233
|
+
}
|
|
1234
|
+
if (component === undefined) {
|
|
1235
|
+
const hasLoadingFallback =
|
|
1236
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1237
|
+
if (!shouldResolve) {
|
|
1238
|
+
component = null;
|
|
1239
|
+
} else if (handler === undefined) {
|
|
1240
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1241
|
+
component = null;
|
|
1242
|
+
} else if (hasLoadingFallback) {
|
|
1243
|
+
const result =
|
|
1244
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1245
|
+
if (result instanceof Promise) {
|
|
1246
|
+
const tracked = deps.trackHandler(result, {
|
|
1247
|
+
segmentId: parallelId,
|
|
1248
|
+
segmentType: "parallel",
|
|
1249
|
+
});
|
|
1250
|
+
observeStreamedHandler(
|
|
1251
|
+
tracked,
|
|
1252
|
+
parallelId,
|
|
1253
|
+
"parallel",
|
|
1254
|
+
context.pathname,
|
|
1255
|
+
routeKey,
|
|
1256
|
+
params,
|
|
1257
|
+
);
|
|
1258
|
+
component = tracked as ReactNode;
|
|
1259
|
+
} else {
|
|
1260
|
+
component = result as ReactNode;
|
|
1261
|
+
}
|
|
1262
|
+
} else {
|
|
1263
|
+
component =
|
|
1264
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
segments.push({
|
|
1269
|
+
id: parallelId,
|
|
1270
|
+
namespace: parallelEntry.id,
|
|
1271
|
+
type: "parallel",
|
|
1272
|
+
index: 0,
|
|
1273
|
+
component,
|
|
1274
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1275
|
+
transition: parallelEntry.transition,
|
|
1276
|
+
params,
|
|
1277
|
+
slot,
|
|
1278
|
+
belongsToRoute,
|
|
1279
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1280
|
+
...(parallelEntry.mountPath
|
|
1281
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1282
|
+
: {}),
|
|
1283
|
+
});
|
|
1144
1284
|
}
|
|
1145
1285
|
|
|
1146
1286
|
return { segments, matchedIds };
|
|
@@ -1165,6 +1305,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1165
1305
|
localRouteName: string,
|
|
1166
1306
|
pathname: string,
|
|
1167
1307
|
deps: SegmentResolutionDeps<TEnv>,
|
|
1308
|
+
stale?: boolean,
|
|
1168
1309
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1169
1310
|
const allSegments: ResolvedSegment[] = [];
|
|
1170
1311
|
const matchedIds: string[] = [];
|
|
@@ -1191,6 +1332,10 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1191
1332
|
}
|
|
1192
1333
|
|
|
1193
1334
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1335
|
+
if (entry.type === "cache") {
|
|
1336
|
+
const store = RSCRouterContext.getStore();
|
|
1337
|
+
if (store) store.insideCacheScope = true;
|
|
1338
|
+
}
|
|
1194
1339
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1195
1340
|
const resolved = await resolveWithErrorBoundary(
|
|
1196
1341
|
nonParallelEntry,
|
|
@@ -1209,7 +1354,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1209
1354
|
loaderPromises,
|
|
1210
1355
|
deps,
|
|
1211
1356
|
actionContext,
|
|
1212
|
-
|
|
1357
|
+
stale,
|
|
1213
1358
|
),
|
|
1214
1359
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
1215
1360
|
deps,
|