@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- 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 +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -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 +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +188 -93
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -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,
|
|
@@ -35,9 +39,14 @@ import {
|
|
|
35
39
|
resolveLayoutComponent,
|
|
36
40
|
resolveWithErrorBoundary,
|
|
37
41
|
} from "./helpers.js";
|
|
42
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
38
43
|
import { getRouterContext } from "../router-context.js";
|
|
39
44
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
40
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
track,
|
|
47
|
+
RangoContext,
|
|
48
|
+
runInsideLoaderScope,
|
|
49
|
+
} from "../../server/context.js";
|
|
41
50
|
|
|
42
51
|
// ---------------------------------------------------------------------------
|
|
43
52
|
// Telemetry helpers
|
|
@@ -81,6 +90,27 @@ function observeStreamedHandler(
|
|
|
81
90
|
});
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Trace a parallel slot that's being force-rendered on a full refetch (client
|
|
95
|
+
* has no cached state). User revalidate fns are bypassed in this case — see
|
|
96
|
+
* the call sites for the load-bearing rationale.
|
|
97
|
+
*/
|
|
98
|
+
function traceFullRefetchedParallelSlot(
|
|
99
|
+
parallelId: string,
|
|
100
|
+
belongsToRoute: boolean,
|
|
101
|
+
): void {
|
|
102
|
+
if (!isTraceActive()) return;
|
|
103
|
+
pushRevalidationTraceEntry({
|
|
104
|
+
segmentId: parallelId,
|
|
105
|
+
segmentType: "parallel",
|
|
106
|
+
belongsToRoute,
|
|
107
|
+
source: "parallel",
|
|
108
|
+
defaultShouldRevalidate: true,
|
|
109
|
+
finalShouldRevalidate: true,
|
|
110
|
+
reason: "full-refetch",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
84
114
|
// ---------------------------------------------------------------------------
|
|
85
115
|
// Revalidation telemetry helper
|
|
86
116
|
// ---------------------------------------------------------------------------
|
|
@@ -228,7 +258,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
228
258
|
params: ctx.params,
|
|
229
259
|
loaderId: loader.$$id,
|
|
230
260
|
loaderData: deps.wrapLoaderPromise(
|
|
231
|
-
|
|
261
|
+
runInsideLoaderScope(() =>
|
|
262
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
263
|
+
),
|
|
232
264
|
entry,
|
|
233
265
|
segmentId,
|
|
234
266
|
ctx.pathname,
|
|
@@ -258,26 +290,95 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
258
290
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
259
291
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
260
292
|
const allMatchedIds: string[] = [];
|
|
293
|
+
const seenIds = new Set<string>();
|
|
294
|
+
|
|
295
|
+
async function collectEntryLoaders(
|
|
296
|
+
entry: EntryData,
|
|
297
|
+
belongsToRoute: boolean,
|
|
298
|
+
shortCodeOverride?: string,
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
// Skip if all loaders from this entry have already been resolved
|
|
301
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
302
|
+
const loaderEntries = entry.loader ?? [];
|
|
303
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
304
|
+
const allAlreadySeen =
|
|
305
|
+
loaderEntries.length > 0 &&
|
|
306
|
+
loaderEntries.every((le, i) =>
|
|
307
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
308
|
+
);
|
|
309
|
+
if (!allAlreadySeen) {
|
|
310
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
311
|
+
entry,
|
|
312
|
+
context,
|
|
313
|
+
belongsToRoute,
|
|
314
|
+
clientSegmentIds,
|
|
315
|
+
prevParams,
|
|
316
|
+
request,
|
|
317
|
+
prevUrl,
|
|
318
|
+
nextUrl,
|
|
319
|
+
routeKey,
|
|
320
|
+
deps,
|
|
321
|
+
actionContext,
|
|
322
|
+
shortCodeOverride,
|
|
323
|
+
stale,
|
|
324
|
+
);
|
|
325
|
+
for (const seg of segments) {
|
|
326
|
+
if (!seenIds.has(seg.id)) {
|
|
327
|
+
seenIds.add(seg.id);
|
|
328
|
+
allLoaderSegments.push(seg);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
allMatchedIds.push(...matchedIds);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const seenParallelEntryIds = new Set<string>();
|
|
335
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
336
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
337
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
338
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
342
|
+
for (const layoutEntry of entry.layout) {
|
|
343
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
344
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
345
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
346
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
347
|
+
if (
|
|
348
|
+
entry.type === "route" &&
|
|
349
|
+
entry.loader &&
|
|
350
|
+
entry.loader.length > 0 &&
|
|
351
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
352
|
+
) {
|
|
353
|
+
const inherited = await resolveLoadersWithRevalidation(
|
|
354
|
+
entry,
|
|
355
|
+
context,
|
|
356
|
+
childBelongsToRoute,
|
|
357
|
+
clientSegmentIds,
|
|
358
|
+
prevParams,
|
|
359
|
+
request,
|
|
360
|
+
prevUrl,
|
|
361
|
+
nextUrl,
|
|
362
|
+
routeKey,
|
|
363
|
+
deps,
|
|
364
|
+
actionContext,
|
|
365
|
+
layoutEntry.shortCode,
|
|
366
|
+
stale,
|
|
367
|
+
);
|
|
368
|
+
for (const seg of inherited.segments) {
|
|
369
|
+
if (!seenIds.has(seg.id)) {
|
|
370
|
+
seenIds.add(seg.id);
|
|
371
|
+
seg._inherited = true;
|
|
372
|
+
allLoaderSegments.push(seg);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
261
379
|
|
|
262
380
|
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);
|
|
381
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
281
382
|
}
|
|
282
383
|
|
|
283
384
|
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
@@ -301,22 +402,20 @@ export function buildEntryRevalidateMap(
|
|
|
301
402
|
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
302
403
|
|
|
303
404
|
if (entry.type !== "parallel") {
|
|
304
|
-
for (const parallelEntry of
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}
|
|
405
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
406
|
+
entry.parallel,
|
|
407
|
+
)) {
|
|
408
|
+
const parallelParentShortCode = parentShortCode ?? entry.shortCode;
|
|
409
|
+
const parallelId = `${parallelParentShortCode}.${slot}`;
|
|
410
|
+
map.set(parallelId, {
|
|
411
|
+
entry: parallelEntry,
|
|
412
|
+
revalidate: parallelEntry.revalidate,
|
|
413
|
+
});
|
|
315
414
|
}
|
|
316
415
|
}
|
|
317
416
|
|
|
318
417
|
for (const layoutEntry of entry.layout) {
|
|
319
|
-
processEntry(layoutEntry);
|
|
418
|
+
processEntry(layoutEntry, entry.shortCode);
|
|
320
419
|
}
|
|
321
420
|
}
|
|
322
421
|
|
|
@@ -348,7 +447,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
348
447
|
const segments: ResolvedSegment[] = [];
|
|
349
448
|
const matchedIds: string[] = [];
|
|
350
449
|
|
|
351
|
-
|
|
450
|
+
const resolvedParallelEntries = new Set<string>();
|
|
451
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
452
|
+
entry.parallel,
|
|
453
|
+
)) {
|
|
352
454
|
invariant(
|
|
353
455
|
parallelEntry.type === "parallel",
|
|
354
456
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
@@ -359,108 +461,106 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
359
461
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
360
462
|
| ReactNode
|
|
361
463
|
>;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
464
|
+
// In production, static handler bodies are evicted and the slot value
|
|
465
|
+
// may be undefined. The static store holds the pre-rendered component.
|
|
466
|
+
// We defer the handler check until after tryStaticSlot.
|
|
467
|
+
const handler = slots[slot];
|
|
468
|
+
|
|
469
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
470
|
+
|
|
471
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
472
|
+
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
473
|
+
// Always announce the slot in matchedIds — it's unconditionally appended
|
|
474
|
+
// to `segments` below, and a segment present in segments but missing from
|
|
475
|
+
// matched lets the client prune it (then it's missing from clientSegmentIds
|
|
476
|
+
// on the next request, perpetuating the staleness).
|
|
477
|
+
matchedIds.push(parallelId);
|
|
478
|
+
|
|
479
|
+
let shouldResolve: boolean;
|
|
480
|
+
if (isFullRefetch) {
|
|
481
|
+
// Client has nothing cached — slot MUST render. User revalidate fns are
|
|
482
|
+
// bypassed here because returning false would leave the segment blank
|
|
483
|
+
// with no client-side fallback.
|
|
484
|
+
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
485
|
+
shouldResolve = true;
|
|
486
|
+
} else {
|
|
487
|
+
// For non-empty client sets, consult user revalidate fns. When the slot
|
|
488
|
+
// is unknown to the client, override the type-derived default so the
|
|
489
|
+
// soft chain seeds with the right "new segment" / "parent-chain" value.
|
|
490
|
+
let defaultOverride: { value: boolean; reason: string } | undefined;
|
|
491
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
492
|
+
const value = belongsToRoute || isNewParent;
|
|
493
|
+
defaultOverride = {
|
|
494
|
+
value,
|
|
495
|
+
reason: value ? "new-segment" : "skip-parent-chain",
|
|
496
|
+
};
|
|
380
497
|
}
|
|
381
498
|
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
398
|
-
const result = belongsToRoute || isNewParent;
|
|
399
|
-
if (isTraceActive()) {
|
|
400
|
-
pushRevalidationTraceEntry({
|
|
401
|
-
segmentId: parallelId,
|
|
402
|
-
segmentType: "parallel",
|
|
403
|
-
belongsToRoute,
|
|
404
|
-
source: "parallel",
|
|
405
|
-
defaultShouldRevalidate: result,
|
|
406
|
-
finalShouldRevalidate: result,
|
|
407
|
-
reason: result ? "new-segment" : "skip-parent-chain",
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
return result;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const dummySegment: ResolvedSegment = {
|
|
414
|
-
id: parallelId,
|
|
415
|
-
namespace: parallelEntry.id,
|
|
416
|
-
type: "parallel",
|
|
417
|
-
index: 0,
|
|
418
|
-
component: null as any,
|
|
419
|
-
params,
|
|
420
|
-
slot,
|
|
421
|
-
belongsToRoute,
|
|
422
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
423
|
-
...(parallelEntry.mountPath
|
|
424
|
-
? { mountPath: parallelEntry.mountPath }
|
|
425
|
-
: {}),
|
|
426
|
-
};
|
|
499
|
+
const dummySegment: ResolvedSegment = {
|
|
500
|
+
id: parallelId,
|
|
501
|
+
namespace: parallelEntry.id,
|
|
502
|
+
type: "parallel",
|
|
503
|
+
index: 0,
|
|
504
|
+
component: null as any,
|
|
505
|
+
params,
|
|
506
|
+
slot,
|
|
507
|
+
belongsToRoute,
|
|
508
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
509
|
+
...(parallelEntry.mountPath
|
|
510
|
+
? { mountPath: parallelEntry.mountPath }
|
|
511
|
+
: {}),
|
|
512
|
+
};
|
|
427
513
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
routeKey,
|
|
440
|
-
context,
|
|
441
|
-
actionContext,
|
|
442
|
-
stale,
|
|
443
|
-
traceSource: "parallel",
|
|
444
|
-
});
|
|
445
|
-
})();
|
|
446
|
-
emitRevalidationDecision(
|
|
447
|
-
parallelId,
|
|
448
|
-
context.pathname,
|
|
514
|
+
shouldResolve = await evaluateRevalidation({
|
|
515
|
+
segment: dummySegment,
|
|
516
|
+
prevParams,
|
|
517
|
+
getPrevSegment: null,
|
|
518
|
+
request,
|
|
519
|
+
prevUrl,
|
|
520
|
+
nextUrl,
|
|
521
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
522
|
+
name: `revalidate${i}`,
|
|
523
|
+
fn,
|
|
524
|
+
})),
|
|
449
525
|
routeKey,
|
|
450
|
-
|
|
451
|
-
|
|
526
|
+
context,
|
|
527
|
+
actionContext,
|
|
528
|
+
stale,
|
|
529
|
+
traceSource: "parallel",
|
|
530
|
+
defaultOverride,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
emitRevalidationDecision(
|
|
534
|
+
parallelId,
|
|
535
|
+
context.pathname,
|
|
536
|
+
routeKey,
|
|
537
|
+
shouldResolve,
|
|
538
|
+
);
|
|
452
539
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
540
|
+
let component: ReactNode | undefined;
|
|
541
|
+
let handlerRan = false;
|
|
542
|
+
if (shouldResolve) {
|
|
543
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
544
|
+
// tryStaticSlot returning a value means the static cache supplied the
|
|
545
|
+
// component — handler did NOT run. handlerRan stays false.
|
|
546
|
+
}
|
|
547
|
+
if (component === undefined) {
|
|
548
|
+
const hasLoadingFallback =
|
|
549
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
550
|
+
if (!shouldResolve) {
|
|
551
|
+
component = null;
|
|
552
|
+
} else if (handler === undefined) {
|
|
553
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
554
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
555
|
+
component = null;
|
|
556
|
+
} else {
|
|
557
|
+
// Slot-keyed pushes — slot owns its own bucket, parent layout owns
|
|
558
|
+
// its own. On slot-only revalidations the partial merge updates only
|
|
559
|
+
// the slot's bucket; the parent's bucket stays intact.
|
|
560
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
561
|
+
parallelId;
|
|
562
|
+
handlerRan = true;
|
|
563
|
+
if (hasLoadingFallback) {
|
|
464
564
|
const result =
|
|
465
565
|
typeof handler === "function" ? handler(context) : handler;
|
|
466
566
|
if (result instanceof Promise) {
|
|
@@ -485,44 +585,51 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
485
585
|
typeof handler === "function" ? await handler(context) : handler;
|
|
486
586
|
}
|
|
487
587
|
}
|
|
488
|
-
|
|
489
|
-
segments.push({
|
|
490
|
-
id: parallelId,
|
|
491
|
-
namespace: parallelEntry.id,
|
|
492
|
-
type: "parallel",
|
|
493
|
-
index: 0,
|
|
494
|
-
component,
|
|
495
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
496
|
-
transition: parallelEntry.transition,
|
|
497
|
-
params,
|
|
498
|
-
slot,
|
|
499
|
-
belongsToRoute,
|
|
500
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
501
|
-
...(parallelEntry.mountPath
|
|
502
|
-
? { mountPath: parallelEntry.mountPath }
|
|
503
|
-
: {}),
|
|
504
|
-
});
|
|
505
588
|
}
|
|
506
589
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
590
|
+
segments.push({
|
|
591
|
+
id: parallelId,
|
|
592
|
+
namespace: parallelEntry.id,
|
|
593
|
+
type: "parallel",
|
|
594
|
+
index: 0,
|
|
595
|
+
component,
|
|
596
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
597
|
+
transition: applyViewTransitionDefault(
|
|
598
|
+
parallelEntry.transition,
|
|
599
|
+
deps.viewTransitionDefault,
|
|
600
|
+
),
|
|
601
|
+
params,
|
|
602
|
+
slot,
|
|
603
|
+
_handlerRan: handlerRan,
|
|
604
|
+
belongsToRoute,
|
|
605
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
606
|
+
...(parallelEntry.mountPath
|
|
607
|
+
? { mountPath: parallelEntry.mountPath }
|
|
608
|
+
: {}),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) {
|
|
612
|
+
continue;
|
|
525
613
|
}
|
|
614
|
+
|
|
615
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
616
|
+
parallelEntry,
|
|
617
|
+
context,
|
|
618
|
+
belongsToRoute,
|
|
619
|
+
clientSegmentIds,
|
|
620
|
+
prevParams,
|
|
621
|
+
request,
|
|
622
|
+
prevUrl,
|
|
623
|
+
nextUrl,
|
|
624
|
+
routeKey,
|
|
625
|
+
deps,
|
|
626
|
+
actionContext,
|
|
627
|
+
entry.shortCode,
|
|
628
|
+
stale,
|
|
629
|
+
);
|
|
630
|
+
segments.push(...loaderResult.segments);
|
|
631
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
632
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
526
633
|
}
|
|
527
634
|
|
|
528
635
|
return { segments, matchedIds };
|
|
@@ -548,6 +655,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
548
655
|
): Promise<{ segment: ResolvedSegment; matchedId: string }> {
|
|
549
656
|
const matchedId = entry.shortCode;
|
|
550
657
|
|
|
658
|
+
let handlerRan = false;
|
|
551
659
|
const component = await revalidate(
|
|
552
660
|
async () => {
|
|
553
661
|
const hasSegment = clientSegmentIds.has(entry.shortCode);
|
|
@@ -608,6 +716,8 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
608
716
|
context,
|
|
609
717
|
actionContext,
|
|
610
718
|
stale,
|
|
719
|
+
traceSource:
|
|
720
|
+
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
611
721
|
});
|
|
612
722
|
emitRevalidationDecision(
|
|
613
723
|
entry.shortCode,
|
|
@@ -622,6 +732,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
622
732
|
return shouldRevalidate;
|
|
623
733
|
},
|
|
624
734
|
async () => {
|
|
735
|
+
handlerRan = true;
|
|
625
736
|
const doneHandler = track(`handler:${entry.id}`, 2);
|
|
626
737
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
627
738
|
entry.shortCode;
|
|
@@ -636,13 +747,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
636
747
|
return staticComponent;
|
|
637
748
|
}
|
|
638
749
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
750
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
751
|
+
// the build handler. At build time (context.build === true), always
|
|
752
|
+
// use the build handler from routeEntry.handler.
|
|
753
|
+
const handler =
|
|
754
|
+
!context.build && routeEntry.liveHandler
|
|
755
|
+
? routeEntry.liveHandler
|
|
756
|
+
: routeEntry.handler;
|
|
639
757
|
if (!routeEntry.loading) {
|
|
640
|
-
const result = handleHandlerResult(await
|
|
758
|
+
const result = handleHandlerResult(await handler(context));
|
|
641
759
|
doneHandler();
|
|
642
760
|
return result;
|
|
643
761
|
}
|
|
644
762
|
if (!actionContext) {
|
|
645
|
-
const result = handleHandlerResult(
|
|
763
|
+
const result = handleHandlerResult(handler(context));
|
|
646
764
|
if (result instanceof Promise) {
|
|
647
765
|
result.finally(doneHandler).catch(() => {});
|
|
648
766
|
const tracked = deps.trackHandler(result, {
|
|
@@ -665,9 +783,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
665
783
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
666
784
|
entryId: entry.id,
|
|
667
785
|
});
|
|
668
|
-
const actionResult = handleHandlerResult(
|
|
669
|
-
await routeEntry.handler(context),
|
|
670
|
-
);
|
|
786
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
671
787
|
doneHandler();
|
|
672
788
|
return {
|
|
673
789
|
content: Promise.resolve(actionResult),
|
|
@@ -676,10 +792,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
676
792
|
() => null,
|
|
677
793
|
);
|
|
678
794
|
|
|
795
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
796
|
+
// component === null checks work consistently for both void and explicit null.
|
|
679
797
|
const resolvedComponent =
|
|
680
798
|
component && typeof component === "object" && "content" in component
|
|
681
|
-
? (component as { content: ReactNode }).content
|
|
682
|
-
: component;
|
|
799
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
800
|
+
: (component ?? null);
|
|
683
801
|
|
|
684
802
|
const segment: ResolvedSegment = {
|
|
685
803
|
id: entry.shortCode,
|
|
@@ -689,13 +807,17 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
689
807
|
index: 0,
|
|
690
808
|
component: resolvedComponent,
|
|
691
809
|
loading: entry.loading === false ? null : entry.loading,
|
|
692
|
-
transition:
|
|
810
|
+
transition: applyViewTransitionDefault(
|
|
811
|
+
entry.transition,
|
|
812
|
+
deps.viewTransitionDefault,
|
|
813
|
+
),
|
|
693
814
|
params,
|
|
694
815
|
belongsToRoute,
|
|
695
816
|
...(entry.type === "layout" || entry.type === "cache"
|
|
696
817
|
? { layoutName: entry.id }
|
|
697
818
|
: {}),
|
|
698
819
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
820
|
+
_handlerRan: handlerRan,
|
|
699
821
|
};
|
|
700
822
|
|
|
701
823
|
return { segment, matchedId };
|
|
@@ -776,11 +898,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
776
898
|
prevUrl,
|
|
777
899
|
nextUrl,
|
|
778
900
|
routeKey,
|
|
779
|
-
loaderPromises,
|
|
780
901
|
true,
|
|
781
902
|
deps,
|
|
782
903
|
actionContext,
|
|
783
904
|
stale,
|
|
905
|
+
entry,
|
|
784
906
|
);
|
|
785
907
|
segments.push(...orphanResult.segments);
|
|
786
908
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -860,7 +982,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
860
982
|
prevUrl,
|
|
861
983
|
nextUrl,
|
|
862
984
|
routeKey,
|
|
863
|
-
loaderPromises,
|
|
864
985
|
false,
|
|
865
986
|
deps,
|
|
866
987
|
actionContext,
|
|
@@ -887,11 +1008,12 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
887
1008
|
prevUrl: URL,
|
|
888
1009
|
nextUrl: URL,
|
|
889
1010
|
routeKey: string,
|
|
890
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
891
1011
|
belongsToRoute: boolean,
|
|
892
1012
|
deps: SegmentResolutionDeps<TEnv>,
|
|
893
1013
|
actionContext?: ActionContext,
|
|
894
1014
|
stale?: boolean,
|
|
1015
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
1016
|
+
parentRouteEntry?: EntryData,
|
|
895
1017
|
): Promise<SegmentRevalidationResult> {
|
|
896
1018
|
invariant(
|
|
897
1019
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -919,6 +1041,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
919
1041
|
segments.push(...loaderResult.segments);
|
|
920
1042
|
matchedIds.push(...loaderResult.matchedIds);
|
|
921
1043
|
|
|
1044
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1045
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1046
|
+
if (
|
|
1047
|
+
parentRouteEntry &&
|
|
1048
|
+
parentRouteEntry.loader &&
|
|
1049
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1050
|
+
Object.keys(orphan.parallel).length > 0
|
|
1051
|
+
) {
|
|
1052
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1053
|
+
parentRouteEntry,
|
|
1054
|
+
context,
|
|
1055
|
+
belongsToRoute,
|
|
1056
|
+
clientSegmentIds,
|
|
1057
|
+
prevParams,
|
|
1058
|
+
request,
|
|
1059
|
+
prevUrl,
|
|
1060
|
+
nextUrl,
|
|
1061
|
+
routeKey,
|
|
1062
|
+
deps,
|
|
1063
|
+
actionContext,
|
|
1064
|
+
orphan.shortCode,
|
|
1065
|
+
stale,
|
|
1066
|
+
);
|
|
1067
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1068
|
+
for (const s of inheritedResult.segments) {
|
|
1069
|
+
s._inherited = true;
|
|
1070
|
+
}
|
|
1071
|
+
segments.push(...inheritedResult.segments);
|
|
1072
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
922
1075
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
923
1076
|
// so ctx.set() values are visible to parallel children.
|
|
924
1077
|
matchedIds.push(orphan.shortCode);
|
|
@@ -991,114 +1144,133 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
991
1144
|
belongsToRoute,
|
|
992
1145
|
layoutName: orphan.id,
|
|
993
1146
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
994
|
-
transition:
|
|
1147
|
+
transition: applyViewTransitionDefault(
|
|
1148
|
+
orphan.transition,
|
|
1149
|
+
deps.viewTransitionDefault,
|
|
1150
|
+
),
|
|
995
1151
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
996
1152
|
});
|
|
997
1153
|
|
|
998
|
-
|
|
1154
|
+
const resolvedParallelEntries = new Set<string>();
|
|
1155
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
1156
|
+
orphan.parallel,
|
|
1157
|
+
)) {
|
|
999
1158
|
invariant(
|
|
1000
1159
|
parallelEntry.type === "parallel",
|
|
1001
1160
|
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
1002
1161
|
);
|
|
1003
1162
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1163
|
+
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1164
|
+
// shortCodeOverride must match the parent layout, not the parallel entry.
|
|
1165
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1166
|
+
parallelEntry,
|
|
1167
|
+
context,
|
|
1168
|
+
belongsToRoute,
|
|
1169
|
+
clientSegmentIds,
|
|
1170
|
+
prevParams,
|
|
1171
|
+
request,
|
|
1172
|
+
prevUrl,
|
|
1173
|
+
nextUrl,
|
|
1174
|
+
routeKey,
|
|
1175
|
+
deps,
|
|
1176
|
+
actionContext,
|
|
1177
|
+
orphan.shortCode,
|
|
1178
|
+
stale,
|
|
1179
|
+
);
|
|
1180
|
+
segments.push(...loaderResult.segments);
|
|
1181
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1182
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
1183
|
+
}
|
|
1021
1184
|
|
|
1022
1185
|
const slots = parallelEntry.handler as Record<
|
|
1023
1186
|
`@${string}`,
|
|
1024
1187
|
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1025
1188
|
| ReactNode
|
|
1026
1189
|
>;
|
|
1190
|
+
// Handler may be undefined in production after static handler eviction.
|
|
1191
|
+
const handler = slots[slot];
|
|
1192
|
+
|
|
1193
|
+
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1194
|
+
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1195
|
+
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1196
|
+
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1197
|
+
matchedIds.push(parallelId);
|
|
1198
|
+
|
|
1199
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
1200
|
+
let shouldResolve: boolean;
|
|
1201
|
+
if (isFullRefetch) {
|
|
1202
|
+
// Same load-bearing rationale as the main parallel path: full refetch
|
|
1203
|
+
// means the client has nothing to fall back to, so the slot must render.
|
|
1204
|
+
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
1205
|
+
shouldResolve = true;
|
|
1206
|
+
} else {
|
|
1207
|
+
// When slot is unknown to the client, seed the soft chain with `true`
|
|
1208
|
+
// (orphan parallels always belong to the route — we want them rendered
|
|
1209
|
+
// unless the user explicitly opts out via revalidate()).
|
|
1210
|
+
const defaultOverride = clientSegmentIds.has(parallelId)
|
|
1211
|
+
? undefined
|
|
1212
|
+
: { value: true, reason: "new-segment" };
|
|
1027
1213
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
source: "parallel",
|
|
1043
|
-
defaultShouldRevalidate: true,
|
|
1044
|
-
finalShouldRevalidate: true,
|
|
1045
|
-
reason: "new-segment",
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
return true;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
const dummySegment: ResolvedSegment = {
|
|
1052
|
-
id: parallelId,
|
|
1053
|
-
namespace: parallelEntry.id,
|
|
1054
|
-
type: "parallel",
|
|
1055
|
-
index: 0,
|
|
1056
|
-
component: null as any,
|
|
1057
|
-
params,
|
|
1058
|
-
slot,
|
|
1059
|
-
belongsToRoute,
|
|
1060
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1061
|
-
...(parallelEntry.mountPath
|
|
1062
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1063
|
-
: {}),
|
|
1064
|
-
};
|
|
1214
|
+
const dummySegment: ResolvedSegment = {
|
|
1215
|
+
id: parallelId,
|
|
1216
|
+
namespace: parallelEntry.id,
|
|
1217
|
+
type: "parallel",
|
|
1218
|
+
index: 0,
|
|
1219
|
+
component: null as any,
|
|
1220
|
+
params,
|
|
1221
|
+
slot,
|
|
1222
|
+
belongsToRoute,
|
|
1223
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1224
|
+
...(parallelEntry.mountPath
|
|
1225
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1226
|
+
: {}),
|
|
1227
|
+
};
|
|
1065
1228
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
routeKey,
|
|
1078
|
-
context,
|
|
1079
|
-
actionContext,
|
|
1080
|
-
stale,
|
|
1081
|
-
traceSource: "parallel",
|
|
1082
|
-
});
|
|
1083
|
-
})();
|
|
1084
|
-
emitRevalidationDecision(
|
|
1085
|
-
parallelId,
|
|
1086
|
-
context.pathname,
|
|
1229
|
+
shouldResolve = await evaluateRevalidation({
|
|
1230
|
+
segment: dummySegment,
|
|
1231
|
+
prevParams,
|
|
1232
|
+
getPrevSegment: null,
|
|
1233
|
+
request,
|
|
1234
|
+
prevUrl,
|
|
1235
|
+
nextUrl,
|
|
1236
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1237
|
+
name: `revalidate${i}`,
|
|
1238
|
+
fn,
|
|
1239
|
+
})),
|
|
1087
1240
|
routeKey,
|
|
1088
|
-
|
|
1089
|
-
|
|
1241
|
+
context,
|
|
1242
|
+
actionContext,
|
|
1243
|
+
stale,
|
|
1244
|
+
traceSource: "parallel",
|
|
1245
|
+
defaultOverride,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
emitRevalidationDecision(
|
|
1249
|
+
parallelId,
|
|
1250
|
+
context.pathname,
|
|
1251
|
+
routeKey,
|
|
1252
|
+
shouldResolve,
|
|
1253
|
+
);
|
|
1090
1254
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1255
|
+
let component: ReactNode | undefined;
|
|
1256
|
+
let handlerRan = false;
|
|
1257
|
+
if (shouldResolve) {
|
|
1258
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1259
|
+
}
|
|
1260
|
+
if (component === undefined) {
|
|
1261
|
+
const hasLoadingFallback =
|
|
1262
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1263
|
+
if (!shouldResolve) {
|
|
1264
|
+
component = null;
|
|
1265
|
+
} else if (handler === undefined) {
|
|
1266
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
1267
|
+
component = null;
|
|
1268
|
+
} else {
|
|
1269
|
+
// Slot-keyed pushes — see resolveParallelSegmentsWithRevalidation.
|
|
1270
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
1271
|
+
parallelId;
|
|
1272
|
+
handlerRan = true;
|
|
1273
|
+
if (hasLoadingFallback) {
|
|
1102
1274
|
const result =
|
|
1103
1275
|
typeof handler === "function" ? handler(context) : handler;
|
|
1104
1276
|
if (result instanceof Promise) {
|
|
@@ -1123,24 +1295,28 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1123
1295
|
typeof handler === "function" ? await handler(context) : handler;
|
|
1124
1296
|
}
|
|
1125
1297
|
}
|
|
1126
|
-
|
|
1127
|
-
segments.push({
|
|
1128
|
-
id: parallelId,
|
|
1129
|
-
namespace: parallelEntry.id,
|
|
1130
|
-
type: "parallel",
|
|
1131
|
-
index: 0,
|
|
1132
|
-
component,
|
|
1133
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1134
|
-
transition: parallelEntry.transition,
|
|
1135
|
-
params,
|
|
1136
|
-
slot,
|
|
1137
|
-
belongsToRoute,
|
|
1138
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1139
|
-
...(parallelEntry.mountPath
|
|
1140
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1141
|
-
: {}),
|
|
1142
|
-
});
|
|
1143
1298
|
}
|
|
1299
|
+
|
|
1300
|
+
segments.push({
|
|
1301
|
+
id: parallelId,
|
|
1302
|
+
namespace: parallelEntry.id,
|
|
1303
|
+
type: "parallel",
|
|
1304
|
+
index: 0,
|
|
1305
|
+
component,
|
|
1306
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1307
|
+
transition: applyViewTransitionDefault(
|
|
1308
|
+
parallelEntry.transition,
|
|
1309
|
+
deps.viewTransitionDefault,
|
|
1310
|
+
),
|
|
1311
|
+
params,
|
|
1312
|
+
slot,
|
|
1313
|
+
_handlerRan: handlerRan,
|
|
1314
|
+
belongsToRoute,
|
|
1315
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1316
|
+
...(parallelEntry.mountPath
|
|
1317
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1318
|
+
: {}),
|
|
1319
|
+
});
|
|
1144
1320
|
}
|
|
1145
1321
|
|
|
1146
1322
|
return { segments, matchedIds };
|
|
@@ -1165,6 +1341,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1165
1341
|
localRouteName: string,
|
|
1166
1342
|
pathname: string,
|
|
1167
1343
|
deps: SegmentResolutionDeps<TEnv>,
|
|
1344
|
+
stale?: boolean,
|
|
1168
1345
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1169
1346
|
const allSegments: ResolvedSegment[] = [];
|
|
1170
1347
|
const matchedIds: string[] = [];
|
|
@@ -1191,6 +1368,10 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1191
1368
|
}
|
|
1192
1369
|
|
|
1193
1370
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1371
|
+
if (entry.type === "cache") {
|
|
1372
|
+
const store = RangoContext.getStore();
|
|
1373
|
+
if (store) store.insideCacheScope = true;
|
|
1374
|
+
}
|
|
1194
1375
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1195
1376
|
const resolved = await resolveWithErrorBoundary(
|
|
1196
1377
|
nonParallelEntry,
|
|
@@ -1209,7 +1390,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1209
1390
|
loaderPromises,
|
|
1210
1391
|
deps,
|
|
1211
1392
|
actionContext,
|
|
1212
|
-
|
|
1393
|
+
stale,
|
|
1213
1394
|
),
|
|
1214
1395
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
1215
1396
|
deps,
|