@rangojs/router 0.0.0-experimental.f2337aef → 0.0.0-experimental.fa8a383a
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 -3
- 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 -7
- 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 +4 -1
- package/src/router/logging.ts +1 -1
- package/src/router/manifest.ts +9 -3
- package/src/router/match-middleware/background-revalidation.ts +18 -1
- package/src/router/match-middleware/cache-lookup.ts +20 -3
- 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/middleware.ts +2 -1
- package/src/router/segment-resolution/fresh.ts +104 -14
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +307 -272
- package/src/router.ts +5 -1
- package/src/rsc/handler.ts +9 -0
- 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/route-entry.ts +7 -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,
|
|
@@ -259,8 +263,11 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
259
263
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
260
264
|
const allMatchedIds: string[] = [];
|
|
261
265
|
|
|
262
|
-
|
|
263
|
-
|
|
266
|
+
async function collectEntryLoaders(
|
|
267
|
+
entry: EntryData,
|
|
268
|
+
belongsToRoute: boolean,
|
|
269
|
+
shortCodeOverride?: string,
|
|
270
|
+
): Promise<void> {
|
|
264
271
|
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
265
272
|
entry,
|
|
266
273
|
context,
|
|
@@ -273,11 +280,27 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
273
280
|
routeKey,
|
|
274
281
|
deps,
|
|
275
282
|
actionContext,
|
|
276
|
-
|
|
283
|
+
shortCodeOverride,
|
|
277
284
|
stale,
|
|
278
285
|
);
|
|
279
286
|
allLoaderSegments.push(...segments);
|
|
280
287
|
allMatchedIds.push(...matchedIds);
|
|
288
|
+
|
|
289
|
+
const seenParallelEntryIds = new Set<string>();
|
|
290
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
291
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
292
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
293
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
297
|
+
for (const layoutEntry of entry.layout) {
|
|
298
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
281
304
|
}
|
|
282
305
|
|
|
283
306
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -301,22 +324,20 @@ export function buildEntryRevalidateMap(
|
|
|
301
324
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
302
325
|
|
|
303
326
|
if (entry.type !== "parallel") {
|
|
304
|
-
for (const parallelEntry of
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}
|
|
327
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
328
|
+
entry.parallel,
|
|
329
|
+
)) {
|
|
330
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
331
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
332
|
+
map.set(parallelId, {
|
|
333
|
+
entry: parallelEntry,
|
|
334
|
+
revalidate: parallelEntry.revalidate,
|
|
335
|
+
});
|
|
315
336
|
}
|
|
316
337
|
}
|
|
317
338
|
|
|
318
339
|
for (const layoutEntry of entry.layout) {
|
|
319
|
-
processEntry(layoutEntry);
|
|
340
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
320
341
|
}
|
|
321
342
|
}
|
|
322
343
|
|
|
@@ -348,7 +369,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
348
369
|
const segments: ResolvedSegment[] = [];
|
|
349
370
|
const matchedIds: string[] = [];
|
|
350
371
|
|
|
351
|
-
|
|
372
|
+
const resolvedParallelEntries = new Set<string>();
|
|
373
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
374
|
+
entry.parallel,
|
|
375
|
+
)) {
|
|
352
376
|
invariant(
|
|
353
377
|
parallelEntry.type === "parallel",
|
|
354
378
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -359,141 +383,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
359
383
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
360
384
|
| ReactNode
|
|
361
385
|
>;
|
|
386
|
+
// In production, static handler bodies are evicted and the slot value
|
|
387
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
388
|
+
// We defer the handler check until after tryStaticSlot.
|
|
389
|
+
const handler = slots[slot];
|
|
390
|
+
|
|
391
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
392
|
+
|
|
393
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
394
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
395
|
+
if (
|
|
396
|
+
isFullRefetch ||
|
|
397
|
+
clientSegmentIds.has(parallelId) ||
|
|
398
|
+
belongsToRoute ||
|
|
399
|
+
isNewParent
|
|
400
|
+
) {
|
|
401
|
+
matchedIds.push(parallelId);
|
|
402
|
+
}
|
|
362
403
|
|
|
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;
|
|
404
|
+
const shouldResolve = await (async () => {
|
|
405
|
+
if (isFullRefetch) {
|
|
406
|
+
if (isTraceActive()) {
|
|
407
|
+
pushRevalidationTraceEntry({
|
|
408
|
+
segmentId: parallelId,
|
|
409
|
+
segmentType: "parallel",
|
|
410
|
+
belongsToRoute,
|
|
411
|
+
source: "parallel",
|
|
412
|
+
defaultShouldRevalidate: true,
|
|
413
|
+
finalShouldRevalidate: true,
|
|
414
|
+
reason: "full-refetch",
|
|
415
|
+
});
|
|
411
416
|
}
|
|
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);
|
|
417
|
+
return true;
|
|
456
418
|
}
|
|
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;
|
|
419
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
420
|
+
const result = belongsToRoute || isNewParent;
|
|
421
|
+
if (isTraceActive()) {
|
|
422
|
+
pushRevalidationTraceEntry({
|
|
423
|
+
segmentId: parallelId,
|
|
424
|
+
segmentType: "parallel",
|
|
425
|
+
belongsToRoute,
|
|
426
|
+
source: "parallel",
|
|
427
|
+
defaultShouldRevalidate: result,
|
|
428
|
+
finalShouldRevalidate: result,
|
|
429
|
+
reason: result ? "new-segment" : "skip-parent-chain",
|
|
430
|
+
});
|
|
486
431
|
}
|
|
432
|
+
return result;
|
|
487
433
|
}
|
|
488
434
|
|
|
489
|
-
|
|
435
|
+
const dummySegment: ResolvedSegment = {
|
|
490
436
|
id: parallelId,
|
|
491
437
|
namespace: parallelEntry.id,
|
|
492
438
|
type: "parallel",
|
|
493
439
|
index: 0,
|
|
494
|
-
component,
|
|
495
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
496
|
-
transition: parallelEntry.transition,
|
|
440
|
+
component: null as any,
|
|
497
441
|
params,
|
|
498
442
|
slot,
|
|
499
443
|
belongsToRoute,
|
|
@@ -501,28 +445,111 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
501
445
|
...(parallelEntry.mountPath
|
|
502
446
|
? { mountPath: parallelEntry.mountPath }
|
|
503
447
|
: {}),
|
|
504
|
-
}
|
|
505
|
-
}
|
|
448
|
+
};
|
|
506
449
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
parallelEntry,
|
|
510
|
-
context,
|
|
511
|
-
belongsToRoute,
|
|
512
|
-
clientSegmentIds,
|
|
450
|
+
return await evaluateRevalidation({
|
|
451
|
+
segment: dummySegment,
|
|
513
452
|
prevParams,
|
|
453
|
+
getPrevSegment: null,
|
|
514
454
|
request,
|
|
515
455
|
prevUrl,
|
|
516
456
|
nextUrl,
|
|
457
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
458
|
+
name: `revalidate${i}`,
|
|
459
|
+
fn,
|
|
460
|
+
})),
|
|
517
461
|
routeKey,
|
|
518
|
-
|
|
462
|
+
context,
|
|
519
463
|
actionContext,
|
|
520
|
-
entry.shortCode,
|
|
521
464
|
stale,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
465
|
+
traceSource: "parallel",
|
|
466
|
+
});
|
|
467
|
+
})();
|
|
468
|
+
emitRevalidationDecision(
|
|
469
|
+
parallelId,
|
|
470
|
+
context.pathname,
|
|
471
|
+
routeKey,
|
|
472
|
+
shouldResolve,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
let component: ReactNode | undefined;
|
|
476
|
+
if (shouldResolve) {
|
|
477
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
478
|
+
}
|
|
479
|
+
if (component === undefined) {
|
|
480
|
+
const hasLoadingFallback =
|
|
481
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
482
|
+
if (!shouldResolve) {
|
|
483
|
+
component = null;
|
|
484
|
+
} else if (handler === undefined) {
|
|
485
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
486
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
487
|
+
component = null;
|
|
488
|
+
} else if (hasLoadingFallback) {
|
|
489
|
+
const result =
|
|
490
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
491
|
+
if (result instanceof Promise) {
|
|
492
|
+
const tracked = deps.trackHandler(result, {
|
|
493
|
+
segmentId: parallelId,
|
|
494
|
+
segmentType: "parallel",
|
|
495
|
+
});
|
|
496
|
+
observeStreamedHandler(
|
|
497
|
+
tracked,
|
|
498
|
+
parallelId,
|
|
499
|
+
"parallel",
|
|
500
|
+
context.pathname,
|
|
501
|
+
routeKey,
|
|
502
|
+
params,
|
|
503
|
+
);
|
|
504
|
+
component = tracked as ReactNode;
|
|
505
|
+
} else {
|
|
506
|
+
component = result as ReactNode;
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
component =
|
|
510
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
511
|
+
}
|
|
525
512
|
}
|
|
513
|
+
|
|
514
|
+
segments.push({
|
|
515
|
+
id: parallelId,
|
|
516
|
+
namespace: parallelEntry.id,
|
|
517
|
+
type: "parallel",
|
|
518
|
+
index: 0,
|
|
519
|
+
component,
|
|
520
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
521
|
+
transition: parallelEntry.transition,
|
|
522
|
+
params,
|
|
523
|
+
slot,
|
|
524
|
+
belongsToRoute,
|
|
525
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
526
|
+
...(parallelEntry.mountPath
|
|
527
|
+
? { mountPath: parallelEntry.mountPath }
|
|
528
|
+
: {}),
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
536
|
+
parallelEntry,
|
|
537
|
+
context,
|
|
538
|
+
belongsToRoute,
|
|
539
|
+
clientSegmentIds,
|
|
540
|
+
prevParams,
|
|
541
|
+
request,
|
|
542
|
+
prevUrl,
|
|
543
|
+
nextUrl,
|
|
544
|
+
routeKey,
|
|
545
|
+
deps,
|
|
546
|
+
actionContext,
|
|
547
|
+
entry.shortCode,
|
|
548
|
+
stale,
|
|
549
|
+
);
|
|
550
|
+
segments.push(...loaderResult.segments);
|
|
551
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
552
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
526
553
|
}
|
|
527
554
|
|
|
528
555
|
return { segments, matchedIds };
|
|
@@ -997,143 +1024,72 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
997
1024
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
998
1025
|
});
|
|
999
1026
|
|
|
1000
|
-
|
|
1027
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1028
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1029
|
+
orphan.parallel,
|
|
1030
|
+
)) {
|
|
1001
1031
|
invariant(
|
|
1002
1032
|
parallelEntry.type === "parallel",
|
|
1003
1033
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
1004
1034
|
);
|
|
1005
1035
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1036
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1037
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1038
|
+
parallelEntry,
|
|
1039
|
+
context,
|
|
1040
|
+
belongsToRoute,
|
|
1041
|
+
clientSegmentIds,
|
|
1042
|
+
prevParams,
|
|
1043
|
+
request,
|
|
1044
|
+
prevUrl,
|
|
1045
|
+
nextUrl,
|
|
1046
|
+
routeKey,
|
|
1047
|
+
deps,
|
|
1048
|
+
actionContext,
|
|
1049
|
+
undefined,
|
|
1050
|
+
stale,
|
|
1051
|
+
);
|
|
1052
|
+
segments.push(...loaderResult.segments);
|
|
1053
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1054
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1055
|
+
}
|
|
1023
1056
|
|
|
1024
1057
|
const slots = parallelEntry.handler as Record<
|
|
1025
1058
|
`@${string}`,
|
|
1026
1059
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1027
1060
|
| ReactNode
|
|
1028
1061
|
>;
|
|
1062
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1063
|
+
const handler = slots[slot];
|
|
1029
1064
|
|
|
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
|
-
);
|
|
1065
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1066
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1067
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1068
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1069
|
+
matchedIds.push(parallelId);
|
|
1092
1070
|
|
|
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;
|
|
1071
|
+
const shouldResolve = await (async () => {
|
|
1072
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
1073
|
+
if (isTraceActive()) {
|
|
1074
|
+
pushRevalidationTraceEntry({
|
|
1075
|
+
segmentId: parallelId,
|
|
1076
|
+
segmentType: "parallel",
|
|
1077
|
+
belongsToRoute,
|
|
1078
|
+
source: "parallel",
|
|
1079
|
+
defaultShouldRevalidate: true,
|
|
1080
|
+
finalShouldRevalidate: true,
|
|
1081
|
+
reason: "new-segment",
|
|
1082
|
+
});
|
|
1126
1083
|
}
|
|
1084
|
+
return true;
|
|
1127
1085
|
}
|
|
1128
1086
|
|
|
1129
|
-
|
|
1087
|
+
const dummySegment: ResolvedSegment = {
|
|
1130
1088
|
id: parallelId,
|
|
1131
1089
|
namespace: parallelEntry.id,
|
|
1132
1090
|
type: "parallel",
|
|
1133
1091
|
index: 0,
|
|
1134
|
-
component,
|
|
1135
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1136
|
-
transition: parallelEntry.transition,
|
|
1092
|
+
component: null as any,
|
|
1137
1093
|
params,
|
|
1138
1094
|
slot,
|
|
1139
1095
|
belongsToRoute,
|
|
@@ -1141,8 +1097,87 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1141
1097
|
...(parallelEntry.mountPath
|
|
1142
1098
|
? { mountPath: parallelEntry.mountPath }
|
|
1143
1099
|
: {}),
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
return await evaluateRevalidation({
|
|
1103
|
+
segment: dummySegment,
|
|
1104
|
+
prevParams,
|
|
1105
|
+
getPrevSegment: null,
|
|
1106
|
+
request,
|
|
1107
|
+
prevUrl,
|
|
1108
|
+
nextUrl,
|
|
1109
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1110
|
+
name: `revalidate${i}`,
|
|
1111
|
+
fn,
|
|
1112
|
+
})),
|
|
1113
|
+
routeKey,
|
|
1114
|
+
context,
|
|
1115
|
+
actionContext,
|
|
1116
|
+
stale,
|
|
1117
|
+
traceSource: "parallel",
|
|
1144
1118
|
});
|
|
1119
|
+
})();
|
|
1120
|
+
emitRevalidationDecision(
|
|
1121
|
+
parallelId,
|
|
1122
|
+
context.pathname,
|
|
1123
|
+
routeKey,
|
|
1124
|
+
shouldResolve,
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
let component: ReactNode | undefined;
|
|
1128
|
+
if (shouldResolve) {
|
|
1129
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1145
1130
|
}
|
|
1131
|
+
if (component === undefined) {
|
|
1132
|
+
const hasLoadingFallback =
|
|
1133
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1134
|
+
if (!shouldResolve) {
|
|
1135
|
+
component = null;
|
|
1136
|
+
} else if (handler === undefined) {
|
|
1137
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1138
|
+
component = null;
|
|
1139
|
+
} else if (hasLoadingFallback) {
|
|
1140
|
+
const result =
|
|
1141
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1142
|
+
if (result instanceof Promise) {
|
|
1143
|
+
const tracked = deps.trackHandler(result, {
|
|
1144
|
+
segmentId: parallelId,
|
|
1145
|
+
segmentType: "parallel",
|
|
1146
|
+
});
|
|
1147
|
+
observeStreamedHandler(
|
|
1148
|
+
tracked,
|
|
1149
|
+
parallelId,
|
|
1150
|
+
"parallel",
|
|
1151
|
+
context.pathname,
|
|
1152
|
+
routeKey,
|
|
1153
|
+
params,
|
|
1154
|
+
);
|
|
1155
|
+
component = tracked as ReactNode;
|
|
1156
|
+
} else {
|
|
1157
|
+
component = result as ReactNode;
|
|
1158
|
+
}
|
|
1159
|
+
} else {
|
|
1160
|
+
component =
|
|
1161
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
segments.push({
|
|
1166
|
+
id: parallelId,
|
|
1167
|
+
namespace: parallelEntry.id,
|
|
1168
|
+
type: "parallel",
|
|
1169
|
+
index: 0,
|
|
1170
|
+
component,
|
|
1171
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1172
|
+
transition: parallelEntry.transition,
|
|
1173
|
+
params,
|
|
1174
|
+
slot,
|
|
1175
|
+
belongsToRoute,
|
|
1176
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1177
|
+
...(parallelEntry.mountPath
|
|
1178
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1179
|
+
: {}),
|
|
1180
|
+
});
|
|
1146
1181
|
}
|
|
1147
1182
|
|
|
1148
1183
|
return { segments, matchedIds };
|