@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c
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/AGENTS.md +4 -0
- package/README.md +172 -50
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1160 -508
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +17 -16
- package/skills/breadcrumbs/SKILL.md +252 -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/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +61 -51
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +107 -24
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +185 -0
- package/skills/prerender/SKILL.md +112 -70
- package/skills/rango/SKILL.md +24 -23
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +58 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +38 -24
- package/src/__internal.ts +92 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +175 -17
- package/src/browser/navigation-client.ts +177 -44
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +113 -17
- package/src/browser/prefetch/cache.ts +275 -28
- package/src/browser/prefetch/fetch.ts +191 -46
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +98 -14
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/rsc-router.tsx +177 -66
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +73 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +67 -25
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +455 -15
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +2 -1
- package/src/client.tsx +85 -276
- 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 +9 -36
- package/src/index.ts +79 -70
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +57 -15
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- package/src/route-definition/helpers-types.ts +67 -19
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +129 -26
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +160 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -193
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +103 -18
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +48 -27
- package/src/router/middleware.ts +201 -86
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +77 -11
- 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 +215 -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 +454 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +30 -6
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +89 -17
- package/src/rsc/handler.ts +563 -364
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +37 -10
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +47 -44
- package/src/rsc/server-action.ts +24 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +11 -1
- package/src/search-params.ts +16 -13
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +174 -19
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +218 -65
- 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 +140 -72
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -5
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +7 -4
- package/src/vite/discovery/prerender-collection.ts +162 -88
- 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/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- 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 +190 -217
- package/src/vite/router-discovery.ts +241 -45
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +34 -1
- package/src/vite/utils/prerender-utils.ts +97 -5
- 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,95 @@ 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
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
324
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
325
|
+
if (
|
|
326
|
+
entry.type === "route" &&
|
|
327
|
+
entry.loader &&
|
|
328
|
+
entry.loader.length > 0 &&
|
|
329
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
330
|
+
) {
|
|
331
|
+
const inherited = await resolveLoadersWithRevalidation(
|
|
332
|
+
entry,
|
|
333
|
+
context,
|
|
334
|
+
childBelongsToRoute,
|
|
335
|
+
clientSegmentIds,
|
|
336
|
+
prevParams,
|
|
337
|
+
request,
|
|
338
|
+
prevUrl,
|
|
339
|
+
nextUrl,
|
|
340
|
+
routeKey,
|
|
341
|
+
deps,
|
|
342
|
+
actionContext,
|
|
343
|
+
layoutEntry.shortCode,
|
|
344
|
+
stale,
|
|
345
|
+
);
|
|
346
|
+
for (const seg of inherited.segments) {
|
|
347
|
+
if (!seenIds.has(seg.id)) {
|
|
348
|
+
seenIds.add(seg.id);
|
|
349
|
+
seg._inherited = true;
|
|
350
|
+
allLoaderSegments.push(seg);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
260
357
|
|
|
261
358
|
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);
|
|
359
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
280
360
|
}
|
|
281
361
|
|
|
282
362
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -300,22 +380,20 @@ export function buildEntryRevalidateMap(
|
|
|
300
380
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
301
381
|
|
|
302
382
|
if (entry.type !== "parallel") {
|
|
303
|
-
for (const parallelEntry of
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
}
|
|
383
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
384
|
+
entry.parallel,
|
|
385
|
+
)) {
|
|
386
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
387
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
388
|
+
map.set(parallelId, {
|
|
389
|
+
entry: parallelEntry,
|
|
390
|
+
revalidate: parallelEntry.revalidate,
|
|
391
|
+
});
|
|
314
392
|
}
|
|
315
393
|
}
|
|
316
394
|
|
|
317
395
|
for (const layoutEntry of entry.layout) {
|
|
318
|
-
processEntry(layoutEntry);
|
|
396
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
319
397
|
}
|
|
320
398
|
}
|
|
321
399
|
|
|
@@ -347,7 +425,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
347
425
|
const segments: ResolvedSegment[] = [];
|
|
348
426
|
const matchedIds: string[] = [];
|
|
349
427
|
|
|
350
|
-
|
|
428
|
+
const resolvedParallelEntries = new Set<string>();
|
|
429
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
430
|
+
entry.parallel,
|
|
431
|
+
)) {
|
|
351
432
|
invariant(
|
|
352
433
|
parallelEntry.type === "parallel",
|
|
353
434
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -358,141 +439,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
358
439
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
359
440
|
| ReactNode
|
|
360
441
|
>;
|
|
442
|
+
// In production, static handler bodies are evicted and the slot value
|
|
443
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
444
|
+
// We defer the handler check until after tryStaticSlot.
|
|
445
|
+
const handler = slots[slot];
|
|
446
|
+
|
|
447
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
448
|
+
|
|
449
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
450
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
451
|
+
if (
|
|
452
|
+
isFullRefetch ||
|
|
453
|
+
clientSegmentIds.has(parallelId) ||
|
|
454
|
+
belongsToRoute ||
|
|
455
|
+
isNewParent
|
|
456
|
+
) {
|
|
457
|
+
matchedIds.push(parallelId);
|
|
458
|
+
}
|
|
361
459
|
|
|
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;
|
|
460
|
+
const shouldResolve = await (async () => {
|
|
461
|
+
if (isFullRefetch) {
|
|
462
|
+
if (isTraceActive()) {
|
|
463
|
+
pushRevalidationTraceEntry({
|
|
464
|
+
segmentId: parallelId,
|
|
465
|
+
segmentType: "parallel",
|
|
466
|
+
belongsToRoute,
|
|
467
|
+
source: "parallel",
|
|
468
|
+
defaultShouldRevalidate: true,
|
|
469
|
+
finalShouldRevalidate: true,
|
|
470
|
+
reason: "full-refetch",
|
|
471
|
+
});
|
|
410
472
|
}
|
|
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);
|
|
473
|
+
return true;
|
|
455
474
|
}
|
|
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;
|
|
475
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
476
|
+
const result = belongsToRoute || isNewParent;
|
|
477
|
+
if (isTraceActive()) {
|
|
478
|
+
pushRevalidationTraceEntry({
|
|
479
|
+
segmentId: parallelId,
|
|
480
|
+
segmentType: "parallel",
|
|
481
|
+
belongsToRoute,
|
|
482
|
+
source: "parallel",
|
|
483
|
+
defaultShouldRevalidate: result,
|
|
484
|
+
finalShouldRevalidate: result,
|
|
485
|
+
reason: result ? "new-segment" : "skip-parent-chain",
|
|
486
|
+
});
|
|
485
487
|
}
|
|
488
|
+
return result;
|
|
486
489
|
}
|
|
487
490
|
|
|
488
|
-
|
|
491
|
+
const dummySegment: ResolvedSegment = {
|
|
489
492
|
id: parallelId,
|
|
490
493
|
namespace: parallelEntry.id,
|
|
491
494
|
type: "parallel",
|
|
492
495
|
index: 0,
|
|
493
|
-
component,
|
|
494
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
495
|
-
transition: parallelEntry.transition,
|
|
496
|
+
component: null as any,
|
|
496
497
|
params,
|
|
497
498
|
slot,
|
|
498
499
|
belongsToRoute,
|
|
@@ -500,28 +501,111 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
500
501
|
...(parallelEntry.mountPath
|
|
501
502
|
? { mountPath: parallelEntry.mountPath }
|
|
502
503
|
: {}),
|
|
503
|
-
}
|
|
504
|
-
}
|
|
504
|
+
};
|
|
505
505
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
parallelEntry,
|
|
509
|
-
context,
|
|
510
|
-
belongsToRoute,
|
|
511
|
-
clientSegmentIds,
|
|
506
|
+
return await evaluateRevalidation({
|
|
507
|
+
segment: dummySegment,
|
|
512
508
|
prevParams,
|
|
509
|
+
getPrevSegment: null,
|
|
513
510
|
request,
|
|
514
511
|
prevUrl,
|
|
515
512
|
nextUrl,
|
|
513
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
514
|
+
name: `revalidate${i}`,
|
|
515
|
+
fn,
|
|
516
|
+
})),
|
|
516
517
|
routeKey,
|
|
517
|
-
|
|
518
|
+
context,
|
|
518
519
|
actionContext,
|
|
519
|
-
entry.shortCode,
|
|
520
520
|
stale,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
521
|
+
traceSource: "parallel",
|
|
522
|
+
});
|
|
523
|
+
})();
|
|
524
|
+
emitRevalidationDecision(
|
|
525
|
+
parallelId,
|
|
526
|
+
context.pathname,
|
|
527
|
+
routeKey,
|
|
528
|
+
shouldResolve,
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
let component: ReactNode | undefined;
|
|
532
|
+
if (shouldResolve) {
|
|
533
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
524
534
|
}
|
|
535
|
+
if (component === undefined) {
|
|
536
|
+
const hasLoadingFallback =
|
|
537
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
538
|
+
if (!shouldResolve) {
|
|
539
|
+
component = null;
|
|
540
|
+
} else if (handler === undefined) {
|
|
541
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
542
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
543
|
+
component = null;
|
|
544
|
+
} else if (hasLoadingFallback) {
|
|
545
|
+
const result =
|
|
546
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
547
|
+
if (result instanceof Promise) {
|
|
548
|
+
const tracked = deps.trackHandler(result, {
|
|
549
|
+
segmentId: parallelId,
|
|
550
|
+
segmentType: "parallel",
|
|
551
|
+
});
|
|
552
|
+
observeStreamedHandler(
|
|
553
|
+
tracked,
|
|
554
|
+
parallelId,
|
|
555
|
+
"parallel",
|
|
556
|
+
context.pathname,
|
|
557
|
+
routeKey,
|
|
558
|
+
params,
|
|
559
|
+
);
|
|
560
|
+
component = tracked as ReactNode;
|
|
561
|
+
} else {
|
|
562
|
+
component = result as ReactNode;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
component =
|
|
566
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
segments.push({
|
|
571
|
+
id: parallelId,
|
|
572
|
+
namespace: parallelEntry.id,
|
|
573
|
+
type: "parallel",
|
|
574
|
+
index: 0,
|
|
575
|
+
component,
|
|
576
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
577
|
+
transition: parallelEntry.transition,
|
|
578
|
+
params,
|
|
579
|
+
slot,
|
|
580
|
+
belongsToRoute,
|
|
581
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
582
|
+
...(parallelEntry.mountPath
|
|
583
|
+
? { mountPath: parallelEntry.mountPath }
|
|
584
|
+
: {}),
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
592
|
+
parallelEntry,
|
|
593
|
+
context,
|
|
594
|
+
belongsToRoute,
|
|
595
|
+
clientSegmentIds,
|
|
596
|
+
prevParams,
|
|
597
|
+
request,
|
|
598
|
+
prevUrl,
|
|
599
|
+
nextUrl,
|
|
600
|
+
routeKey,
|
|
601
|
+
deps,
|
|
602
|
+
actionContext,
|
|
603
|
+
entry.shortCode,
|
|
604
|
+
stale,
|
|
605
|
+
);
|
|
606
|
+
segments.push(...loaderResult.segments);
|
|
607
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
608
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
525
609
|
}
|
|
526
610
|
|
|
527
611
|
return { segments, matchedIds };
|
|
@@ -607,6 +691,8 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
607
691
|
context,
|
|
608
692
|
actionContext,
|
|
609
693
|
stale,
|
|
694
|
+
traceSource:
|
|
695
|
+
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
610
696
|
});
|
|
611
697
|
emitRevalidationDecision(
|
|
612
698
|
entry.shortCode,
|
|
@@ -621,20 +707,36 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
621
707
|
return shouldRevalidate;
|
|
622
708
|
},
|
|
623
709
|
async () => {
|
|
710
|
+
const doneHandler = track(`handler:${entry.id}`, 2);
|
|
624
711
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
625
712
|
entry.shortCode;
|
|
626
713
|
if (entry.type === "layout" || entry.type === "cache") {
|
|
627
|
-
|
|
714
|
+
const layoutComponent = await resolveLayoutComponent(entry, context);
|
|
715
|
+
doneHandler();
|
|
716
|
+
return layoutComponent;
|
|
628
717
|
}
|
|
629
718
|
const staticComponent = await tryStaticHandler(entry, entry.shortCode);
|
|
630
|
-
if (staticComponent !== undefined)
|
|
719
|
+
if (staticComponent !== undefined) {
|
|
720
|
+
doneHandler();
|
|
721
|
+
return staticComponent;
|
|
722
|
+
}
|
|
631
723
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
724
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
725
|
+
// the build handler. At build time (context.build === true), always
|
|
726
|
+
// use the build handler from routeEntry.handler.
|
|
727
|
+
const handler =
|
|
728
|
+
!context.build && routeEntry.liveHandler
|
|
729
|
+
? routeEntry.liveHandler
|
|
730
|
+
: routeEntry.handler;
|
|
632
731
|
if (!routeEntry.loading) {
|
|
633
|
-
|
|
732
|
+
const result = handleHandlerResult(await handler(context));
|
|
733
|
+
doneHandler();
|
|
734
|
+
return result;
|
|
634
735
|
}
|
|
635
736
|
if (!actionContext) {
|
|
636
|
-
const result = handleHandlerResult(
|
|
737
|
+
const result = handleHandlerResult(handler(context));
|
|
637
738
|
if (result instanceof Promise) {
|
|
739
|
+
result.finally(doneHandler).catch(() => {});
|
|
638
740
|
const tracked = deps.trackHandler(result, {
|
|
639
741
|
segmentId: entry.shortCode,
|
|
640
742
|
segmentType: entry.type,
|
|
@@ -649,24 +751,27 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
649
751
|
);
|
|
650
752
|
return { content: tracked };
|
|
651
753
|
}
|
|
754
|
+
doneHandler();
|
|
652
755
|
return { content: result };
|
|
653
756
|
}
|
|
654
757
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
655
758
|
entryId: entry.id,
|
|
656
759
|
});
|
|
760
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
761
|
+
doneHandler();
|
|
657
762
|
return {
|
|
658
|
-
content: Promise.resolve(
|
|
659
|
-
handleHandlerResult(await routeEntry.handler(context)),
|
|
660
|
-
),
|
|
763
|
+
content: Promise.resolve(actionResult),
|
|
661
764
|
};
|
|
662
765
|
},
|
|
663
766
|
() => null,
|
|
664
767
|
);
|
|
665
768
|
|
|
769
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
770
|
+
// component === null checks work consistently for both void and explicit null.
|
|
666
771
|
const resolvedComponent =
|
|
667
772
|
component && typeof component === "object" && "content" in component
|
|
668
|
-
? (component as { content: ReactNode }).content
|
|
669
|
-
: component;
|
|
773
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
774
|
+
: (component ?? null);
|
|
670
775
|
|
|
671
776
|
const segment: ResolvedSegment = {
|
|
672
777
|
id: entry.shortCode,
|
|
@@ -768,6 +873,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
768
873
|
deps,
|
|
769
874
|
actionContext,
|
|
770
875
|
stale,
|
|
876
|
+
entry,
|
|
771
877
|
);
|
|
772
878
|
segments.push(...orphanResult.segments);
|
|
773
879
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -879,6 +985,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
879
985
|
deps: SegmentResolutionDeps<TEnv>,
|
|
880
986
|
actionContext?: ActionContext,
|
|
881
987
|
stale?: boolean,
|
|
988
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
989
|
+
parentRouteEntry?: EntryData,
|
|
882
990
|
): Promise<SegmentRevalidationResult> {
|
|
883
991
|
invariant(
|
|
884
992
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -906,6 +1014,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
906
1014
|
segments.push(...loaderResult.segments);
|
|
907
1015
|
matchedIds.push(...loaderResult.matchedIds);
|
|
908
1016
|
|
|
1017
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1018
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1019
|
+
if (
|
|
1020
|
+
parentRouteEntry &&
|
|
1021
|
+
parentRouteEntry.loader &&
|
|
1022
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1023
|
+
Object.keys(orphan.parallel).length > 0
|
|
1024
|
+
) {
|
|
1025
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1026
|
+
parentRouteEntry,
|
|
1027
|
+
context,
|
|
1028
|
+
belongsToRoute,
|
|
1029
|
+
clientSegmentIds,
|
|
1030
|
+
prevParams,
|
|
1031
|
+
request,
|
|
1032
|
+
prevUrl,
|
|
1033
|
+
nextUrl,
|
|
1034
|
+
routeKey,
|
|
1035
|
+
deps,
|
|
1036
|
+
actionContext,
|
|
1037
|
+
orphan.shortCode,
|
|
1038
|
+
stale,
|
|
1039
|
+
);
|
|
1040
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1041
|
+
for (const s of inheritedResult.segments) {
|
|
1042
|
+
s._inherited = true;
|
|
1043
|
+
}
|
|
1044
|
+
segments.push(...inheritedResult.segments);
|
|
1045
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
909
1048
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
910
1049
|
// so ctx.set() values are visible to parallel children.
|
|
911
1050
|
matchedIds.push(orphan.shortCode);
|
|
@@ -982,143 +1121,73 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
982
1121
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
983
1122
|
});
|
|
984
1123
|
|
|
985
|
-
|
|
1124
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1125
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1126
|
+
orphan.parallel,
|
|
1127
|
+
)) {
|
|
986
1128
|
invariant(
|
|
987
1129
|
parallelEntry.type === "parallel",
|
|
988
1130
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
989
1131
|
);
|
|
990
1132
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1133
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1134
|
+
// shortCodeOverride must match the parent layout, not the parallel entry.
|
|
1135
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1136
|
+
parallelEntry,
|
|
1137
|
+
context,
|
|
1138
|
+
belongsToRoute,
|
|
1139
|
+
clientSegmentIds,
|
|
1140
|
+
prevParams,
|
|
1141
|
+
request,
|
|
1142
|
+
prevUrl,
|
|
1143
|
+
nextUrl,
|
|
1144
|
+
routeKey,
|
|
1145
|
+
deps,
|
|
1146
|
+
actionContext,
|
|
1147
|
+
orphan.shortCode,
|
|
1148
|
+
stale,
|
|
1149
|
+
);
|
|
1150
|
+
segments.push(...loaderResult.segments);
|
|
1151
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1152
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1153
|
+
}
|
|
1008
1154
|
|
|
1009
1155
|
const slots = parallelEntry.handler as Record<
|
|
1010
1156
|
`@${string}`,
|
|
1011
1157
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1012
1158
|
| ReactNode
|
|
1013
1159
|
>;
|
|
1160
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1161
|
+
const handler = slots[slot];
|
|
1014
1162
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
matchedIds.push(parallelId);
|
|
1163
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1164
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1165
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1166
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1167
|
+
matchedIds.push(parallelId);
|
|
1021
1168
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
);
|
|
1077
|
-
|
|
1078
|
-
let component: ReactNode | undefined;
|
|
1079
|
-
if (shouldResolve) {
|
|
1080
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1081
|
-
}
|
|
1082
|
-
if (component === undefined) {
|
|
1083
|
-
const hasLoadingFallback =
|
|
1084
|
-
parallelEntry.loading !== undefined &&
|
|
1085
|
-
parallelEntry.loading !== false;
|
|
1086
|
-
if (!shouldResolve) {
|
|
1087
|
-
component = null;
|
|
1088
|
-
} else if (hasLoadingFallback) {
|
|
1089
|
-
const result =
|
|
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;
|
|
1169
|
+
const shouldResolve = await (async () => {
|
|
1170
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
1171
|
+
if (isTraceActive()) {
|
|
1172
|
+
pushRevalidationTraceEntry({
|
|
1173
|
+
segmentId: parallelId,
|
|
1174
|
+
segmentType: "parallel",
|
|
1175
|
+
belongsToRoute,
|
|
1176
|
+
source: "parallel",
|
|
1177
|
+
defaultShouldRevalidate: true,
|
|
1178
|
+
finalShouldRevalidate: true,
|
|
1179
|
+
reason: "new-segment",
|
|
1180
|
+
});
|
|
1111
1181
|
}
|
|
1182
|
+
return true;
|
|
1112
1183
|
}
|
|
1113
1184
|
|
|
1114
|
-
|
|
1185
|
+
const dummySegment: ResolvedSegment = {
|
|
1115
1186
|
id: parallelId,
|
|
1116
1187
|
namespace: parallelEntry.id,
|
|
1117
1188
|
type: "parallel",
|
|
1118
1189
|
index: 0,
|
|
1119
|
-
component,
|
|
1120
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1121
|
-
transition: parallelEntry.transition,
|
|
1190
|
+
component: null as any,
|
|
1122
1191
|
params,
|
|
1123
1192
|
slot,
|
|
1124
1193
|
belongsToRoute,
|
|
@@ -1126,8 +1195,87 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1126
1195
|
...(parallelEntry.mountPath
|
|
1127
1196
|
? { mountPath: parallelEntry.mountPath }
|
|
1128
1197
|
: {}),
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
return await evaluateRevalidation({
|
|
1201
|
+
segment: dummySegment,
|
|
1202
|
+
prevParams,
|
|
1203
|
+
getPrevSegment: null,
|
|
1204
|
+
request,
|
|
1205
|
+
prevUrl,
|
|
1206
|
+
nextUrl,
|
|
1207
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1208
|
+
name: `revalidate${i}`,
|
|
1209
|
+
fn,
|
|
1210
|
+
})),
|
|
1211
|
+
routeKey,
|
|
1212
|
+
context,
|
|
1213
|
+
actionContext,
|
|
1214
|
+
stale,
|
|
1215
|
+
traceSource: "parallel",
|
|
1129
1216
|
});
|
|
1217
|
+
})();
|
|
1218
|
+
emitRevalidationDecision(
|
|
1219
|
+
parallelId,
|
|
1220
|
+
context.pathname,
|
|
1221
|
+
routeKey,
|
|
1222
|
+
shouldResolve,
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
let component: ReactNode | undefined;
|
|
1226
|
+
if (shouldResolve) {
|
|
1227
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1228
|
+
}
|
|
1229
|
+
if (component === undefined) {
|
|
1230
|
+
const hasLoadingFallback =
|
|
1231
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1232
|
+
if (!shouldResolve) {
|
|
1233
|
+
component = null;
|
|
1234
|
+
} else if (handler === undefined) {
|
|
1235
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1236
|
+
component = null;
|
|
1237
|
+
} else if (hasLoadingFallback) {
|
|
1238
|
+
const result =
|
|
1239
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1240
|
+
if (result instanceof Promise) {
|
|
1241
|
+
const tracked = deps.trackHandler(result, {
|
|
1242
|
+
segmentId: parallelId,
|
|
1243
|
+
segmentType: "parallel",
|
|
1244
|
+
});
|
|
1245
|
+
observeStreamedHandler(
|
|
1246
|
+
tracked,
|
|
1247
|
+
parallelId,
|
|
1248
|
+
"parallel",
|
|
1249
|
+
context.pathname,
|
|
1250
|
+
routeKey,
|
|
1251
|
+
params,
|
|
1252
|
+
);
|
|
1253
|
+
component = tracked as ReactNode;
|
|
1254
|
+
} else {
|
|
1255
|
+
component = result as ReactNode;
|
|
1256
|
+
}
|
|
1257
|
+
} else {
|
|
1258
|
+
component =
|
|
1259
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
1260
|
+
}
|
|
1130
1261
|
}
|
|
1262
|
+
|
|
1263
|
+
segments.push({
|
|
1264
|
+
id: parallelId,
|
|
1265
|
+
namespace: parallelEntry.id,
|
|
1266
|
+
type: "parallel",
|
|
1267
|
+
index: 0,
|
|
1268
|
+
component,
|
|
1269
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1270
|
+
transition: parallelEntry.transition,
|
|
1271
|
+
params,
|
|
1272
|
+
slot,
|
|
1273
|
+
belongsToRoute,
|
|
1274
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1275
|
+
...(parallelEntry.mountPath
|
|
1276
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1277
|
+
: {}),
|
|
1278
|
+
});
|
|
1131
1279
|
}
|
|
1132
1280
|
|
|
1133
1281
|
return { segments, matchedIds };
|
|
@@ -1152,6 +1300,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1152
1300
|
localRouteName: string,
|
|
1153
1301
|
pathname: string,
|
|
1154
1302
|
deps: SegmentResolutionDeps<TEnv>,
|
|
1303
|
+
stale?: boolean,
|
|
1155
1304
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1156
1305
|
const allSegments: ResolvedSegment[] = [];
|
|
1157
1306
|
const matchedIds: string[] = [];
|
|
@@ -1178,6 +1327,11 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1178
1327
|
}
|
|
1179
1328
|
|
|
1180
1329
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1330
|
+
if (entry.type === "cache") {
|
|
1331
|
+
const store = RSCRouterContext.getStore();
|
|
1332
|
+
if (store) store.insideCacheScope = true;
|
|
1333
|
+
}
|
|
1334
|
+
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1181
1335
|
const resolved = await resolveWithErrorBoundary(
|
|
1182
1336
|
nonParallelEntry,
|
|
1183
1337
|
params,
|
|
@@ -1195,15 +1349,14 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1195
1349
|
loaderPromises,
|
|
1196
1350
|
deps,
|
|
1197
1351
|
actionContext,
|
|
1198
|
-
|
|
1352
|
+
stale,
|
|
1199
1353
|
),
|
|
1200
1354
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
1201
1355
|
deps,
|
|
1202
|
-
telemetry
|
|
1203
|
-
? { request, url: context.url, routeKey, isPartial: true, telemetry }
|
|
1204
|
-
: undefined,
|
|
1356
|
+
{ request, url: context.url, routeKey, isPartial: true, telemetry },
|
|
1205
1357
|
pathname,
|
|
1206
1358
|
);
|
|
1359
|
+
doneEntry();
|
|
1207
1360
|
|
|
1208
1361
|
// Deduplicate segments and matchedIds by ID, matching resolveAllSegments.
|
|
1209
1362
|
// include() scopes can produce entries that resolve the same shared
|