@rangojs/router 0.0.0-experimental.8a4d0430 → 0.0.0-experimental.8bcfea43
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 +126 -38
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1171 -461
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +19 -16
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +28 -20
- 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 +88 -45
- 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 +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +55 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +13 -1
- package/src/__internal.ts +1 -1
- 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/navigation-bridge.ts +90 -16
- package/src/browser/navigation-client.ts +167 -59
- 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 +184 -16
- package/src/browser/prefetch/fetch.ts +180 -33
- 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 +81 -9
- 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 +168 -65
- 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 +49 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- 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.tsx +84 -230
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- 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 -0
- 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 +101 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +127 -192
- 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 +104 -10
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +8 -30
- package/src/router/middleware.ts +36 -10
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +198 -20
- 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 +438 -300
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +10 -4
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +60 -8
- package/src/rsc/handler.ts +478 -374
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +16 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +19 -1
- package/src/rsc/server-action.ts +10 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- 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 +166 -17
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +194 -60
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +137 -65
- 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 +2 -0
- 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/debug.ts +55 -0
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +128 -74
- package/src/vite/discovery/state.ts +13 -6
- package/src/vite/index.ts +4 -0
- 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 +86 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +335 -64
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ReactNode } from "react";
|
|
9
9
|
import { invariant } from "../../errors";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
getParallelEntries,
|
|
12
|
+
getParallelSlotEntries,
|
|
13
|
+
type EntryData,
|
|
14
|
+
} from "../../server/context";
|
|
11
15
|
import type {
|
|
12
16
|
HandlerContext,
|
|
13
17
|
InternalHandlerContext,
|
|
@@ -15,6 +19,8 @@ import type {
|
|
|
15
19
|
} from "../../types";
|
|
16
20
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
17
21
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
22
|
+
import { _getRequestContext } from "../../server/request-context.js";
|
|
23
|
+
import { appendMetric } from "../metrics.js";
|
|
18
24
|
import {
|
|
19
25
|
handleHandlerResult,
|
|
20
26
|
tryStaticHandler,
|
|
@@ -24,7 +30,11 @@ import {
|
|
|
24
30
|
} from "./helpers.js";
|
|
25
31
|
import { getRouterContext } from "../router-context.js";
|
|
26
32
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
27
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
track,
|
|
35
|
+
RSCRouterContext,
|
|
36
|
+
runInsideLoaderScope,
|
|
37
|
+
} from "../../server/context.js";
|
|
28
38
|
|
|
29
39
|
// ---------------------------------------------------------------------------
|
|
30
40
|
// Streamed handler telemetry
|
|
@@ -90,9 +100,11 @@ export async function resolveLoaders<TEnv>(
|
|
|
90
100
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
91
101
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
92
102
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
103
|
+
const ms = _getRequestContext()?._metricsStore;
|
|
93
104
|
|
|
94
105
|
if (!loadingDisabled) {
|
|
95
|
-
|
|
106
|
+
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
107
|
+
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
96
108
|
const { loader } = loaderEntry;
|
|
97
109
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
98
110
|
return {
|
|
@@ -104,7 +116,9 @@ export async function resolveLoaders<TEnv>(
|
|
|
104
116
|
params: ctx.params,
|
|
105
117
|
loaderId: loader.$$id,
|
|
106
118
|
loaderData: deps.wrapLoaderPromise(
|
|
107
|
-
|
|
119
|
+
runInsideLoaderScope(() =>
|
|
120
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
121
|
+
),
|
|
108
122
|
entry,
|
|
109
123
|
segmentId,
|
|
110
124
|
ctx.pathname,
|
|
@@ -112,18 +126,38 @@ export async function resolveLoaders<TEnv>(
|
|
|
112
126
|
belongsToRoute,
|
|
113
127
|
};
|
|
114
128
|
});
|
|
129
|
+
|
|
130
|
+
return segments;
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
118
134
|
// settled promises so handlers don't stream loading placeholders.
|
|
119
|
-
const pendingLoaderData = loaderEntries.map((loaderEntry) =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry) => {
|
|
136
|
+
const start = performance.now();
|
|
137
|
+
const promise = runInsideLoaderScope(() =>
|
|
138
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
139
|
+
);
|
|
140
|
+
return { promise, start, loaderId: loaderEntry.loader.$$id };
|
|
141
|
+
});
|
|
142
|
+
await Promise.all(pendingLoaderData.map((p) => p.promise));
|
|
123
143
|
|
|
124
144
|
return loaderEntries.map((loaderEntry, i) => {
|
|
125
145
|
const { loader } = loaderEntry;
|
|
126
146
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
147
|
+
const pending = pendingLoaderData[i]!;
|
|
148
|
+
if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
|
|
149
|
+
// All loaders ran in parallel via Promise.all — each span covers
|
|
150
|
+
// from its own kickoff to the batch settlement, giving a ceiling
|
|
151
|
+
// on that loader's contribution to the overall wait.
|
|
152
|
+
const batchEnd = performance.now();
|
|
153
|
+
appendMetric(
|
|
154
|
+
ms,
|
|
155
|
+
`loader:${loader.$$id}`,
|
|
156
|
+
pending.start,
|
|
157
|
+
batchEnd - pending.start,
|
|
158
|
+
2,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
127
161
|
return {
|
|
128
162
|
id: segmentId,
|
|
129
163
|
namespace: entry.id,
|
|
@@ -133,7 +167,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
133
167
|
params: ctx.params,
|
|
134
168
|
loaderId: loader.$$id,
|
|
135
169
|
loaderData: deps.wrapLoaderPromise(
|
|
136
|
-
|
|
170
|
+
pending.promise,
|
|
137
171
|
entry,
|
|
138
172
|
segmentId,
|
|
139
173
|
ctx.pathname,
|
|
@@ -197,7 +231,10 @@ export async function resolveSegment<TEnv>(
|
|
|
197
231
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
198
232
|
});
|
|
199
233
|
|
|
200
|
-
|
|
234
|
+
const resolvedParallelEntries = new Set<string>();
|
|
235
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
236
|
+
entry.parallel,
|
|
237
|
+
)) {
|
|
201
238
|
const parallelSegments = await resolveParallelEntry(
|
|
202
239
|
parallelEntry,
|
|
203
240
|
params,
|
|
@@ -207,8 +244,11 @@ export async function resolveSegment<TEnv>(
|
|
|
207
244
|
deps,
|
|
208
245
|
options,
|
|
209
246
|
routeKey,
|
|
247
|
+
[slot],
|
|
248
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
210
249
|
);
|
|
211
250
|
segments.push(...parallelSegments);
|
|
251
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
212
252
|
}
|
|
213
253
|
|
|
214
254
|
for (const orphan of entry.layout) {
|
|
@@ -244,9 +284,14 @@ export async function resolveSegment<TEnv>(
|
|
|
244
284
|
entry.shortCode,
|
|
245
285
|
);
|
|
246
286
|
if (component === undefined) {
|
|
287
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
288
|
+
// the build handler. At build time (context.build === true), always
|
|
289
|
+
// use the build handler from entry.handler.
|
|
290
|
+
const handler =
|
|
291
|
+
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
247
292
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
248
293
|
if (entry.loading) {
|
|
249
|
-
const result = handleHandlerResult(
|
|
294
|
+
const result = handleHandlerResult(handler(context));
|
|
250
295
|
if (result instanceof Promise) {
|
|
251
296
|
result.finally(doneRouteHandler).catch(() => {});
|
|
252
297
|
const tracked = deps.trackHandler(result, {
|
|
@@ -267,7 +312,7 @@ export async function resolveSegment<TEnv>(
|
|
|
267
312
|
component = result;
|
|
268
313
|
}
|
|
269
314
|
} else {
|
|
270
|
-
component = handleHandlerResult(await
|
|
315
|
+
component = handleHandlerResult(await handler(context));
|
|
271
316
|
doneRouteHandler();
|
|
272
317
|
}
|
|
273
318
|
}
|
|
@@ -282,11 +327,15 @@ export async function resolveSegment<TEnv>(
|
|
|
282
327
|
deps,
|
|
283
328
|
options,
|
|
284
329
|
routeKey,
|
|
330
|
+
entry,
|
|
285
331
|
);
|
|
286
332
|
segments.push(...orphanSegments);
|
|
287
333
|
}
|
|
288
334
|
|
|
289
|
-
|
|
335
|
+
const resolvedParallelEntries = new Set<string>();
|
|
336
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
337
|
+
entry.parallel,
|
|
338
|
+
)) {
|
|
290
339
|
const parallelSegments = await resolveParallelEntry(
|
|
291
340
|
parallelEntry,
|
|
292
341
|
params,
|
|
@@ -296,8 +345,11 @@ export async function resolveSegment<TEnv>(
|
|
|
296
345
|
deps,
|
|
297
346
|
options,
|
|
298
347
|
routeKey,
|
|
348
|
+
[slot],
|
|
349
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
299
350
|
);
|
|
300
351
|
segments.push(...parallelSegments);
|
|
352
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
301
353
|
}
|
|
302
354
|
|
|
303
355
|
segments.push({
|
|
@@ -305,7 +357,7 @@ export async function resolveSegment<TEnv>(
|
|
|
305
357
|
namespace: entry.id,
|
|
306
358
|
type: "route",
|
|
307
359
|
index: 0,
|
|
308
|
-
component,
|
|
360
|
+
component: component ?? null,
|
|
309
361
|
loading: entry.loading === false ? null : entry.loading,
|
|
310
362
|
transition: entry.transition,
|
|
311
363
|
params,
|
|
@@ -331,6 +383,9 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
331
383
|
deps: SegmentResolutionDeps<TEnv>,
|
|
332
384
|
options?: ResolveSegmentOptions,
|
|
333
385
|
routeKey?: string,
|
|
386
|
+
/** Parent route entry — its loaders are inherited by the layout so
|
|
387
|
+
* parallel slots inside this layout can access them via useLoader(). */
|
|
388
|
+
parentRouteEntry?: EntryData,
|
|
334
389
|
): Promise<ResolvedSegment[]> {
|
|
335
390
|
invariant(
|
|
336
391
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -346,6 +401,30 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
346
401
|
deps,
|
|
347
402
|
);
|
|
348
403
|
segments.push(...loaderSegments);
|
|
404
|
+
|
|
405
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
406
|
+
// can access them via useLoader(). Without this, the route's loaders
|
|
407
|
+
// are only in the route's OutletProvider (rendered as <Outlet /> content),
|
|
408
|
+
// which is a child — not a parent — of the layout's context.
|
|
409
|
+
if (
|
|
410
|
+
parentRouteEntry &&
|
|
411
|
+
parentRouteEntry.loader &&
|
|
412
|
+
parentRouteEntry.loader.length > 0 &&
|
|
413
|
+
Object.keys(orphan.parallel).length > 0
|
|
414
|
+
) {
|
|
415
|
+
const inheritedLoaders = await resolveLoaders(
|
|
416
|
+
parentRouteEntry,
|
|
417
|
+
context,
|
|
418
|
+
belongsToRoute,
|
|
419
|
+
deps,
|
|
420
|
+
orphan.shortCode,
|
|
421
|
+
);
|
|
422
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
423
|
+
for (const s of inheritedLoaders) {
|
|
424
|
+
s._inherited = true;
|
|
425
|
+
}
|
|
426
|
+
segments.push(...inheritedLoaders);
|
|
427
|
+
}
|
|
349
428
|
}
|
|
350
429
|
|
|
351
430
|
// Handler-first: orphan layout handler executes before its parallels
|
|
@@ -368,7 +447,10 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
368
447
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
369
448
|
});
|
|
370
449
|
|
|
371
|
-
|
|
450
|
+
const resolvedParallelEntries = new Set<string>();
|
|
451
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
452
|
+
orphan.parallel,
|
|
453
|
+
)) {
|
|
372
454
|
const parallelSegments = await resolveParallelEntry(
|
|
373
455
|
parallelEntry,
|
|
374
456
|
params,
|
|
@@ -378,8 +460,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
378
460
|
deps,
|
|
379
461
|
options,
|
|
380
462
|
routeKey,
|
|
463
|
+
[slot],
|
|
464
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
381
465
|
);
|
|
382
466
|
segments.push(...parallelSegments);
|
|
467
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
383
468
|
}
|
|
384
469
|
|
|
385
470
|
return segments;
|
|
@@ -397,6 +482,8 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
397
482
|
deps: SegmentResolutionDeps<TEnv>,
|
|
398
483
|
options?: ResolveSegmentOptions,
|
|
399
484
|
routeKey?: string,
|
|
485
|
+
slotNames?: `@${string}`[],
|
|
486
|
+
includeLoaders: boolean = true,
|
|
400
487
|
): Promise<ResolvedSegment[]> {
|
|
401
488
|
invariant(
|
|
402
489
|
parallelEntry.type === "parallel",
|
|
@@ -411,7 +498,12 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
411
498
|
| ReactNode
|
|
412
499
|
>;
|
|
413
500
|
|
|
414
|
-
|
|
501
|
+
const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
|
|
502
|
+
|
|
503
|
+
for (const slot of slotsToResolve) {
|
|
504
|
+
// Try static lookup first — in production, handler bodies are evicted
|
|
505
|
+
// and replaced with stubs that have no .handler property (undefined).
|
|
506
|
+
// The static store holds the pre-rendered component for these slots.
|
|
415
507
|
let component: ReactNode | undefined = await tryStaticSlot(
|
|
416
508
|
parallelEntry,
|
|
417
509
|
slot,
|
|
@@ -419,6 +511,10 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
419
511
|
);
|
|
420
512
|
|
|
421
513
|
if (component === undefined) {
|
|
514
|
+
const handler = slots[slot];
|
|
515
|
+
if (handler === undefined) {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
422
518
|
const doneParallelHandler = track(
|
|
423
519
|
`handler:${parallelEntry.id}.${slot}`,
|
|
424
520
|
2,
|
|
@@ -472,7 +568,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
472
568
|
});
|
|
473
569
|
}
|
|
474
570
|
|
|
475
|
-
if (!
|
|
571
|
+
if (!options?.skipLoaders && includeLoaders) {
|
|
476
572
|
const loaderSegments = await resolveLoaders(
|
|
477
573
|
parallelEntry,
|
|
478
574
|
context,
|
|
@@ -480,6 +576,15 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
480
576
|
deps,
|
|
481
577
|
parentShortCode,
|
|
482
578
|
);
|
|
579
|
+
// Tag parallel-owned loaders so renderSegments can stream them
|
|
580
|
+
// using the parallel's loading() instead of awaiting on the layout
|
|
581
|
+
const parallelLoading =
|
|
582
|
+
parallelEntry.loading === false ? undefined : parallelEntry.loading;
|
|
583
|
+
if (parallelLoading) {
|
|
584
|
+
for (const seg of loaderSegments) {
|
|
585
|
+
seg.parallelLoading = parallelLoading;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
483
588
|
segments.push(...loaderSegments);
|
|
484
589
|
}
|
|
485
590
|
|
|
@@ -515,6 +620,13 @@ export async function resolveAllSegments<TEnv>(
|
|
|
515
620
|
} catch {}
|
|
516
621
|
|
|
517
622
|
for (const entry of entries) {
|
|
623
|
+
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
624
|
+
// can guard non-cacheable variable reads. Also guards response-level
|
|
625
|
+
// side effects (headers.set). Persists for all descendant entries.
|
|
626
|
+
if (entry.type === "cache") {
|
|
627
|
+
const store = RSCRouterContext.getStore();
|
|
628
|
+
if (store) store.insideCacheScope = true;
|
|
629
|
+
}
|
|
518
630
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
519
631
|
const resolvedSegments = await resolveWithErrorBoundary(
|
|
520
632
|
entry,
|
|
@@ -559,11 +671,77 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
559
671
|
deps: SegmentResolutionDeps<TEnv>,
|
|
560
672
|
): Promise<ResolvedSegment[]> {
|
|
561
673
|
const loaderSegments: ResolvedSegment[] = [];
|
|
674
|
+
const seenIds = new Set<string>();
|
|
675
|
+
|
|
676
|
+
async function collectEntryLoaders(
|
|
677
|
+
entry: EntryData,
|
|
678
|
+
belongsToRoute: boolean,
|
|
679
|
+
shortCodeOverride?: string,
|
|
680
|
+
): Promise<void> {
|
|
681
|
+
// Skip if all loaders from this entry have already been resolved
|
|
682
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
683
|
+
const entryLoaders = entry.loader ?? [];
|
|
684
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
685
|
+
const allAlreadySeen =
|
|
686
|
+
entryLoaders.length > 0 &&
|
|
687
|
+
entryLoaders.every((le, i) =>
|
|
688
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
689
|
+
);
|
|
690
|
+
if (!allAlreadySeen) {
|
|
691
|
+
const segments = await resolveLoaders(
|
|
692
|
+
entry,
|
|
693
|
+
context,
|
|
694
|
+
belongsToRoute,
|
|
695
|
+
deps,
|
|
696
|
+
shortCodeOverride,
|
|
697
|
+
);
|
|
698
|
+
for (const seg of segments) {
|
|
699
|
+
if (!seenIds.has(seg.id)) {
|
|
700
|
+
seenIds.add(seg.id);
|
|
701
|
+
loaderSegments.push(seg);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const seenParallelEntryIds = new Set<string>();
|
|
707
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
708
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
709
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
710
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
714
|
+
for (const layoutEntry of entry.layout) {
|
|
715
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
716
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
717
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
718
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
719
|
+
if (
|
|
720
|
+
entry.type === "route" &&
|
|
721
|
+
entry.loader &&
|
|
722
|
+
entry.loader.length > 0 &&
|
|
723
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
724
|
+
) {
|
|
725
|
+
const inherited = await resolveLoaders(
|
|
726
|
+
entry,
|
|
727
|
+
context,
|
|
728
|
+
childBelongsToRoute,
|
|
729
|
+
deps,
|
|
730
|
+
layoutEntry.shortCode,
|
|
731
|
+
);
|
|
732
|
+
for (const seg of inherited) {
|
|
733
|
+
if (!seenIds.has(seg.id)) {
|
|
734
|
+
seenIds.add(seg.id);
|
|
735
|
+
seg._inherited = true;
|
|
736
|
+
loaderSegments.push(seg);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
562
742
|
|
|
563
743
|
for (const entry of entries) {
|
|
564
|
-
|
|
565
|
-
const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
|
|
566
|
-
loaderSegments.push(...segments);
|
|
744
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
567
745
|
}
|
|
568
746
|
|
|
569
747
|
return loaderSegments;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Error boundary segment creation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type
|
|
11
|
+
import { createElement, type ReactNode } from "react";
|
|
12
12
|
import { DataNotFoundError } from "../../errors";
|
|
13
13
|
import {
|
|
14
14
|
createErrorInfo,
|
|
@@ -180,34 +180,39 @@ export function catchSegmentError<TEnv>(
|
|
|
180
180
|
|
|
181
181
|
if (error instanceof DataNotFoundError) {
|
|
182
182
|
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
183
|
+
// Fall back to router's notFound component, then a plain default
|
|
184
|
+
const notFoundOption = deps.notFoundComponent;
|
|
185
|
+
const defaultFallback =
|
|
186
|
+
typeof notFoundOption === "function"
|
|
187
|
+
? notFoundOption({ pathname: pathname ?? "" })
|
|
188
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
189
|
+
const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
+
const notFoundInfo = createNotFoundInfo(
|
|
192
|
+
error,
|
|
193
|
+
entry.shortCode,
|
|
194
|
+
entry.type,
|
|
195
|
+
pathname,
|
|
196
|
+
);
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
reportError(true, {
|
|
199
|
+
notFound: true,
|
|
200
|
+
message: notFoundInfo.message,
|
|
201
|
+
});
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
204
|
+
segmentId: entry.shortCode,
|
|
205
|
+
message: notFoundInfo.message,
|
|
206
|
+
});
|
|
201
207
|
|
|
202
|
-
|
|
208
|
+
setResponseStatus(404);
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
210
|
+
return createNotFoundSegment(
|
|
211
|
+
notFoundInfo,
|
|
212
|
+
effectiveNotFoundFallback,
|
|
213
|
+
entry,
|
|
214
|
+
params,
|
|
215
|
+
);
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
const fallback = deps.findNearestErrorBoundary(entry);
|
|
@@ -147,6 +147,7 @@ export function resolveLoaderData<TEnv>(
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const loaderId = loaderEntry.loader.$$id;
|
|
150
|
+
|
|
150
151
|
const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
|
|
151
152
|
const swrWindow = resolveSwrWindow(options.swr, store.defaults);
|
|
152
153
|
const swr = swrWindow || undefined;
|