@rangojs/router 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/client-urls/server-projection.d.ts +4 -4
- package/dist/types/client-urls/types.d.ts +13 -12
- package/dist/types/route-definition/helpers-types.d.ts +6 -5
- package/dist/types/router/segment-resolution/loader-mask.d.ts +1 -1
- package/dist/types/rsc/shell-capture.d.ts +9 -0
- package/dist/types/rsc/types.d.ts +8 -0
- package/dist/types/server/context.d.ts +1 -1
- package/dist/types/server/request-context.d.ts +1 -1
- package/dist/types/ssr/index.d.ts +16 -0
- package/dist/types/ssr/ssr-root.d.ts +5 -0
- package/dist/types/types/loader-types.d.ts +24 -22
- package/dist/types/urls/path-helper-types.d.ts +8 -7
- package/dist/vite/index.js +8 -5
- package/package.json +22 -21
- package/skills/breadcrumbs/SKILL.md +2 -2
- package/skills/catalog.json +2 -2
- package/skills/client-urls/SKILL.md +9 -8
- package/skills/comparison/references/framework-comparison.md +2 -2
- package/skills/hooks/data.md +1 -1
- package/skills/hooks/handle-and-actions.md +1 -1
- package/skills/loader/SKILL.md +29 -28
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/migrate-react-router/component-migration.md +1 -1
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/migrate-react-router/route-mapping.md +1 -1
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +1 -1
- package/skills/rango/SKILL.md +20 -20
- package/skills/router-setup/SKILL.md +1 -1
- package/skills/scripts/SKILL.md +1 -1
- package/skills/shell-manifest/SKILL.md +1 -1
- package/src/browser/react/Link.tsx +27 -4
- package/src/client-urls/client-urls.ts +12 -9
- package/src/client-urls/server-projection.ts +6 -6
- package/src/client-urls/types.ts +13 -12
- package/src/route-definition/dsl-helpers.ts +7 -3
- package/src/route-definition/helpers-types.ts +6 -5
- package/src/router/loader-resolution.ts +3 -3
- package/src/router/segment-resolution/fresh.ts +2 -2
- package/src/router/segment-resolution/loader-cache.ts +1 -1
- package/src/router/segment-resolution/loader-mask.ts +1 -1
- package/src/rsc/rsc-rendering.ts +10 -0
- package/src/rsc/shell-capture.ts +10 -0
- package/src/rsc/ssr-setup.ts +4 -0
- package/src/rsc/types.ts +18 -2
- package/src/server/context.ts +1 -1
- package/src/server/request-context.ts +1 -1
- package/src/ssr/index.tsx +22 -2
- package/src/ssr/ssr-root.tsx +17 -1
- package/src/types/loader-types.ts +21 -19
- package/src/urls/path-helper-types.ts +8 -7
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -155,6 +155,7 @@ function createShellCaptureDescriptor(
|
|
|
155
155
|
ctx: HandlerContext<any>,
|
|
156
156
|
key: string,
|
|
157
157
|
searchSeed: string,
|
|
158
|
+
originSeed: string,
|
|
158
159
|
pprConfig: ResolvedPprConfig,
|
|
159
160
|
store: SegmentCacheStore<any>,
|
|
160
161
|
navigationOnly?: true,
|
|
@@ -162,6 +163,11 @@ function createShellCaptureDescriptor(
|
|
|
162
163
|
return {
|
|
163
164
|
key,
|
|
164
165
|
searchSeed,
|
|
166
|
+
// Derived from the same request URL as the key (the callers compute
|
|
167
|
+
// both next to each other, so they cannot drift): seeding the origin
|
|
168
|
+
// keeps origin-dependent static markup (Link's data-external)
|
|
169
|
+
// identical across capture, resume, and browser hydration.
|
|
170
|
+
originSeed,
|
|
165
171
|
buildVersion: ctx.version,
|
|
166
172
|
ttl: pprConfig.ttl,
|
|
167
173
|
swr: pprConfig.swr,
|
|
@@ -519,6 +525,7 @@ function* shellServePlan<TEnv>(
|
|
|
519
525
|
ctx,
|
|
520
526
|
key,
|
|
521
527
|
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
528
|
+
url.origin,
|
|
522
529
|
pprConfig,
|
|
523
530
|
store,
|
|
524
531
|
);
|
|
@@ -1004,6 +1011,7 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
1004
1011
|
partialCaptureKey ??
|
|
1005
1012
|
buildNavigationShellKey(url, reqCtx._searchParamsFilter),
|
|
1006
1013
|
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1014
|
+
url.origin,
|
|
1007
1015
|
pprConfig,
|
|
1008
1016
|
store,
|
|
1009
1017
|
true,
|
|
@@ -1524,6 +1532,8 @@ function serveShellHit(
|
|
|
1524
1532
|
// The shell key's own search — identical to the capture seed for
|
|
1525
1533
|
// this key, so the resume tree matches the captured tree.
|
|
1526
1534
|
search: shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1535
|
+
// The HIT request's origin — same host as the capture's (key-scoped).
|
|
1536
|
+
origin: url.origin,
|
|
1527
1537
|
}),
|
|
1528
1538
|
);
|
|
1529
1539
|
};
|
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -917,6 +917,15 @@ export interface ShellCaptureDescriptor {
|
|
|
917
917
|
* Omitted (build-time bare-pathname captures) = search-less, same as "".
|
|
918
918
|
*/
|
|
919
919
|
searchSeed?: string;
|
|
920
|
+
/**
|
|
921
|
+
* The scheduling request's origin, seeding the capture render's SSR store
|
|
922
|
+
* location. Shell keys are host-scoped, so the resume pass's request
|
|
923
|
+
* agrees modulo protocol drift; the seed keeps origin-dependent static
|
|
924
|
+
* markup (Link's data-external) identical across capture, resume, and
|
|
925
|
+
* browser hydration. Omitted (build-time host-agnostic captures) falls
|
|
926
|
+
* back to the internal host.
|
|
927
|
+
*/
|
|
928
|
+
originSeed?: string;
|
|
920
929
|
/**
|
|
921
930
|
* The RSC handler's build version (HandlerContext.version), stamped into the
|
|
922
931
|
* stored entry as ShellCacheEntry.buildVersion — the serve-side
|
|
@@ -1767,6 +1776,7 @@ async function captureAndStoreShell(
|
|
|
1767
1776
|
// The shell key's own search seeds the capture render's store —
|
|
1768
1777
|
// static-part search reads bake what the key names.
|
|
1769
1778
|
search: capture.searchSeed,
|
|
1779
|
+
origin: capture.originSeed,
|
|
1770
1780
|
}),
|
|
1771
1781
|
);
|
|
1772
1782
|
} catch (error) {
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -120,6 +120,10 @@ export function createSsrHtmlStage<TEnv>(
|
|
|
120
120
|
...options.render,
|
|
121
121
|
streamMode,
|
|
122
122
|
search: options.url.search,
|
|
123
|
+
// origin: same out-of-band channel — seeds the SSR store location
|
|
124
|
+
// so origin-dependent markup (Link's data-external) agrees with the
|
|
125
|
+
// browser's window.location across hydration.
|
|
126
|
+
origin: options.url.origin,
|
|
123
127
|
}),
|
|
124
128
|
...(options.init && { init: options.init }),
|
|
125
129
|
};
|
package/src/rsc/types.ts
CHANGED
|
@@ -187,6 +187,12 @@ export interface SSRRenderOptions {
|
|
|
187
187
|
* search; see ShellCaptureOptions.search in the SSR entry).
|
|
188
188
|
*/
|
|
189
189
|
search?: string;
|
|
190
|
+
/**
|
|
191
|
+
* The live request's origin (`url.origin`), seeding the SSR store
|
|
192
|
+
* location so origin-dependent markup (Link's data-external) agrees with
|
|
193
|
+
* the browser across hydration. Absent on host-agnostic build captures.
|
|
194
|
+
*/
|
|
195
|
+
origin?: string;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
/**
|
|
@@ -208,7 +214,12 @@ export interface SSRModule {
|
|
|
208
214
|
*/
|
|
209
215
|
captureShellHTML?: (
|
|
210
216
|
rscStream: ReadableStream<Uint8Array>,
|
|
211
|
-
options: {
|
|
217
|
+
options: {
|
|
218
|
+
quiesce: Promise<void>;
|
|
219
|
+
maxWaitMs?: number;
|
|
220
|
+
search?: string;
|
|
221
|
+
origin?: string;
|
|
222
|
+
},
|
|
212
223
|
) => Promise<{ prelude: Uint8Array; postponed: string | null } | null>;
|
|
213
224
|
|
|
214
225
|
/**
|
|
@@ -221,7 +232,12 @@ export interface SSRModule {
|
|
|
221
232
|
*/
|
|
222
233
|
resumeShellHTML?: (
|
|
223
234
|
rscStream: ReadableStream<Uint8Array>,
|
|
224
|
-
options: {
|
|
235
|
+
options: {
|
|
236
|
+
postponed: string | null;
|
|
237
|
+
nonce?: string;
|
|
238
|
+
search?: string;
|
|
239
|
+
origin?: string;
|
|
240
|
+
},
|
|
225
241
|
) => Promise<ReadableStream<Uint8Array>>;
|
|
226
242
|
}
|
|
227
243
|
|
package/src/server/context.ts
CHANGED
|
@@ -108,7 +108,7 @@ export type LoaderEntry = {
|
|
|
108
108
|
cache?: EntryCacheConfig;
|
|
109
109
|
/**
|
|
110
110
|
* Document renders await this loader before segment resolution returns
|
|
111
|
-
* (loader(Def, {
|
|
111
|
+
* (loader(Def, { ssr: false })), so its data, handle pushes, and
|
|
112
112
|
* thrown notFound()/redirect() deterministically precede first flush.
|
|
113
113
|
* Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
|
|
114
114
|
* per-isSSR (router/manifest.ts cache key), so the flag is already
|
|
@@ -620,7 +620,7 @@ export interface RequestContext<
|
|
|
620
620
|
|
|
621
621
|
/**
|
|
622
622
|
* @internal Loader IDs ($$id) whose entries carry `awaitBeforeFlush`
|
|
623
|
-
* (loader(Def, {
|
|
623
|
+
* (loader(Def, { ssr: false })): segment resolution awaits these
|
|
624
624
|
* before returning, so the render barrier cannot resolve until they settle.
|
|
625
625
|
* rendered() checks this set to fail fast — a flagged loader awaiting the
|
|
626
626
|
* barrier is a guaranteed cycle, not a race. Registered by resolveLoaders
|
package/src/ssr/index.tsx
CHANGED
|
@@ -114,6 +114,14 @@ export interface SSRRenderOptions {
|
|
|
114
114
|
* only (runtime captures own the search variants, seeded per key).
|
|
115
115
|
*/
|
|
116
116
|
search?: string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The live request's origin, seeding the SSR store location so
|
|
120
|
+
* origin-dependent markup (Link's data-external) agrees with the
|
|
121
|
+
* browser's window.location across hydration. Absent on the build-time
|
|
122
|
+
* prerender pass (host-agnostic bare captures).
|
|
123
|
+
*/
|
|
124
|
+
origin?: string;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
/**
|
|
@@ -363,6 +371,13 @@ interface ShellCaptureOptions {
|
|
|
363
371
|
* the shell's own key. MUST equal the resume pass's seed for the same key.
|
|
364
372
|
*/
|
|
365
373
|
search?: string;
|
|
374
|
+
/**
|
|
375
|
+
* The capture request's origin. Shell keys are host-scoped, so the resume
|
|
376
|
+
* pass's request agrees modulo protocol drift; seeding it keeps
|
|
377
|
+
* origin-dependent static markup (Link's data-external) identical across
|
|
378
|
+
* capture, resume, and browser hydration.
|
|
379
|
+
*/
|
|
380
|
+
origin?: string;
|
|
366
381
|
}
|
|
367
382
|
|
|
368
383
|
/**
|
|
@@ -391,6 +406,8 @@ interface ShellResumeOptions {
|
|
|
391
406
|
* postponed holes.
|
|
392
407
|
*/
|
|
393
408
|
search?: string;
|
|
409
|
+
/** The HIT request's origin — same host as the capture's (key-scoped). */
|
|
410
|
+
origin?: string;
|
|
394
411
|
}
|
|
395
412
|
|
|
396
413
|
/**
|
|
@@ -472,7 +489,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
472
489
|
rscStream: ReadableStream<Uint8Array>,
|
|
473
490
|
options?: SSRRenderOptions,
|
|
474
491
|
): Promise<ReadableStream<Uint8Array>> {
|
|
475
|
-
const { nonce, formState, streamMode, search } = options ?? {};
|
|
492
|
+
const { nonce, formState, streamMode, search, origin } = options ?? {};
|
|
476
493
|
|
|
477
494
|
try {
|
|
478
495
|
// Tee the stream:
|
|
@@ -490,6 +507,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
490
507
|
// same key => same seed, so the resume tree matches the captured
|
|
491
508
|
// tree while static-part search reads render what the key names.
|
|
492
509
|
search,
|
|
510
|
+
origin,
|
|
493
511
|
});
|
|
494
512
|
|
|
495
513
|
// Get bootstrap script content
|
|
@@ -594,6 +612,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
594
612
|
// The shell key's own search: static-part search reads render what
|
|
595
613
|
// the key names, and the resume pass seeds the identical string.
|
|
596
614
|
search: opts.search,
|
|
615
|
+
origin: opts.origin,
|
|
597
616
|
});
|
|
598
617
|
|
|
599
618
|
// Bootstrap load raced against the deadline. A load that never resolves
|
|
@@ -767,7 +786,7 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
767
786
|
rscStream: ReadableStream<Uint8Array>,
|
|
768
787
|
opts: ShellResumeOptions,
|
|
769
788
|
): Promise<ReadableStream<Uint8Array>> {
|
|
770
|
-
const { postponed, nonce, search } = opts;
|
|
789
|
+
const { postponed, nonce, search, origin } = opts;
|
|
771
790
|
|
|
772
791
|
try {
|
|
773
792
|
if (postponed === null) {
|
|
@@ -800,6 +819,7 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
800
819
|
// Same seed as the capture pass for this key: the resume tree must
|
|
801
820
|
// match the captured tree above the holes, search reads included.
|
|
802
821
|
search,
|
|
822
|
+
origin,
|
|
803
823
|
});
|
|
804
824
|
|
|
805
825
|
// EAGER injection (resume-only): the stored prelude — a complete document
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -70,13 +70,22 @@ function createSsrEventController(opts: {
|
|
|
70
70
|
* names and both passes agree byte-for-byte.
|
|
71
71
|
*/
|
|
72
72
|
search?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Origin seeding the store location. Live fizz passes the request's real
|
|
75
|
+
* origin so origin-dependent markup (Link's data-external compares link
|
|
76
|
+
* origins against it) agrees with the browser's window.location; ppr
|
|
77
|
+
* capture/resume pass their request's origin (shell keys are host-scoped,
|
|
78
|
+
* so the two agree modulo protocol drift). Absent (build-time bare
|
|
79
|
+
* captures, tests) falls back to the historical internal host.
|
|
80
|
+
*/
|
|
81
|
+
origin?: string;
|
|
73
82
|
params?: Record<string, string>;
|
|
74
83
|
handleData?: HandleData;
|
|
75
84
|
matched?: string[];
|
|
76
85
|
}): EventController {
|
|
77
86
|
const location = new URL(
|
|
78
87
|
`${opts.pathname}${opts.search ?? ""}`,
|
|
79
|
-
"http://localhost",
|
|
88
|
+
opts.origin ?? "http://localhost",
|
|
80
89
|
);
|
|
81
90
|
let params = opts.params ?? {};
|
|
82
91
|
const rawMatched = opts.matched ?? [];
|
|
@@ -162,6 +171,11 @@ export interface SsrRootOptions {
|
|
|
162
171
|
* the resume tree matches the captured tree above the postponed holes.
|
|
163
172
|
*/
|
|
164
173
|
search?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Origin seeding the SSR store location (see createSsrEventController):
|
|
176
|
+
* live fizz passes the request's origin; capture/resume pass theirs.
|
|
177
|
+
*/
|
|
178
|
+
origin?: string;
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
/**
|
|
@@ -188,6 +202,7 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
|
|
|
188
202
|
nonce,
|
|
189
203
|
onPayloadSettled,
|
|
190
204
|
search,
|
|
205
|
+
origin,
|
|
191
206
|
} = opts;
|
|
192
207
|
|
|
193
208
|
let payload: Promise<RscPayload> | undefined;
|
|
@@ -238,6 +253,7 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
|
|
|
238
253
|
eventController: createSsrEventController({
|
|
239
254
|
pathname,
|
|
240
255
|
search,
|
|
256
|
+
origin,
|
|
241
257
|
params: resolved.metadata?.params,
|
|
242
258
|
handleData,
|
|
243
259
|
matched: resolved.metadata?.matched,
|
|
@@ -75,7 +75,7 @@ export type LoaderContext<
|
|
|
75
75
|
* later ones stream to the client and apply post-hydration (document lane)
|
|
76
76
|
* or progressively (navigation/action lanes). To guarantee a loader's
|
|
77
77
|
* handles are in the SSR'd document, register it as
|
|
78
|
-
* `loader(Def, {
|
|
78
|
+
* `loader(Def, { ssr: false })` so the document render awaits it
|
|
79
79
|
* (see {@link LoaderOptions}).
|
|
80
80
|
*
|
|
81
81
|
* @example
|
|
@@ -174,29 +174,31 @@ export type LoaderFn<
|
|
|
174
174
|
> = (ctx: LoaderContext<TParams, TEnv>) => Promise<T> | T;
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
*
|
|
177
|
+
* SSR delivery for a DSL-registered loader: `loader(Def, { ssr })`.
|
|
178
178
|
*
|
|
179
|
-
* Default (omitted): the loader streams on every render. Its data,
|
|
180
|
-
* `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
|
|
181
|
-
* AFTER the document Response is constructed, so none of them are
|
|
182
|
-
* be in the SSR'd HTML.
|
|
179
|
+
* Default (omitted / `true`): the loader streams on every render. Its data,
|
|
180
|
+
* its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
|
|
181
|
+
* may land AFTER the document Response is constructed, so none of them are
|
|
182
|
+
* guaranteed to be in the SSR'd HTML.
|
|
183
183
|
*
|
|
184
|
-
* `
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
184
|
+
* `ssr: false` turns SSR streaming off for this loader — the same knob
|
|
185
|
+
* `loading(fallback, { ssr: false })` is for the fallback: on a DOCUMENT
|
|
186
|
+
* request the loader is awaited before first flush, so no fallback paints for
|
|
187
|
+
* it (`useLoader` still suspends, but on an already-settled promise). Client
|
|
188
|
+
* navigations keep streaming it. This is the SSR-completeness opt-in. Choose
|
|
189
|
+
* it when the loader feeds something that must exist in the document:
|
|
190
|
+
* `<head>` meta via a handle, or a real 404 status (an awaited `notFound()`
|
|
191
|
+
* deterministically precedes Response construction, where the streamed
|
|
192
|
+
* default only wins that race opportunistically). Under ppr it is also the
|
|
193
|
+
* BAKE lane: the loader executes at shell capture and its settled return is
|
|
194
|
+
* shell material (nested promises stay live holes) — see the ppr docs.
|
|
193
195
|
*
|
|
194
|
-
* Scoped per LOADER, not per segment:
|
|
195
|
-
*
|
|
196
|
-
* `loading()`/Suspense boundary.
|
|
196
|
+
* Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
|
|
197
|
+
* deliberately streaming sibling awaits only itself, and the sibling keeps
|
|
198
|
+
* streaming behind its `loading()`/Suspense boundary.
|
|
197
199
|
*/
|
|
198
200
|
export type LoaderOptions = {
|
|
199
|
-
|
|
201
|
+
ssr?: boolean;
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
/**
|
|
@@ -347,13 +347,14 @@ export type PathHelpers<TEnv> = {
|
|
|
347
347
|
/**
|
|
348
348
|
* Attach a data loader to the current route/layout.
|
|
349
349
|
*
|
|
350
|
-
* Pass `{
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* holes) — the pre-flush
|
|
350
|
+
* Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
|
|
351
|
+
* false }) — to await this loader before first flush on DOCUMENT requests
|
|
352
|
+
* (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
|
|
353
|
+
* pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
|
|
354
|
+
* Per-loader: a streaming sibling keeps streaming. Under a `ppr` route the
|
|
355
|
+
* flag BAKES: the loader executes at shell capture and its settled return
|
|
356
|
+
* freezes into the shell (nested promises stay live holes) — the pre-flush
|
|
357
|
+
* promise applied to the prelude.
|
|
357
358
|
*/
|
|
358
359
|
loader: <TData>(
|
|
359
360
|
loaderDef: LoaderDefinition<TData>,
|