@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1fa245e2
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/{CLAUDE.md → AGENTS.md} +4 -0
- package/README.md +122 -30
- package/dist/bin/rango.js +245 -63
- package/dist/vite/index.js +859 -418
- package/package.json +3 -3
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +49 -8
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +33 -31
- package/skills/host-router/SKILL.md +218 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +72 -22
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +126 -0
- package/skills/prerender/SKILL.md +112 -70
- package/skills/rango/SKILL.md +0 -1
- package/skills/route/SKILL.md +34 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/typesafety/SKILL.md +35 -23
- package/src/__internal.ts +92 -0
- package/src/bin/rango.ts +18 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +114 -18
- package/src/browser/navigation-client.ts +126 -44
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +80 -15
- package/src/browser/prefetch/cache.ts +166 -27
- package/src/browser/prefetch/fetch.ts +52 -39
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +92 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +70 -14
- package/src/browser/react/NavigationProvider.tsx +40 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +143 -59
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +6 -1
- package/src/browser/server-action-bridge.ts +454 -436
- package/src/browser/types.ts +60 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +346 -87
- 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 +453 -11
- 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.rsc.tsx +2 -1
- package/src/client.tsx +3 -102
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/host/index.ts +0 -3
- package/src/index.rsc.ts +8 -37
- package/src/index.ts +40 -66
- package/src/prerender/store.ts +57 -15
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +73 -25
- package/src/route-definition/helpers-types.ts +10 -6
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +108 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/loader-resolution.ts +123 -11
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +9 -3
- package/src/router/match-api.ts +125 -190
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +88 -16
- 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 +22 -15
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +53 -12
- package/src/router/middleware.ts +172 -85
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +20 -5
- package/src/router/prerender-match.ts +114 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +27 -7
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +50 -5
- package/src/router/router-options.ts +50 -19
- package/src/router/segment-resolution/fresh.ts +200 -19
- package/src/router/segment-resolution/helpers.ts +30 -25
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +429 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +20 -2
- package/src/router/types.ts +1 -0
- package/src/router.ts +88 -15
- package/src/rsc/handler.ts +546 -359
- package/src/rsc/index.ts +0 -20
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +25 -8
- package/src/rsc/rsc-rendering.ts +35 -43
- package/src/rsc/server-action.ts +16 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +10 -1
- package/src/search-params.ts +16 -13
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +148 -16
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +182 -34
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/theme/index.ts +4 -13
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +149 -49
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +8 -1
- package/src/types/segments.ts +2 -5
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +73 -4
- package/src/vite/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +23 -5
- package/src/vite/discovery/prerender-collection.ts +48 -15
- package/src/vite/discovery/state.ts +17 -13
- package/src/vite/index.ts +8 -3
- package/src/vite/plugin-types.ts +51 -79
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +174 -211
- package/src/vite/router-discovery.ts +169 -42
- package/src/vite/utils/banner.ts +3 -3
- package/src/vite/utils/prerender-utils.ts +78 -0
- package/src/vite/utils/shared-utils.ts +3 -2
- package/skills/testing/SKILL.md +0 -226
- package/src/route-definition/route-function.ts +0 -119
|
@@ -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,6 +41,11 @@ import {
|
|
|
37
41
|
} from "./helpers.js";
|
|
38
42
|
import { getRouterContext } from "../router-context.js";
|
|
39
43
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
44
|
+
import {
|
|
45
|
+
track,
|
|
46
|
+
RSCRouterContext,
|
|
47
|
+
runInsideLoaderScope,
|
|
48
|
+
} from "../../server/context.js";
|
|
40
49
|
|
|
41
50
|
// ---------------------------------------------------------------------------
|
|
42
51
|
// Telemetry helpers
|
|
@@ -227,7 +236,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
227
236
|
params: ctx.params,
|
|
228
237
|
loaderId: loader.$$id,
|
|
229
238
|
loaderData: deps.wrapLoaderPromise(
|
|
230
|
-
|
|
239
|
+
runInsideLoaderScope(() =>
|
|
240
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
241
|
+
),
|
|
231
242
|
entry,
|
|
232
243
|
segmentId,
|
|
233
244
|
ctx.pathname,
|
|
@@ -257,26 +268,75 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
257
268
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
258
269
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
259
270
|
const allMatchedIds: string[] = [];
|
|
271
|
+
const seenIds = new Set<string>();
|
|
272
|
+
|
|
273
|
+
async function collectEntryLoaders(
|
|
274
|
+
entry: EntryData,
|
|
275
|
+
belongsToRoute: boolean,
|
|
276
|
+
shortCodeOverride?: string,
|
|
277
|
+
): Promise<void> {
|
|
278
|
+
// Skip if all loaders from this entry have already been resolved
|
|
279
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
280
|
+
const loaderEntries = entry.loader ?? [];
|
|
281
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
282
|
+
const allAlreadySeen =
|
|
283
|
+
loaderEntries.length > 0 &&
|
|
284
|
+
loaderEntries.every((le, i) =>
|
|
285
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
286
|
+
);
|
|
287
|
+
if (!allAlreadySeen) {
|
|
288
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
289
|
+
entry,
|
|
290
|
+
context,
|
|
291
|
+
belongsToRoute,
|
|
292
|
+
clientSegmentIds,
|
|
293
|
+
prevParams,
|
|
294
|
+
request,
|
|
295
|
+
prevUrl,
|
|
296
|
+
nextUrl,
|
|
297
|
+
routeKey,
|
|
298
|
+
deps,
|
|
299
|
+
actionContext,
|
|
300
|
+
shortCodeOverride,
|
|
301
|
+
stale,
|
|
302
|
+
);
|
|
303
|
+
for (const seg of segments) {
|
|
304
|
+
if (!seenIds.has(seg.id)) {
|
|
305
|
+
seenIds.add(seg.id);
|
|
306
|
+
allLoaderSegments.push(seg);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
allMatchedIds.push(...matchedIds);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const seenParallelEntryIds = new Set<string>();
|
|
313
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
314
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
315
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
316
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
320
|
+
for (const layoutEntry of entry.layout) {
|
|
321
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
322
|
+
// Inherit route loaders for orphan layouts with parallels
|
|
323
|
+
if (
|
|
324
|
+
entry.type === "route" &&
|
|
325
|
+
entry.loader &&
|
|
326
|
+
entry.loader.length > 0 &&
|
|
327
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
328
|
+
) {
|
|
329
|
+
await collectEntryLoaders(
|
|
330
|
+
entry,
|
|
331
|
+
childBelongsToRoute,
|
|
332
|
+
layoutEntry.shortCode,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
260
337
|
|
|
261
338
|
for (const entry of entries) {
|
|
262
|
-
|
|
263
|
-
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
264
|
-
entry,
|
|
265
|
-
context,
|
|
266
|
-
belongsToRoute,
|
|
267
|
-
clientSegmentIds,
|
|
268
|
-
prevParams,
|
|
269
|
-
request,
|
|
270
|
-
prevUrl,
|
|
271
|
-
nextUrl,
|
|
272
|
-
routeKey,
|
|
273
|
-
deps,
|
|
274
|
-
actionContext,
|
|
275
|
-
undefined, // shortCodeOverride
|
|
276
|
-
stale,
|
|
277
|
-
);
|
|
278
|
-
allLoaderSegments.push(...segments);
|
|
279
|
-
allMatchedIds.push(...matchedIds);
|
|
339
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
280
340
|
}
|
|
281
341
|
|
|
282
342
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -300,22 +360,20 @@ export function buildEntryRevalidateMap(
|
|
|
300
360
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
301
361
|
|
|
302
362
|
if (entry.type !== "parallel") {
|
|
303
|
-
for (const parallelEntry of
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
}
|
|
363
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
364
|
+
entry.parallel,
|
|
365
|
+
)) {
|
|
366
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
367
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
368
|
+
map.set(parallelId, {
|
|
369
|
+
entry: parallelEntry,
|
|
370
|
+
revalidate: parallelEntry.revalidate,
|
|
371
|
+
});
|
|
314
372
|
}
|
|
315
373
|
}
|
|
316
374
|
|
|
317
375
|
for (const layoutEntry of entry.layout) {
|
|
318
|
-
processEntry(layoutEntry);
|
|
376
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
319
377
|
}
|
|
320
378
|
}
|
|
321
379
|
|
|
@@ -347,7 +405,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
347
405
|
const segments: ResolvedSegment[] = [];
|
|
348
406
|
const matchedIds: string[] = [];
|
|
349
407
|
|
|
350
|
-
|
|
408
|
+
const resolvedParallelEntries = new Set<string>();
|
|
409
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
410
|
+
entry.parallel,
|
|
411
|
+
)) {
|
|
351
412
|
invariant(
|
|
352
413
|
parallelEntry.type === "parallel",
|
|
353
414
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -358,141 +419,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
358
419
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
359
420
|
| ReactNode
|
|
360
421
|
>;
|
|
422
|
+
// In production, static handler bodies are evicted and the slot value
|
|
423
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
424
|
+
// We defer the handler check until after tryStaticSlot.
|
|
425
|
+
const handler = slots[slot];
|
|
426
|
+
|
|
427
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
428
|
+
|
|
429
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
430
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
431
|
+
if (
|
|
432
|
+
isFullRefetch ||
|
|
433
|
+
clientSegmentIds.has(parallelId) ||
|
|
434
|
+
belongsToRoute ||
|
|
435
|
+
isNewParent
|
|
436
|
+
) {
|
|
437
|
+
matchedIds.push(parallelId);
|
|
438
|
+
}
|
|
361
439
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
clientSegmentIds.has(parallelId) ||
|
|
375
|
-
belongsToRoute ||
|
|
376
|
-
isNewParent
|
|
377
|
-
) {
|
|
378
|
-
matchedIds.push(parallelId);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const shouldResolve = await (async () => {
|
|
382
|
-
if (isFullRefetch) {
|
|
383
|
-
if (isTraceActive()) {
|
|
384
|
-
pushRevalidationTraceEntry({
|
|
385
|
-
segmentId: parallelId,
|
|
386
|
-
segmentType: "parallel",
|
|
387
|
-
belongsToRoute,
|
|
388
|
-
source: "parallel",
|
|
389
|
-
defaultShouldRevalidate: true,
|
|
390
|
-
finalShouldRevalidate: true,
|
|
391
|
-
reason: "full-refetch",
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
397
|
-
const result = belongsToRoute || isNewParent;
|
|
398
|
-
if (isTraceActive()) {
|
|
399
|
-
pushRevalidationTraceEntry({
|
|
400
|
-
segmentId: parallelId,
|
|
401
|
-
segmentType: "parallel",
|
|
402
|
-
belongsToRoute,
|
|
403
|
-
source: "parallel",
|
|
404
|
-
defaultShouldRevalidate: result,
|
|
405
|
-
finalShouldRevalidate: result,
|
|
406
|
-
reason: result ? "new-segment" : "skip-parent-chain",
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
return result;
|
|
440
|
+
const shouldResolve = await (async () => {
|
|
441
|
+
if (isFullRefetch) {
|
|
442
|
+
if (isTraceActive()) {
|
|
443
|
+
pushRevalidationTraceEntry({
|
|
444
|
+
segmentId: parallelId,
|
|
445
|
+
segmentType: "parallel",
|
|
446
|
+
belongsToRoute,
|
|
447
|
+
source: "parallel",
|
|
448
|
+
defaultShouldRevalidate: true,
|
|
449
|
+
finalShouldRevalidate: true,
|
|
450
|
+
reason: "full-refetch",
|
|
451
|
+
});
|
|
410
452
|
}
|
|
411
|
-
|
|
412
|
-
const dummySegment: ResolvedSegment = {
|
|
413
|
-
id: parallelId,
|
|
414
|
-
namespace: parallelEntry.id,
|
|
415
|
-
type: "parallel",
|
|
416
|
-
index: 0,
|
|
417
|
-
component: null as any,
|
|
418
|
-
params,
|
|
419
|
-
slot,
|
|
420
|
-
belongsToRoute,
|
|
421
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
422
|
-
...(parallelEntry.mountPath
|
|
423
|
-
? { mountPath: parallelEntry.mountPath }
|
|
424
|
-
: {}),
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
return await evaluateRevalidation({
|
|
428
|
-
segment: dummySegment,
|
|
429
|
-
prevParams,
|
|
430
|
-
getPrevSegment: null,
|
|
431
|
-
request,
|
|
432
|
-
prevUrl,
|
|
433
|
-
nextUrl,
|
|
434
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
435
|
-
name: `revalidate${i}`,
|
|
436
|
-
fn,
|
|
437
|
-
})),
|
|
438
|
-
routeKey,
|
|
439
|
-
context,
|
|
440
|
-
actionContext,
|
|
441
|
-
stale,
|
|
442
|
-
traceSource: "parallel",
|
|
443
|
-
});
|
|
444
|
-
})();
|
|
445
|
-
emitRevalidationDecision(
|
|
446
|
-
parallelId,
|
|
447
|
-
context.pathname,
|
|
448
|
-
routeKey,
|
|
449
|
-
shouldResolve,
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
let component: ReactNode | undefined;
|
|
453
|
-
if (shouldResolve) {
|
|
454
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
453
|
+
return true;
|
|
455
454
|
}
|
|
456
|
-
if (
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
segmentType: "parallel",
|
|
469
|
-
});
|
|
470
|
-
observeStreamedHandler(
|
|
471
|
-
tracked,
|
|
472
|
-
parallelId,
|
|
473
|
-
"parallel",
|
|
474
|
-
context.pathname,
|
|
475
|
-
routeKey,
|
|
476
|
-
params,
|
|
477
|
-
);
|
|
478
|
-
component = tracked as ReactNode;
|
|
479
|
-
} else {
|
|
480
|
-
component = result as ReactNode;
|
|
481
|
-
}
|
|
482
|
-
} else {
|
|
483
|
-
component =
|
|
484
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
455
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
456
|
+
const result = belongsToRoute || isNewParent;
|
|
457
|
+
if (isTraceActive()) {
|
|
458
|
+
pushRevalidationTraceEntry({
|
|
459
|
+
segmentId: parallelId,
|
|
460
|
+
segmentType: "parallel",
|
|
461
|
+
belongsToRoute,
|
|
462
|
+
source: "parallel",
|
|
463
|
+
defaultShouldRevalidate: result,
|
|
464
|
+
finalShouldRevalidate: result,
|
|
465
|
+
reason: result ? "new-segment" : "skip-parent-chain",
|
|
466
|
+
});
|
|
485
467
|
}
|
|
468
|
+
return result;
|
|
486
469
|
}
|
|
487
470
|
|
|
488
|
-
|
|
471
|
+
const dummySegment: ResolvedSegment = {
|
|
489
472
|
id: parallelId,
|
|
490
473
|
namespace: parallelEntry.id,
|
|
491
474
|
type: "parallel",
|
|
492
475
|
index: 0,
|
|
493
|
-
component,
|
|
494
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
495
|
-
transition: parallelEntry.transition,
|
|
476
|
+
component: null as any,
|
|
496
477
|
params,
|
|
497
478
|
slot,
|
|
498
479
|
belongsToRoute,
|
|
@@ -500,28 +481,111 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
500
481
|
...(parallelEntry.mountPath
|
|
501
482
|
? { mountPath: parallelEntry.mountPath }
|
|
502
483
|
: {}),
|
|
503
|
-
}
|
|
504
|
-
}
|
|
484
|
+
};
|
|
505
485
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
parallelEntry,
|
|
509
|
-
context,
|
|
510
|
-
belongsToRoute,
|
|
511
|
-
clientSegmentIds,
|
|
486
|
+
return await evaluateRevalidation({
|
|
487
|
+
segment: dummySegment,
|
|
512
488
|
prevParams,
|
|
489
|
+
getPrevSegment: null,
|
|
513
490
|
request,
|
|
514
491
|
prevUrl,
|
|
515
492
|
nextUrl,
|
|
493
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
494
|
+
name: `revalidate${i}`,
|
|
495
|
+
fn,
|
|
496
|
+
})),
|
|
516
497
|
routeKey,
|
|
517
|
-
|
|
498
|
+
context,
|
|
518
499
|
actionContext,
|
|
519
|
-
entry.shortCode,
|
|
520
500
|
stale,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
501
|
+
traceSource: "parallel",
|
|
502
|
+
});
|
|
503
|
+
})();
|
|
504
|
+
emitRevalidationDecision(
|
|
505
|
+
parallelId,
|
|
506
|
+
context.pathname,
|
|
507
|
+
routeKey,
|
|
508
|
+
shouldResolve,
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
let component: ReactNode | undefined;
|
|
512
|
+
if (shouldResolve) {
|
|
513
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
514
|
+
}
|
|
515
|
+
if (component === undefined) {
|
|
516
|
+
const hasLoadingFallback =
|
|
517
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
518
|
+
if (!shouldResolve) {
|
|
519
|
+
component = null;
|
|
520
|
+
} else if (handler === undefined) {
|
|
521
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
522
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
523
|
+
component = null;
|
|
524
|
+
} else if (hasLoadingFallback) {
|
|
525
|
+
const result =
|
|
526
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
527
|
+
if (result instanceof Promise) {
|
|
528
|
+
const tracked = deps.trackHandler(result, {
|
|
529
|
+
segmentId: parallelId,
|
|
530
|
+
segmentType: "parallel",
|
|
531
|
+
});
|
|
532
|
+
observeStreamedHandler(
|
|
533
|
+
tracked,
|
|
534
|
+
parallelId,
|
|
535
|
+
"parallel",
|
|
536
|
+
context.pathname,
|
|
537
|
+
routeKey,
|
|
538
|
+
params,
|
|
539
|
+
);
|
|
540
|
+
component = tracked as ReactNode;
|
|
541
|
+
} else {
|
|
542
|
+
component = result as ReactNode;
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
component =
|
|
546
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
547
|
+
}
|
|
524
548
|
}
|
|
549
|
+
|
|
550
|
+
segments.push({
|
|
551
|
+
id: parallelId,
|
|
552
|
+
namespace: parallelEntry.id,
|
|
553
|
+
type: "parallel",
|
|
554
|
+
index: 0,
|
|
555
|
+
component,
|
|
556
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
557
|
+
transition: parallelEntry.transition,
|
|
558
|
+
params,
|
|
559
|
+
slot,
|
|
560
|
+
belongsToRoute,
|
|
561
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
562
|
+
...(parallelEntry.mountPath
|
|
563
|
+
? { mountPath: parallelEntry.mountPath }
|
|
564
|
+
: {}),
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
572
|
+
parallelEntry,
|
|
573
|
+
context,
|
|
574
|
+
belongsToRoute,
|
|
575
|
+
clientSegmentIds,
|
|
576
|
+
prevParams,
|
|
577
|
+
request,
|
|
578
|
+
prevUrl,
|
|
579
|
+
nextUrl,
|
|
580
|
+
routeKey,
|
|
581
|
+
deps,
|
|
582
|
+
actionContext,
|
|
583
|
+
entry.shortCode,
|
|
584
|
+
stale,
|
|
585
|
+
);
|
|
586
|
+
segments.push(...loaderResult.segments);
|
|
587
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
588
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
525
589
|
}
|
|
526
590
|
|
|
527
591
|
return { segments, matchedIds };
|
|
@@ -607,6 +671,8 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
607
671
|
context,
|
|
608
672
|
actionContext,
|
|
609
673
|
stale,
|
|
674
|
+
traceSource:
|
|
675
|
+
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
610
676
|
});
|
|
611
677
|
emitRevalidationDecision(
|
|
612
678
|
entry.shortCode,
|
|
@@ -621,20 +687,36 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
621
687
|
return shouldRevalidate;
|
|
622
688
|
},
|
|
623
689
|
async () => {
|
|
690
|
+
const doneHandler = track(`handler:${entry.id}`, 2);
|
|
624
691
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
625
692
|
entry.shortCode;
|
|
626
693
|
if (entry.type === "layout" || entry.type === "cache") {
|
|
627
|
-
|
|
694
|
+
const layoutComponent = await resolveLayoutComponent(entry, context);
|
|
695
|
+
doneHandler();
|
|
696
|
+
return layoutComponent;
|
|
628
697
|
}
|
|
629
698
|
const staticComponent = await tryStaticHandler(entry, entry.shortCode);
|
|
630
|
-
if (staticComponent !== undefined)
|
|
699
|
+
if (staticComponent !== undefined) {
|
|
700
|
+
doneHandler();
|
|
701
|
+
return staticComponent;
|
|
702
|
+
}
|
|
631
703
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
704
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
705
|
+
// the build handler. At build time (context.build === true), always
|
|
706
|
+
// use the build handler from routeEntry.handler.
|
|
707
|
+
const handler =
|
|
708
|
+
!context.build && routeEntry.liveHandler
|
|
709
|
+
? routeEntry.liveHandler
|
|
710
|
+
: routeEntry.handler;
|
|
632
711
|
if (!routeEntry.loading) {
|
|
633
|
-
|
|
712
|
+
const result = handleHandlerResult(await handler(context));
|
|
713
|
+
doneHandler();
|
|
714
|
+
return result;
|
|
634
715
|
}
|
|
635
716
|
if (!actionContext) {
|
|
636
|
-
const result = handleHandlerResult(
|
|
717
|
+
const result = handleHandlerResult(handler(context));
|
|
637
718
|
if (result instanceof Promise) {
|
|
719
|
+
result.finally(doneHandler).catch(() => {});
|
|
638
720
|
const tracked = deps.trackHandler(result, {
|
|
639
721
|
segmentId: entry.shortCode,
|
|
640
722
|
segmentType: entry.type,
|
|
@@ -649,24 +731,27 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
649
731
|
);
|
|
650
732
|
return { content: tracked };
|
|
651
733
|
}
|
|
734
|
+
doneHandler();
|
|
652
735
|
return { content: result };
|
|
653
736
|
}
|
|
654
737
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
655
738
|
entryId: entry.id,
|
|
656
739
|
});
|
|
740
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
741
|
+
doneHandler();
|
|
657
742
|
return {
|
|
658
|
-
content: Promise.resolve(
|
|
659
|
-
handleHandlerResult(await routeEntry.handler(context)),
|
|
660
|
-
),
|
|
743
|
+
content: Promise.resolve(actionResult),
|
|
661
744
|
};
|
|
662
745
|
},
|
|
663
746
|
() => null,
|
|
664
747
|
);
|
|
665
748
|
|
|
749
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
750
|
+
// component === null checks work consistently for both void and explicit null.
|
|
666
751
|
const resolvedComponent =
|
|
667
752
|
component && typeof component === "object" && "content" in component
|
|
668
|
-
? (component as { content: ReactNode }).content
|
|
669
|
-
: component;
|
|
753
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
754
|
+
: (component ?? null);
|
|
670
755
|
|
|
671
756
|
const segment: ResolvedSegment = {
|
|
672
757
|
id: entry.shortCode,
|
|
@@ -768,6 +853,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
768
853
|
deps,
|
|
769
854
|
actionContext,
|
|
770
855
|
stale,
|
|
856
|
+
entry,
|
|
771
857
|
);
|
|
772
858
|
segments.push(...orphanResult.segments);
|
|
773
859
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -879,6 +965,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
879
965
|
deps: SegmentResolutionDeps<TEnv>,
|
|
880
966
|
actionContext?: ActionContext,
|
|
881
967
|
stale?: boolean,
|
|
968
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
969
|
+
parentRouteEntry?: EntryData,
|
|
882
970
|
): Promise<SegmentRevalidationResult> {
|
|
883
971
|
invariant(
|
|
884
972
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -906,6 +994,33 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
906
994
|
segments.push(...loaderResult.segments);
|
|
907
995
|
matchedIds.push(...loaderResult.matchedIds);
|
|
908
996
|
|
|
997
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
998
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
999
|
+
if (
|
|
1000
|
+
parentRouteEntry &&
|
|
1001
|
+
parentRouteEntry.loader &&
|
|
1002
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1003
|
+
Object.keys(orphan.parallel).length > 0
|
|
1004
|
+
) {
|
|
1005
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1006
|
+
parentRouteEntry,
|
|
1007
|
+
context,
|
|
1008
|
+
belongsToRoute,
|
|
1009
|
+
clientSegmentIds,
|
|
1010
|
+
prevParams,
|
|
1011
|
+
request,
|
|
1012
|
+
prevUrl,
|
|
1013
|
+
nextUrl,
|
|
1014
|
+
routeKey,
|
|
1015
|
+
deps,
|
|
1016
|
+
actionContext,
|
|
1017
|
+
orphan.shortCode,
|
|
1018
|
+
stale,
|
|
1019
|
+
);
|
|
1020
|
+
segments.push(...inheritedResult.segments);
|
|
1021
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
909
1024
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
910
1025
|
// so ctx.set() values are visible to parallel children.
|
|
911
1026
|
matchedIds.push(orphan.shortCode);
|
|
@@ -982,143 +1097,72 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
982
1097
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
983
1098
|
});
|
|
984
1099
|
|
|
985
|
-
|
|
1100
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1101
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1102
|
+
orphan.parallel,
|
|
1103
|
+
)) {
|
|
986
1104
|
invariant(
|
|
987
1105
|
parallelEntry.type === "parallel",
|
|
988
1106
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
989
1107
|
);
|
|
990
1108
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1109
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1110
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1111
|
+
parallelEntry,
|
|
1112
|
+
context,
|
|
1113
|
+
belongsToRoute,
|
|
1114
|
+
clientSegmentIds,
|
|
1115
|
+
prevParams,
|
|
1116
|
+
request,
|
|
1117
|
+
prevUrl,
|
|
1118
|
+
nextUrl,
|
|
1119
|
+
routeKey,
|
|
1120
|
+
deps,
|
|
1121
|
+
actionContext,
|
|
1122
|
+
undefined,
|
|
1123
|
+
stale,
|
|
1124
|
+
);
|
|
1125
|
+
segments.push(...loaderResult.segments);
|
|
1126
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1127
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1128
|
+
}
|
|
1008
1129
|
|
|
1009
1130
|
const slots = parallelEntry.handler as Record<
|
|
1010
1131
|
`@${string}`,
|
|
1011
1132
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1012
1133
|
| ReactNode
|
|
1013
1134
|
>;
|
|
1135
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1136
|
+
const handler = slots[slot];
|
|
1014
1137
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
matchedIds.push(parallelId);
|
|
1021
|
-
|
|
1022
|
-
const shouldResolve = await (async () => {
|
|
1023
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
1024
|
-
if (isTraceActive()) {
|
|
1025
|
-
pushRevalidationTraceEntry({
|
|
1026
|
-
segmentId: parallelId,
|
|
1027
|
-
segmentType: "parallel",
|
|
1028
|
-
belongsToRoute,
|
|
1029
|
-
source: "parallel",
|
|
1030
|
-
defaultShouldRevalidate: true,
|
|
1031
|
-
finalShouldRevalidate: true,
|
|
1032
|
-
reason: "new-segment",
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
return true;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
const dummySegment: ResolvedSegment = {
|
|
1039
|
-
id: parallelId,
|
|
1040
|
-
namespace: parallelEntry.id,
|
|
1041
|
-
type: "parallel",
|
|
1042
|
-
index: 0,
|
|
1043
|
-
component: null as any,
|
|
1044
|
-
params,
|
|
1045
|
-
slot,
|
|
1046
|
-
belongsToRoute,
|
|
1047
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1048
|
-
...(parallelEntry.mountPath
|
|
1049
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1050
|
-
: {}),
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
return await evaluateRevalidation({
|
|
1054
|
-
segment: dummySegment,
|
|
1055
|
-
prevParams,
|
|
1056
|
-
getPrevSegment: null,
|
|
1057
|
-
request,
|
|
1058
|
-
prevUrl,
|
|
1059
|
-
nextUrl,
|
|
1060
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1061
|
-
name: `revalidate${i}`,
|
|
1062
|
-
fn,
|
|
1063
|
-
})),
|
|
1064
|
-
routeKey,
|
|
1065
|
-
context,
|
|
1066
|
-
actionContext,
|
|
1067
|
-
stale,
|
|
1068
|
-
traceSource: "parallel",
|
|
1069
|
-
});
|
|
1070
|
-
})();
|
|
1071
|
-
emitRevalidationDecision(
|
|
1072
|
-
parallelId,
|
|
1073
|
-
context.pathname,
|
|
1074
|
-
routeKey,
|
|
1075
|
-
shouldResolve,
|
|
1076
|
-
);
|
|
1138
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1139
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1140
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1141
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1142
|
+
matchedIds.push(parallelId);
|
|
1077
1143
|
|
|
1078
|
-
|
|
1079
|
-
if (
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1091
|
-
if (result instanceof Promise) {
|
|
1092
|
-
const tracked = deps.trackHandler(result, {
|
|
1093
|
-
segmentId: parallelId,
|
|
1094
|
-
segmentType: "parallel",
|
|
1095
|
-
});
|
|
1096
|
-
observeStreamedHandler(
|
|
1097
|
-
tracked,
|
|
1098
|
-
parallelId,
|
|
1099
|
-
"parallel",
|
|
1100
|
-
context.pathname,
|
|
1101
|
-
routeKey,
|
|
1102
|
-
params,
|
|
1103
|
-
);
|
|
1104
|
-
component = tracked as ReactNode;
|
|
1105
|
-
} else {
|
|
1106
|
-
component = result as ReactNode;
|
|
1107
|
-
}
|
|
1108
|
-
} else {
|
|
1109
|
-
component =
|
|
1110
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1144
|
+
const shouldResolve = await (async () => {
|
|
1145
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
1146
|
+
if (isTraceActive()) {
|
|
1147
|
+
pushRevalidationTraceEntry({
|
|
1148
|
+
segmentId: parallelId,
|
|
1149
|
+
segmentType: "parallel",
|
|
1150
|
+
belongsToRoute,
|
|
1151
|
+
source: "parallel",
|
|
1152
|
+
defaultShouldRevalidate: true,
|
|
1153
|
+
finalShouldRevalidate: true,
|
|
1154
|
+
reason: "new-segment",
|
|
1155
|
+
});
|
|
1111
1156
|
}
|
|
1157
|
+
return true;
|
|
1112
1158
|
}
|
|
1113
1159
|
|
|
1114
|
-
|
|
1160
|
+
const dummySegment: ResolvedSegment = {
|
|
1115
1161
|
id: parallelId,
|
|
1116
1162
|
namespace: parallelEntry.id,
|
|
1117
1163
|
type: "parallel",
|
|
1118
1164
|
index: 0,
|
|
1119
|
-
component,
|
|
1120
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1121
|
-
transition: parallelEntry.transition,
|
|
1165
|
+
component: null as any,
|
|
1122
1166
|
params,
|
|
1123
1167
|
slot,
|
|
1124
1168
|
belongsToRoute,
|
|
@@ -1126,8 +1170,87 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1126
1170
|
...(parallelEntry.mountPath
|
|
1127
1171
|
? { mountPath: parallelEntry.mountPath }
|
|
1128
1172
|
: {}),
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
return await evaluateRevalidation({
|
|
1176
|
+
segment: dummySegment,
|
|
1177
|
+
prevParams,
|
|
1178
|
+
getPrevSegment: null,
|
|
1179
|
+
request,
|
|
1180
|
+
prevUrl,
|
|
1181
|
+
nextUrl,
|
|
1182
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1183
|
+
name: `revalidate${i}`,
|
|
1184
|
+
fn,
|
|
1185
|
+
})),
|
|
1186
|
+
routeKey,
|
|
1187
|
+
context,
|
|
1188
|
+
actionContext,
|
|
1189
|
+
stale,
|
|
1190
|
+
traceSource: "parallel",
|
|
1129
1191
|
});
|
|
1192
|
+
})();
|
|
1193
|
+
emitRevalidationDecision(
|
|
1194
|
+
parallelId,
|
|
1195
|
+
context.pathname,
|
|
1196
|
+
routeKey,
|
|
1197
|
+
shouldResolve,
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
let component: ReactNode | undefined;
|
|
1201
|
+
if (shouldResolve) {
|
|
1202
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1203
|
+
}
|
|
1204
|
+
if (component === undefined) {
|
|
1205
|
+
const hasLoadingFallback =
|
|
1206
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1207
|
+
if (!shouldResolve) {
|
|
1208
|
+
component = null;
|
|
1209
|
+
} else if (handler === undefined) {
|
|
1210
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1211
|
+
component = null;
|
|
1212
|
+
} else if (hasLoadingFallback) {
|
|
1213
|
+
const result =
|
|
1214
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1215
|
+
if (result instanceof Promise) {
|
|
1216
|
+
const tracked = deps.trackHandler(result, {
|
|
1217
|
+
segmentId: parallelId,
|
|
1218
|
+
segmentType: "parallel",
|
|
1219
|
+
});
|
|
1220
|
+
observeStreamedHandler(
|
|
1221
|
+
tracked,
|
|
1222
|
+
parallelId,
|
|
1223
|
+
"parallel",
|
|
1224
|
+
context.pathname,
|
|
1225
|
+
routeKey,
|
|
1226
|
+
params,
|
|
1227
|
+
);
|
|
1228
|
+
component = tracked as ReactNode;
|
|
1229
|
+
} else {
|
|
1230
|
+
component = result as ReactNode;
|
|
1231
|
+
}
|
|
1232
|
+
} else {
|
|
1233
|
+
component =
|
|
1234
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
1235
|
+
}
|
|
1130
1236
|
}
|
|
1237
|
+
|
|
1238
|
+
segments.push({
|
|
1239
|
+
id: parallelId,
|
|
1240
|
+
namespace: parallelEntry.id,
|
|
1241
|
+
type: "parallel",
|
|
1242
|
+
index: 0,
|
|
1243
|
+
component,
|
|
1244
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1245
|
+
transition: parallelEntry.transition,
|
|
1246
|
+
params,
|
|
1247
|
+
slot,
|
|
1248
|
+
belongsToRoute,
|
|
1249
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1250
|
+
...(parallelEntry.mountPath
|
|
1251
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1252
|
+
: {}),
|
|
1253
|
+
});
|
|
1131
1254
|
}
|
|
1132
1255
|
|
|
1133
1256
|
return { segments, matchedIds };
|
|
@@ -1152,6 +1275,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1152
1275
|
localRouteName: string,
|
|
1153
1276
|
pathname: string,
|
|
1154
1277
|
deps: SegmentResolutionDeps<TEnv>,
|
|
1278
|
+
stale?: boolean,
|
|
1155
1279
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1156
1280
|
const allSegments: ResolvedSegment[] = [];
|
|
1157
1281
|
const matchedIds: string[] = [];
|
|
@@ -1178,6 +1302,11 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1178
1302
|
}
|
|
1179
1303
|
|
|
1180
1304
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1305
|
+
if (entry.type === "cache") {
|
|
1306
|
+
const store = RSCRouterContext.getStore();
|
|
1307
|
+
if (store) store.insideCacheScope = true;
|
|
1308
|
+
}
|
|
1309
|
+
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1181
1310
|
const resolved = await resolveWithErrorBoundary(
|
|
1182
1311
|
nonParallelEntry,
|
|
1183
1312
|
params,
|
|
@@ -1195,15 +1324,14 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1195
1324
|
loaderPromises,
|
|
1196
1325
|
deps,
|
|
1197
1326
|
actionContext,
|
|
1198
|
-
|
|
1327
|
+
stale,
|
|
1199
1328
|
),
|
|
1200
1329
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
1201
1330
|
deps,
|
|
1202
|
-
telemetry
|
|
1203
|
-
? { request, url: context.url, routeKey, isPartial: true, telemetry }
|
|
1204
|
-
: undefined,
|
|
1331
|
+
{ request, url: context.url, routeKey, isPartial: true, telemetry },
|
|
1205
1332
|
pathname,
|
|
1206
1333
|
);
|
|
1334
|
+
doneEntry();
|
|
1207
1335
|
|
|
1208
1336
|
// Deduplicate segments and matchedIds by ID, matching resolveAllSegments.
|
|
1209
1337
|
// include() scopes can produce entries that resolve the same shared
|