@rangojs/router 0.0.0-experimental.52ff0316 → 0.0.0-experimental.54a3dc6a
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/dist/bin/rango.js +8 -3
- package/dist/vite/index.js +139 -200
- package/package.json +1 -1
- package/skills/caching/SKILL.md +37 -4
- package/skills/parallel/SKILL.md +59 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +1 -7
- package/src/browser/navigation-client.ts +60 -27
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +39 -9
- package/src/browser/prefetch/cache.ts +57 -5
- package/src/browser/prefetch/fetch.ts +30 -21
- package/src/browser/prefetch/queue.ts +53 -13
- package/src/browser/react/Link.tsx +9 -1
- package/src/browser/react/NavigationProvider.tsx +27 -0
- package/src/browser/rsc-router.tsx +109 -57
- package/src/browser/scroll-restoration.ts +20 -11
- package/src/browser/segment-reconciler.ts +6 -1
- package/src/browser/types.ts +9 -0
- package/src/build/route-types/router-processing.ts +12 -2
- package/src/cache/cache-scope.ts +2 -2
- 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/debug.ts +2 -2
- package/src/route-definition/dsl-helpers.ts +32 -7
- package/src/route-definition/redirect.ts +2 -2
- package/src/router/lazy-includes.ts +2 -1
- package/src/router/logging.ts +1 -1
- package/src/router/manifest.ts +6 -2
- package/src/router/match-middleware/background-revalidation.ts +18 -1
- package/src/router/match-middleware/cache-lookup.ts +32 -9
- package/src/router/match-middleware/cache-store.ts +32 -6
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +7 -5
- package/src/router/match-result.ts +11 -1
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware.ts +2 -1
- package/src/router/segment-resolution/fresh.ts +121 -14
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +339 -287
- package/src/router.ts +1 -1
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +90 -13
- package/src/server/request-context.ts +10 -4
- package/src/ssr/index.tsx +1 -0
- package/src/types/segments.ts +2 -0
- package/src/urls/path-helper.ts +1 -1
- package/src/vite/discovery/state.ts +0 -2
- package/src/vite/plugin-types.ts +0 -83
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +144 -209
- package/src/vite/router-discovery.ts +0 -8
- package/src/vite/utils/banner.ts +3 -3
|
@@ -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,
|
|
@@ -258,26 +262,62 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
258
262
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
259
263
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
260
264
|
const allMatchedIds: string[] = [];
|
|
265
|
+
const seenIds = new Set<string>();
|
|
266
|
+
|
|
267
|
+
async function collectEntryLoaders(
|
|
268
|
+
entry: EntryData,
|
|
269
|
+
belongsToRoute: boolean,
|
|
270
|
+
shortCodeOverride?: string,
|
|
271
|
+
): Promise<void> {
|
|
272
|
+
// Skip if all loaders from this entry have already been resolved
|
|
273
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
274
|
+
const loaderEntries = entry.loader ?? [];
|
|
275
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
276
|
+
const allAlreadySeen =
|
|
277
|
+
loaderEntries.length > 0 &&
|
|
278
|
+
loaderEntries.every((le, i) =>
|
|
279
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
280
|
+
);
|
|
281
|
+
if (!allAlreadySeen) {
|
|
282
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
283
|
+
entry,
|
|
284
|
+
context,
|
|
285
|
+
belongsToRoute,
|
|
286
|
+
clientSegmentIds,
|
|
287
|
+
prevParams,
|
|
288
|
+
request,
|
|
289
|
+
prevUrl,
|
|
290
|
+
nextUrl,
|
|
291
|
+
routeKey,
|
|
292
|
+
deps,
|
|
293
|
+
actionContext,
|
|
294
|
+
shortCodeOverride,
|
|
295
|
+
stale,
|
|
296
|
+
);
|
|
297
|
+
for (const seg of segments) {
|
|
298
|
+
if (!seenIds.has(seg.id)) {
|
|
299
|
+
seenIds.add(seg.id);
|
|
300
|
+
allLoaderSegments.push(seg);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
allMatchedIds.push(...matchedIds);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const seenParallelEntryIds = new Set<string>();
|
|
307
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
308
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
309
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
310
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
314
|
+
for (const layoutEntry of entry.layout) {
|
|
315
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
261
318
|
|
|
262
319
|
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);
|
|
320
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
281
321
|
}
|
|
282
322
|
|
|
283
323
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -301,22 +341,20 @@ export function buildEntryRevalidateMap(
|
|
|
301
341
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
302
342
|
|
|
303
343
|
if (entry.type !== "parallel") {
|
|
304
|
-
for (const parallelEntry of
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}
|
|
344
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
345
|
+
entry.parallel,
|
|
346
|
+
)) {
|
|
347
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
348
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
349
|
+
map.set(parallelId, {
|
|
350
|
+
entry: parallelEntry,
|
|
351
|
+
revalidate: parallelEntry.revalidate,
|
|
352
|
+
});
|
|
315
353
|
}
|
|
316
354
|
}
|
|
317
355
|
|
|
318
356
|
for (const layoutEntry of entry.layout) {
|
|
319
|
-
processEntry(layoutEntry);
|
|
357
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
320
358
|
}
|
|
321
359
|
}
|
|
322
360
|
|
|
@@ -348,7 +386,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
348
386
|
const segments: ResolvedSegment[] = [];
|
|
349
387
|
const matchedIds: string[] = [];
|
|
350
388
|
|
|
351
|
-
|
|
389
|
+
const resolvedParallelEntries = new Set<string>();
|
|
390
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
391
|
+
entry.parallel,
|
|
392
|
+
)) {
|
|
352
393
|
invariant(
|
|
353
394
|
parallelEntry.type === "parallel",
|
|
354
395
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -359,141 +400,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
359
400
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
360
401
|
| ReactNode
|
|
361
402
|
>;
|
|
403
|
+
// In production, static handler bodies are evicted and the slot value
|
|
404
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
405
|
+
// We defer the handler check until after tryStaticSlot.
|
|
406
|
+
const handler = slots[slot];
|
|
407
|
+
|
|
408
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
409
|
+
|
|
410
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
411
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
412
|
+
if (
|
|
413
|
+
isFullRefetch ||
|
|
414
|
+
clientSegmentIds.has(parallelId) ||
|
|
415
|
+
belongsToRoute ||
|
|
416
|
+
isNewParent
|
|
417
|
+
) {
|
|
418
|
+
matchedIds.push(parallelId);
|
|
419
|
+
}
|
|
362
420
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
clientSegmentIds.has(parallelId) ||
|
|
376
|
-
belongsToRoute ||
|
|
377
|
-
isNewParent
|
|
378
|
-
) {
|
|
379
|
-
matchedIds.push(parallelId);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const shouldResolve = await (async () => {
|
|
383
|
-
if (isFullRefetch) {
|
|
384
|
-
if (isTraceActive()) {
|
|
385
|
-
pushRevalidationTraceEntry({
|
|
386
|
-
segmentId: parallelId,
|
|
387
|
-
segmentType: "parallel",
|
|
388
|
-
belongsToRoute,
|
|
389
|
-
source: "parallel",
|
|
390
|
-
defaultShouldRevalidate: true,
|
|
391
|
-
finalShouldRevalidate: true,
|
|
392
|
-
reason: "full-refetch",
|
|
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;
|
|
421
|
+
const shouldResolve = await (async () => {
|
|
422
|
+
if (isFullRefetch) {
|
|
423
|
+
if (isTraceActive()) {
|
|
424
|
+
pushRevalidationTraceEntry({
|
|
425
|
+
segmentId: parallelId,
|
|
426
|
+
segmentType: "parallel",
|
|
427
|
+
belongsToRoute,
|
|
428
|
+
source: "parallel",
|
|
429
|
+
defaultShouldRevalidate: true,
|
|
430
|
+
finalShouldRevalidate: true,
|
|
431
|
+
reason: "full-refetch",
|
|
432
|
+
});
|
|
411
433
|
}
|
|
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
|
-
: {}),
|
|
426
|
-
};
|
|
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);
|
|
434
|
+
return true;
|
|
456
435
|
}
|
|
457
|
-
if (
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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;
|
|
436
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
437
|
+
const result = belongsToRoute || isNewParent;
|
|
438
|
+
if (isTraceActive()) {
|
|
439
|
+
pushRevalidationTraceEntry({
|
|
440
|
+
segmentId: parallelId,
|
|
441
|
+
segmentType: "parallel",
|
|
442
|
+
belongsToRoute,
|
|
443
|
+
source: "parallel",
|
|
444
|
+
defaultShouldRevalidate: result,
|
|
445
|
+
finalShouldRevalidate: result,
|
|
446
|
+
reason: result ? "new-segment" : "skip-parent-chain",
|
|
447
|
+
});
|
|
486
448
|
}
|
|
449
|
+
return result;
|
|
487
450
|
}
|
|
488
451
|
|
|
489
|
-
|
|
452
|
+
const dummySegment: ResolvedSegment = {
|
|
490
453
|
id: parallelId,
|
|
491
454
|
namespace: parallelEntry.id,
|
|
492
455
|
type: "parallel",
|
|
493
456
|
index: 0,
|
|
494
|
-
component,
|
|
495
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
496
|
-
transition: parallelEntry.transition,
|
|
457
|
+
component: null as any,
|
|
497
458
|
params,
|
|
498
459
|
slot,
|
|
499
460
|
belongsToRoute,
|
|
@@ -501,28 +462,111 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
501
462
|
...(parallelEntry.mountPath
|
|
502
463
|
? { mountPath: parallelEntry.mountPath }
|
|
503
464
|
: {}),
|
|
504
|
-
}
|
|
505
|
-
}
|
|
465
|
+
};
|
|
506
466
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
parallelEntry,
|
|
510
|
-
context,
|
|
511
|
-
belongsToRoute,
|
|
512
|
-
clientSegmentIds,
|
|
467
|
+
return await evaluateRevalidation({
|
|
468
|
+
segment: dummySegment,
|
|
513
469
|
prevParams,
|
|
470
|
+
getPrevSegment: null,
|
|
514
471
|
request,
|
|
515
472
|
prevUrl,
|
|
516
473
|
nextUrl,
|
|
474
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
475
|
+
name: `revalidate${i}`,
|
|
476
|
+
fn,
|
|
477
|
+
})),
|
|
517
478
|
routeKey,
|
|
518
|
-
|
|
479
|
+
context,
|
|
519
480
|
actionContext,
|
|
520
|
-
entry.shortCode,
|
|
521
481
|
stale,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
482
|
+
traceSource: "parallel",
|
|
483
|
+
});
|
|
484
|
+
})();
|
|
485
|
+
emitRevalidationDecision(
|
|
486
|
+
parallelId,
|
|
487
|
+
context.pathname,
|
|
488
|
+
routeKey,
|
|
489
|
+
shouldResolve,
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
let component: ReactNode | undefined;
|
|
493
|
+
if (shouldResolve) {
|
|
494
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
495
|
+
}
|
|
496
|
+
if (component === undefined) {
|
|
497
|
+
const hasLoadingFallback =
|
|
498
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
499
|
+
if (!shouldResolve) {
|
|
500
|
+
component = null;
|
|
501
|
+
} else if (handler === undefined) {
|
|
502
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
503
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
504
|
+
component = null;
|
|
505
|
+
} else if (hasLoadingFallback) {
|
|
506
|
+
const result =
|
|
507
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
508
|
+
if (result instanceof Promise) {
|
|
509
|
+
const tracked = deps.trackHandler(result, {
|
|
510
|
+
segmentId: parallelId,
|
|
511
|
+
segmentType: "parallel",
|
|
512
|
+
});
|
|
513
|
+
observeStreamedHandler(
|
|
514
|
+
tracked,
|
|
515
|
+
parallelId,
|
|
516
|
+
"parallel",
|
|
517
|
+
context.pathname,
|
|
518
|
+
routeKey,
|
|
519
|
+
params,
|
|
520
|
+
);
|
|
521
|
+
component = tracked as ReactNode;
|
|
522
|
+
} else {
|
|
523
|
+
component = result as ReactNode;
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
component =
|
|
527
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
segments.push({
|
|
532
|
+
id: parallelId,
|
|
533
|
+
namespace: parallelEntry.id,
|
|
534
|
+
type: "parallel",
|
|
535
|
+
index: 0,
|
|
536
|
+
component,
|
|
537
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
538
|
+
transition: parallelEntry.transition,
|
|
539
|
+
params,
|
|
540
|
+
slot,
|
|
541
|
+
belongsToRoute,
|
|
542
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
543
|
+
...(parallelEntry.mountPath
|
|
544
|
+
? { mountPath: parallelEntry.mountPath }
|
|
545
|
+
: {}),
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
549
|
+
continue;
|
|
525
550
|
}
|
|
551
|
+
|
|
552
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
553
|
+
parallelEntry,
|
|
554
|
+
context,
|
|
555
|
+
belongsToRoute,
|
|
556
|
+
clientSegmentIds,
|
|
557
|
+
prevParams,
|
|
558
|
+
request,
|
|
559
|
+
prevUrl,
|
|
560
|
+
nextUrl,
|
|
561
|
+
routeKey,
|
|
562
|
+
deps,
|
|
563
|
+
actionContext,
|
|
564
|
+
entry.shortCode,
|
|
565
|
+
stale,
|
|
566
|
+
);
|
|
567
|
+
segments.push(...loaderResult.segments);
|
|
568
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
569
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
526
570
|
}
|
|
527
571
|
|
|
528
572
|
return { segments, matchedIds };
|
|
@@ -997,143 +1041,72 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
997
1041
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
998
1042
|
});
|
|
999
1043
|
|
|
1000
|
-
|
|
1044
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1045
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1046
|
+
orphan.parallel,
|
|
1047
|
+
)) {
|
|
1001
1048
|
invariant(
|
|
1002
1049
|
parallelEntry.type === "parallel",
|
|
1003
1050
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
1004
1051
|
);
|
|
1005
1052
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1053
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1054
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1055
|
+
parallelEntry,
|
|
1056
|
+
context,
|
|
1057
|
+
belongsToRoute,
|
|
1058
|
+
clientSegmentIds,
|
|
1059
|
+
prevParams,
|
|
1060
|
+
request,
|
|
1061
|
+
prevUrl,
|
|
1062
|
+
nextUrl,
|
|
1063
|
+
routeKey,
|
|
1064
|
+
deps,
|
|
1065
|
+
actionContext,
|
|
1066
|
+
undefined,
|
|
1067
|
+
stale,
|
|
1068
|
+
);
|
|
1069
|
+
segments.push(...loaderResult.segments);
|
|
1070
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1071
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1072
|
+
}
|
|
1023
1073
|
|
|
1024
1074
|
const slots = parallelEntry.handler as Record<
|
|
1025
1075
|
`@${string}`,
|
|
1026
1076
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1027
1077
|
| ReactNode
|
|
1028
1078
|
>;
|
|
1079
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1080
|
+
const handler = slots[slot];
|
|
1029
1081
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
matchedIds.push(parallelId);
|
|
1036
|
-
|
|
1037
|
-
const shouldResolve = await (async () => {
|
|
1038
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
1039
|
-
if (isTraceActive()) {
|
|
1040
|
-
pushRevalidationTraceEntry({
|
|
1041
|
-
segmentId: parallelId,
|
|
1042
|
-
segmentType: "parallel",
|
|
1043
|
-
belongsToRoute,
|
|
1044
|
-
source: "parallel",
|
|
1045
|
-
defaultShouldRevalidate: true,
|
|
1046
|
-
finalShouldRevalidate: true,
|
|
1047
|
-
reason: "new-segment",
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
return true;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
const dummySegment: ResolvedSegment = {
|
|
1054
|
-
id: parallelId,
|
|
1055
|
-
namespace: parallelEntry.id,
|
|
1056
|
-
type: "parallel",
|
|
1057
|
-
index: 0,
|
|
1058
|
-
component: null as any,
|
|
1059
|
-
params,
|
|
1060
|
-
slot,
|
|
1061
|
-
belongsToRoute,
|
|
1062
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1063
|
-
...(parallelEntry.mountPath
|
|
1064
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1065
|
-
: {}),
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
return await evaluateRevalidation({
|
|
1069
|
-
segment: dummySegment,
|
|
1070
|
-
prevParams,
|
|
1071
|
-
getPrevSegment: null,
|
|
1072
|
-
request,
|
|
1073
|
-
prevUrl,
|
|
1074
|
-
nextUrl,
|
|
1075
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1076
|
-
name: `revalidate${i}`,
|
|
1077
|
-
fn,
|
|
1078
|
-
})),
|
|
1079
|
-
routeKey,
|
|
1080
|
-
context,
|
|
1081
|
-
actionContext,
|
|
1082
|
-
stale,
|
|
1083
|
-
traceSource: "parallel",
|
|
1084
|
-
});
|
|
1085
|
-
})();
|
|
1086
|
-
emitRevalidationDecision(
|
|
1087
|
-
parallelId,
|
|
1088
|
-
context.pathname,
|
|
1089
|
-
routeKey,
|
|
1090
|
-
shouldResolve,
|
|
1091
|
-
);
|
|
1082
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1083
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1084
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1085
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1086
|
+
matchedIds.push(parallelId);
|
|
1092
1087
|
|
|
1093
|
-
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1106
|
-
if (result instanceof Promise) {
|
|
1107
|
-
const tracked = deps.trackHandler(result, {
|
|
1108
|
-
segmentId: parallelId,
|
|
1109
|
-
segmentType: "parallel",
|
|
1110
|
-
});
|
|
1111
|
-
observeStreamedHandler(
|
|
1112
|
-
tracked,
|
|
1113
|
-
parallelId,
|
|
1114
|
-
"parallel",
|
|
1115
|
-
context.pathname,
|
|
1116
|
-
routeKey,
|
|
1117
|
-
params,
|
|
1118
|
-
);
|
|
1119
|
-
component = tracked as ReactNode;
|
|
1120
|
-
} else {
|
|
1121
|
-
component = result as ReactNode;
|
|
1122
|
-
}
|
|
1123
|
-
} else {
|
|
1124
|
-
component =
|
|
1125
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1088
|
+
const shouldResolve = await (async () => {
|
|
1089
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
1090
|
+
if (isTraceActive()) {
|
|
1091
|
+
pushRevalidationTraceEntry({
|
|
1092
|
+
segmentId: parallelId,
|
|
1093
|
+
segmentType: "parallel",
|
|
1094
|
+
belongsToRoute,
|
|
1095
|
+
source: "parallel",
|
|
1096
|
+
defaultShouldRevalidate: true,
|
|
1097
|
+
finalShouldRevalidate: true,
|
|
1098
|
+
reason: "new-segment",
|
|
1099
|
+
});
|
|
1126
1100
|
}
|
|
1101
|
+
return true;
|
|
1127
1102
|
}
|
|
1128
1103
|
|
|
1129
|
-
|
|
1104
|
+
const dummySegment: ResolvedSegment = {
|
|
1130
1105
|
id: parallelId,
|
|
1131
1106
|
namespace: parallelEntry.id,
|
|
1132
1107
|
type: "parallel",
|
|
1133
1108
|
index: 0,
|
|
1134
|
-
component,
|
|
1135
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1136
|
-
transition: parallelEntry.transition,
|
|
1109
|
+
component: null as any,
|
|
1137
1110
|
params,
|
|
1138
1111
|
slot,
|
|
1139
1112
|
belongsToRoute,
|
|
@@ -1141,8 +1114,87 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1141
1114
|
...(parallelEntry.mountPath
|
|
1142
1115
|
? { mountPath: parallelEntry.mountPath }
|
|
1143
1116
|
: {}),
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
return await evaluateRevalidation({
|
|
1120
|
+
segment: dummySegment,
|
|
1121
|
+
prevParams,
|
|
1122
|
+
getPrevSegment: null,
|
|
1123
|
+
request,
|
|
1124
|
+
prevUrl,
|
|
1125
|
+
nextUrl,
|
|
1126
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1127
|
+
name: `revalidate${i}`,
|
|
1128
|
+
fn,
|
|
1129
|
+
})),
|
|
1130
|
+
routeKey,
|
|
1131
|
+
context,
|
|
1132
|
+
actionContext,
|
|
1133
|
+
stale,
|
|
1134
|
+
traceSource: "parallel",
|
|
1144
1135
|
});
|
|
1136
|
+
})();
|
|
1137
|
+
emitRevalidationDecision(
|
|
1138
|
+
parallelId,
|
|
1139
|
+
context.pathname,
|
|
1140
|
+
routeKey,
|
|
1141
|
+
shouldResolve,
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
let component: ReactNode | undefined;
|
|
1145
|
+
if (shouldResolve) {
|
|
1146
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1147
|
+
}
|
|
1148
|
+
if (component === undefined) {
|
|
1149
|
+
const hasLoadingFallback =
|
|
1150
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1151
|
+
if (!shouldResolve) {
|
|
1152
|
+
component = null;
|
|
1153
|
+
} else if (handler === undefined) {
|
|
1154
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1155
|
+
component = null;
|
|
1156
|
+
} else if (hasLoadingFallback) {
|
|
1157
|
+
const result =
|
|
1158
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1159
|
+
if (result instanceof Promise) {
|
|
1160
|
+
const tracked = deps.trackHandler(result, {
|
|
1161
|
+
segmentId: parallelId,
|
|
1162
|
+
segmentType: "parallel",
|
|
1163
|
+
});
|
|
1164
|
+
observeStreamedHandler(
|
|
1165
|
+
tracked,
|
|
1166
|
+
parallelId,
|
|
1167
|
+
"parallel",
|
|
1168
|
+
context.pathname,
|
|
1169
|
+
routeKey,
|
|
1170
|
+
params,
|
|
1171
|
+
);
|
|
1172
|
+
component = tracked as ReactNode;
|
|
1173
|
+
} else {
|
|
1174
|
+
component = result as ReactNode;
|
|
1175
|
+
}
|
|
1176
|
+
} else {
|
|
1177
|
+
component =
|
|
1178
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
1179
|
+
}
|
|
1145
1180
|
}
|
|
1181
|
+
|
|
1182
|
+
segments.push({
|
|
1183
|
+
id: parallelId,
|
|
1184
|
+
namespace: parallelEntry.id,
|
|
1185
|
+
type: "parallel",
|
|
1186
|
+
index: 0,
|
|
1187
|
+
component,
|
|
1188
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1189
|
+
transition: parallelEntry.transition,
|
|
1190
|
+
params,
|
|
1191
|
+
slot,
|
|
1192
|
+
belongsToRoute,
|
|
1193
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1194
|
+
...(parallelEntry.mountPath
|
|
1195
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1196
|
+
: {}),
|
|
1197
|
+
});
|
|
1146
1198
|
}
|
|
1147
1199
|
|
|
1148
1200
|
return { segments, matchedIds };
|