@rangojs/router 0.0.0-experimental.61 → 0.0.0-experimental.63
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 +61 -8
- package/dist/bin/rango.js +2 -1
- package/dist/vite/index.js +144 -62
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +14 -15
- package/skills/prerender/SKILL.md +110 -68
- package/src/__internal.ts +1 -1
- package/src/build/generate-manifest.ts +3 -6
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +8 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +2 -0
- package/src/route-definition/dsl-helpers.ts +37 -18
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-types.ts +11 -0
- package/src/router/handler-context.ts +22 -5
- package/src/router/match-api.ts +2 -8
- package/src/router/match-middleware/cache-lookup.ts +2 -6
- package/src/router/prerender-match.ts +104 -8
- package/src/router/router-interfaces.ts +4 -0
- package/src/router/segment-resolution/fresh.ts +7 -2
- package/src/router/segment-resolution/revalidation.ts +10 -5
- package/src/router.ts +9 -1
- package/src/server/context.ts +5 -1
- package/src/static-handler.ts +18 -6
- package/src/types/handler-context.ts +12 -2
- package/src/types/route-entry.ts +1 -1
- package/src/urls/path-helper-types.ts +5 -1
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/response-types.ts +16 -6
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/prerender-collection.ts +14 -1
- package/src/vite/discovery/state.ts +13 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- package/src/vite/rango.ts +2 -1
- package/src/vite/router-discovery.ts +153 -34
- package/src/vite/utils/prerender-utils.ts +2 -0
|
@@ -114,9 +114,9 @@ function createPrerenderPassthroughFn(
|
|
|
114
114
|
}
|
|
115
115
|
if (!isPassthroughRoute) {
|
|
116
116
|
throw new Error(
|
|
117
|
-
"ctx.passthrough() is only available on routes
|
|
118
|
-
"
|
|
119
|
-
"
|
|
117
|
+
"ctx.passthrough() is only available on routes wrapped with " +
|
|
118
|
+
"Passthrough(). Remove the passthrough() call or wrap the " +
|
|
119
|
+
"Prerender definition with Passthrough(prerenderDef, liveHandler).",
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
return PRERENDER_PASSTHROUGH;
|
|
@@ -195,7 +195,9 @@ export function createReverseFunction(
|
|
|
195
195
|
// Clean up slashes only when an optional param was actually omitted,
|
|
196
196
|
// so intentional trailing-slash patterns like "/blog/" are preserved.
|
|
197
197
|
if (hadOmittedOptional) {
|
|
198
|
+
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
198
199
|
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
200
|
+
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -270,6 +272,7 @@ export function createHandlerContext<TEnv>(
|
|
|
270
272
|
ctx = {
|
|
271
273
|
params,
|
|
272
274
|
build: false,
|
|
275
|
+
dev: false,
|
|
273
276
|
request,
|
|
274
277
|
searchParams,
|
|
275
278
|
search: searchSchema ? resolvedSearchParams : {},
|
|
@@ -349,6 +352,8 @@ export function createPrerenderContext<TEnv>(
|
|
|
349
352
|
routeName?: string,
|
|
350
353
|
buildVars?: Record<string, any>,
|
|
351
354
|
isPassthroughRoute?: boolean,
|
|
355
|
+
buildEnv?: TEnv,
|
|
356
|
+
devMode?: boolean,
|
|
352
357
|
): InternalHandlerContext<any, TEnv> {
|
|
353
358
|
const syntheticUrl = new URL(`http://prerender${pathname}`);
|
|
354
359
|
const variables = buildVars ?? {};
|
|
@@ -363,6 +368,7 @@ export function createPrerenderContext<TEnv>(
|
|
|
363
368
|
return {
|
|
364
369
|
params,
|
|
365
370
|
build: true,
|
|
371
|
+
dev: devMode ?? false,
|
|
366
372
|
get request(): Request {
|
|
367
373
|
return throwUnavailable("request");
|
|
368
374
|
},
|
|
@@ -372,7 +378,11 @@ export function createPrerenderContext<TEnv>(
|
|
|
372
378
|
url: syntheticUrl,
|
|
373
379
|
originalUrl: syntheticUrl,
|
|
374
380
|
get env(): TEnv {
|
|
375
|
-
|
|
381
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
382
|
+
throw new Error(
|
|
383
|
+
"ctx.env is not available during pre-rendering. " +
|
|
384
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
385
|
+
);
|
|
376
386
|
},
|
|
377
387
|
_variables: variables,
|
|
378
388
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
@@ -420,6 +430,8 @@ export function createPrerenderContext<TEnv>(
|
|
|
420
430
|
export function createStaticContext<TEnv>(
|
|
421
431
|
routeMap: Record<string, string>,
|
|
422
432
|
routeName?: string,
|
|
433
|
+
buildEnv?: TEnv,
|
|
434
|
+
devMode?: boolean,
|
|
423
435
|
): InternalHandlerContext<any, TEnv> {
|
|
424
436
|
const variables: Record<string, any> = {};
|
|
425
437
|
|
|
@@ -435,6 +447,7 @@ export function createStaticContext<TEnv>(
|
|
|
435
447
|
return throwUnavailable("params");
|
|
436
448
|
},
|
|
437
449
|
build: true,
|
|
450
|
+
dev: devMode ?? false,
|
|
438
451
|
get request(): Request {
|
|
439
452
|
return throwUnavailable("request");
|
|
440
453
|
},
|
|
@@ -454,7 +467,11 @@ export function createStaticContext<TEnv>(
|
|
|
454
467
|
return throwUnavailable("originalUrl");
|
|
455
468
|
},
|
|
456
469
|
get env(): TEnv {
|
|
457
|
-
|
|
470
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
471
|
+
throw new Error(
|
|
472
|
+
"ctx.env is not available in Static() handlers. " +
|
|
473
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
474
|
+
);
|
|
458
475
|
},
|
|
459
476
|
_variables: variables,
|
|
460
477
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
package/src/router/match-api.ts
CHANGED
|
@@ -91,10 +91,7 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
if (
|
|
95
|
-
manifestEntry.type === "route" &&
|
|
96
|
-
manifestEntry.prerenderDef?.options?.passthrough === true
|
|
97
|
-
) {
|
|
94
|
+
if (manifestEntry.type === "route" && manifestEntry.isPassthrough === true) {
|
|
98
95
|
matched.pt = true;
|
|
99
96
|
}
|
|
100
97
|
|
|
@@ -289,10 +286,7 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
289
286
|
});
|
|
290
287
|
}
|
|
291
288
|
|
|
292
|
-
if (
|
|
293
|
-
manifestEntry.type === "route" &&
|
|
294
|
-
manifestEntry.prerenderDef?.options?.passthrough === true
|
|
295
|
-
) {
|
|
289
|
+
if (manifestEntry.type === "route" && manifestEntry.isPassthrough === true) {
|
|
296
290
|
matched.pt = true;
|
|
297
291
|
}
|
|
298
292
|
|
|
@@ -324,9 +324,7 @@ export function withCacheLookup<TEnv>(
|
|
|
324
324
|
if (prerenderStoreInstance) {
|
|
325
325
|
const paramHash = _hashParams!(ctx.matched.params);
|
|
326
326
|
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
327
|
-
(entry) =>
|
|
328
|
-
entry.type === "route" &&
|
|
329
|
-
entry.prerenderDef?.options?.passthrough === true,
|
|
327
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
330
328
|
);
|
|
331
329
|
|
|
332
330
|
if (ctx.isIntercept) {
|
|
@@ -396,9 +394,7 @@ export function withCacheLookup<TEnv>(
|
|
|
396
394
|
if (prerenderStoreInstance) {
|
|
397
395
|
const paramHash = _hashParams!(ctx.matched.params);
|
|
398
396
|
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
399
|
-
(entry) =>
|
|
400
|
-
entry.type === "route" &&
|
|
401
|
-
entry.prerenderDef?.options?.passthrough === true,
|
|
397
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
402
398
|
);
|
|
403
399
|
|
|
404
400
|
if (ctx.isIntercept) {
|
|
@@ -54,6 +54,9 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
54
54
|
deps: PrerenderMatchDeps<TEnv>,
|
|
55
55
|
buildVars?: Record<string, any>,
|
|
56
56
|
isPassthroughRoute?: boolean,
|
|
57
|
+
buildEnv?: TEnv,
|
|
58
|
+
/** Dev-only: check getParams() for passthrough routes to skip unknown params. */
|
|
59
|
+
devMode?: boolean,
|
|
57
60
|
): Promise<{
|
|
58
61
|
segments: SerializedSegmentData[];
|
|
59
62
|
handles: Record<string, SegmentHandleData>;
|
|
@@ -90,15 +93,100 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
90
93
|
entries.push(entry);
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
// 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
|
|
97
|
+
// and has getParams(), check if the matched params are in the known list.
|
|
98
|
+
// In production, only known params are pre-rendered; unknown params fall
|
|
99
|
+
// through to the live handler. Mirror that behavior in dev mode to avoid
|
|
100
|
+
// rendering unknown params with build: true.
|
|
101
|
+
// Vars collected from getParams() probe — merged into render context below.
|
|
102
|
+
let devProbeBuildVars: Record<string, any> | undefined;
|
|
103
|
+
|
|
104
|
+
if (devMode && matchedPassthroughRoute) {
|
|
105
|
+
const routeEntry = entries.find(
|
|
106
|
+
(
|
|
107
|
+
e,
|
|
108
|
+
): e is EntryData & {
|
|
109
|
+
type: "route";
|
|
110
|
+
prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
|
|
111
|
+
} =>
|
|
112
|
+
e.type === "route" &&
|
|
113
|
+
!!(e as any).isPassthrough &&
|
|
114
|
+
!!(e as any).prerenderDef?.getParams,
|
|
115
|
+
);
|
|
116
|
+
if (routeEntry) {
|
|
117
|
+
try {
|
|
118
|
+
const probeBuildVars: Record<string, any> = {};
|
|
119
|
+
const knownParamsList = await routeEntry.prerenderDef.getParams({
|
|
120
|
+
build: true as const,
|
|
121
|
+
dev: true,
|
|
122
|
+
set: ((keyOrVar: any, value: any) => {
|
|
123
|
+
contextSet(probeBuildVars, keyOrVar, value);
|
|
124
|
+
}) as any,
|
|
125
|
+
reverse: createReverseFunction(deps.mergedRouteMap),
|
|
126
|
+
get env() {
|
|
127
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
128
|
+
throw new Error(
|
|
129
|
+
"[rsc-router] ctx.env is not available during dev-mode getParams(). " +
|
|
130
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// Compare only the keys returned by getParams — ignore mount params
|
|
135
|
+
// from include() prefixes that aren't part of the handler's params.
|
|
136
|
+
const isKnown = knownParamsList.some((known: Record<string, any>) => {
|
|
137
|
+
const knownKeys = Object.keys(known);
|
|
138
|
+
return knownKeys.every(
|
|
139
|
+
(k) => String(known[k]) === String(matchedParams[k]),
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
if (!isKnown) {
|
|
143
|
+
return {
|
|
144
|
+
segments: [],
|
|
145
|
+
handles: {},
|
|
146
|
+
routeName: matched.routeKey,
|
|
147
|
+
params: matchedParams,
|
|
148
|
+
passthrough: true as const,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Preserve vars set by getParams() for the render context
|
|
152
|
+
if (
|
|
153
|
+
Object.keys(probeBuildVars).length > 0 ||
|
|
154
|
+
Object.getOwnPropertySymbols(probeBuildVars).length > 0
|
|
155
|
+
) {
|
|
156
|
+
devProbeBuildVars = probeBuildVars;
|
|
157
|
+
}
|
|
158
|
+
} catch (err: any) {
|
|
159
|
+
// Mirror production semantics (prerender-collection.ts):
|
|
160
|
+
// Skip errors are intentional — treat as passthrough.
|
|
161
|
+
// All other errors propagate so dev surfaces them.
|
|
162
|
+
if (err?.name === "Skip") {
|
|
163
|
+
return {
|
|
164
|
+
segments: [],
|
|
165
|
+
handles: {},
|
|
166
|
+
routeName: matched.routeKey,
|
|
167
|
+
params: matchedParams,
|
|
168
|
+
passthrough: true as const,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
93
176
|
// 4. Create handle store for collecting handle data
|
|
94
177
|
const handleStore = createHandleStore();
|
|
95
178
|
|
|
96
179
|
// 5. Create a minimal request context with the handle store
|
|
97
|
-
// Shallow-copy getParams vars so each param set is independent
|
|
98
|
-
|
|
180
|
+
// Shallow-copy getParams vars so each param set is independent.
|
|
181
|
+
// In dev mode, merge vars from the getParams() probe if the caller
|
|
182
|
+
// didn't provide buildVars (production passes them from expandPrerenderRoutes).
|
|
183
|
+
const effectiveBuildVars = buildVars ?? devProbeBuildVars;
|
|
184
|
+
const variables: Record<string, any> = effectiveBuildVars
|
|
185
|
+
? { ...effectiveBuildVars }
|
|
186
|
+
: {};
|
|
99
187
|
const stubRes = new Response(null, { status: 200 });
|
|
100
188
|
const minimalRequestContext: RequestContext<TEnv> = {
|
|
101
|
-
env: {} as TEnv,
|
|
189
|
+
env: buildEnv ?? ({} as TEnv),
|
|
102
190
|
request: new Request("http://prerender" + pathname),
|
|
103
191
|
url: new URL("http://prerender" + pathname),
|
|
104
192
|
originalUrl: new URL("http://prerender" + pathname),
|
|
@@ -140,7 +228,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
140
228
|
return runWithRequestContext(minimalRequestContext, async () => {
|
|
141
229
|
// 6. Create prerender context with synthetic URL.
|
|
142
230
|
// Prerender handlers get params, pathname, url, searchParams, search,
|
|
143
|
-
// reverse,
|
|
231
|
+
// reverse, use(handle), and optionally env (when buildEnv is configured).
|
|
144
232
|
const buildCtx = createPrerenderContext<TEnv>(
|
|
145
233
|
matchedParams,
|
|
146
234
|
pathname,
|
|
@@ -148,6 +236,8 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
148
236
|
matched.routeKey,
|
|
149
237
|
variables,
|
|
150
238
|
matchedPassthroughRoute,
|
|
239
|
+
buildEnv,
|
|
240
|
+
devMode,
|
|
151
241
|
);
|
|
152
242
|
|
|
153
243
|
// 7. Wire use() for handles only (loaders throw)
|
|
@@ -320,6 +410,8 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
320
410
|
handlerId: string,
|
|
321
411
|
mergedRouteMap: Record<string, string>,
|
|
322
412
|
routeName?: string,
|
|
413
|
+
buildEnv?: TEnv,
|
|
414
|
+
devMode?: boolean,
|
|
323
415
|
): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
|
|
324
416
|
const syntheticUrl = new URL("http://prerender/");
|
|
325
417
|
const syntheticRequest = new Request(syntheticUrl);
|
|
@@ -330,7 +422,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
330
422
|
// Minimal request context so setupBuildUse can find the HandleStore
|
|
331
423
|
const stubRes = new Response(null, { status: 200 });
|
|
332
424
|
const minimalRequestContext: RequestContext<TEnv> = {
|
|
333
|
-
env: {} as TEnv,
|
|
425
|
+
env: buildEnv ?? ({} as TEnv),
|
|
334
426
|
request: syntheticRequest,
|
|
335
427
|
url: syntheticUrl,
|
|
336
428
|
originalUrl: syntheticUrl,
|
|
@@ -368,9 +460,13 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
368
460
|
};
|
|
369
461
|
|
|
370
462
|
return runWithRequestContext(minimalRequestContext, async () => {
|
|
371
|
-
// Static handlers get only reverse
|
|
372
|
-
|
|
373
|
-
|
|
463
|
+
// Static handlers get only reverse, use(handle), and optionally env.
|
|
464
|
+
const buildCtx = createStaticContext<TEnv>(
|
|
465
|
+
mergedRouteMap,
|
|
466
|
+
routeName,
|
|
467
|
+
buildEnv,
|
|
468
|
+
devMode,
|
|
469
|
+
);
|
|
374
470
|
|
|
375
471
|
// Set segment ID so handle pushes are keyed correctly
|
|
376
472
|
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
@@ -374,6 +374,8 @@ export interface RSCRouterInternal<
|
|
|
374
374
|
params: Record<string, string>,
|
|
375
375
|
buildVars?: Record<string, any>,
|
|
376
376
|
isPassthroughRoute?: boolean,
|
|
377
|
+
buildEnv?: any,
|
|
378
|
+
devMode?: boolean,
|
|
377
379
|
): Promise<{
|
|
378
380
|
segments: SerializedSegmentData[];
|
|
379
381
|
handles: Record<string, SegmentHandleData>;
|
|
@@ -392,6 +394,8 @@ export interface RSCRouterInternal<
|
|
|
392
394
|
handler: Function,
|
|
393
395
|
handlerId: string,
|
|
394
396
|
routeName?: string,
|
|
397
|
+
buildEnv?: any,
|
|
398
|
+
devMode?: boolean,
|
|
395
399
|
): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null>;
|
|
396
400
|
|
|
397
401
|
/**
|
|
@@ -284,9 +284,14 @@ export async function resolveSegment<TEnv>(
|
|
|
284
284
|
entry.shortCode,
|
|
285
285
|
);
|
|
286
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;
|
|
287
292
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
288
293
|
if (entry.loading) {
|
|
289
|
-
const result = handleHandlerResult(
|
|
294
|
+
const result = handleHandlerResult(handler(context));
|
|
290
295
|
if (result instanceof Promise) {
|
|
291
296
|
result.finally(doneRouteHandler).catch(() => {});
|
|
292
297
|
const tracked = deps.trackHandler(result, {
|
|
@@ -307,7 +312,7 @@ export async function resolveSegment<TEnv>(
|
|
|
307
312
|
component = result;
|
|
308
313
|
}
|
|
309
314
|
} else {
|
|
310
|
-
component = handleHandlerResult(await
|
|
315
|
+
component = handleHandlerResult(await handler(context));
|
|
311
316
|
doneRouteHandler();
|
|
312
317
|
}
|
|
313
318
|
}
|
|
@@ -688,13 +688,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
688
688
|
return staticComponent;
|
|
689
689
|
}
|
|
690
690
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
691
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
692
|
+
// the build handler. At build time (context.build === true), always
|
|
693
|
+
// use the build handler from routeEntry.handler.
|
|
694
|
+
const handler =
|
|
695
|
+
!context.build && routeEntry.liveHandler
|
|
696
|
+
? routeEntry.liveHandler
|
|
697
|
+
: routeEntry.handler;
|
|
691
698
|
if (!routeEntry.loading) {
|
|
692
|
-
const result = handleHandlerResult(await
|
|
699
|
+
const result = handleHandlerResult(await handler(context));
|
|
693
700
|
doneHandler();
|
|
694
701
|
return result;
|
|
695
702
|
}
|
|
696
703
|
if (!actionContext) {
|
|
697
|
-
const result = handleHandlerResult(
|
|
704
|
+
const result = handleHandlerResult(handler(context));
|
|
698
705
|
if (result instanceof Promise) {
|
|
699
706
|
result.finally(doneHandler).catch(() => {});
|
|
700
707
|
const tracked = deps.trackHandler(result, {
|
|
@@ -717,9 +724,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
717
724
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
718
725
|
entryId: entry.id,
|
|
719
726
|
});
|
|
720
|
-
const actionResult = handleHandlerResult(
|
|
721
|
-
await routeEntry.handler(context),
|
|
722
|
-
);
|
|
727
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
723
728
|
doneHandler();
|
|
724
729
|
return {
|
|
725
730
|
content: Promise.resolve(actionResult),
|
package/src/router.ts
CHANGED
|
@@ -625,6 +625,8 @@ export function createRouter<TEnv = any>(
|
|
|
625
625
|
params: Record<string, string>,
|
|
626
626
|
buildVars?: Record<string, any>,
|
|
627
627
|
isPassthroughRoute?: boolean,
|
|
628
|
+
buildEnv?: TEnv,
|
|
629
|
+
devMode?: boolean,
|
|
628
630
|
) {
|
|
629
631
|
return _matchForPrerender(
|
|
630
632
|
pathname,
|
|
@@ -632,6 +634,8 @@ export function createRouter<TEnv = any>(
|
|
|
632
634
|
prerenderDeps,
|
|
633
635
|
buildVars,
|
|
634
636
|
isPassthroughRoute,
|
|
637
|
+
buildEnv,
|
|
638
|
+
devMode,
|
|
635
639
|
);
|
|
636
640
|
}
|
|
637
641
|
|
|
@@ -639,12 +643,16 @@ export function createRouter<TEnv = any>(
|
|
|
639
643
|
handler: Function,
|
|
640
644
|
handlerId: string,
|
|
641
645
|
routeName?: string,
|
|
646
|
+
buildEnv?: TEnv,
|
|
647
|
+
devMode?: boolean,
|
|
642
648
|
) {
|
|
643
649
|
return _renderStaticSegment<TEnv>(
|
|
644
650
|
handler,
|
|
645
651
|
handlerId,
|
|
646
652
|
mergedRouteMap,
|
|
647
653
|
routeName,
|
|
654
|
+
buildEnv,
|
|
655
|
+
devMode,
|
|
648
656
|
);
|
|
649
657
|
}
|
|
650
658
|
|
|
@@ -748,7 +756,7 @@ export function createRouter<TEnv = any>(
|
|
|
748
756
|
if (entry.type === "route" && entry.isPrerender) {
|
|
749
757
|
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
750
758
|
prerenderRouteKeys.add(name);
|
|
751
|
-
if (entry.
|
|
759
|
+
if (entry.isPassthrough === true) {
|
|
752
760
|
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
753
761
|
passthroughRouteKeys.add(name);
|
|
754
762
|
}
|
package/src/server/context.ts
CHANGED
|
@@ -191,8 +191,12 @@ export type EntryData =
|
|
|
191
191
|
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
192
192
|
prerenderDef?: {
|
|
193
193
|
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
194
|
-
options?: {
|
|
194
|
+
options?: { concurrency?: number };
|
|
195
195
|
};
|
|
196
|
+
/** Set when route is wrapped with Passthrough() — has a separate live handler */
|
|
197
|
+
isPassthrough?: true;
|
|
198
|
+
/** Live handler for runtime fallback (only set on Passthrough routes) */
|
|
199
|
+
liveHandler?: Handler<any, any, any>;
|
|
196
200
|
/** Set when handler is a Static definition (build-time only) */
|
|
197
201
|
isStaticPrerender?: true;
|
|
198
202
|
/** Static handler $$id for build-time store lookup */
|
package/src/static-handler.ts
CHANGED
|
@@ -32,11 +32,21 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { ReactNode } from "react";
|
|
34
34
|
import type { Handler } from "./types.js";
|
|
35
|
-
import type {
|
|
35
|
+
import type { StaticBuildContext } from "./prerender.js";
|
|
36
|
+
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
36
37
|
import { isCachedFunction } from "./cache/taint.js";
|
|
37
38
|
|
|
38
39
|
// -- Types ------------------------------------------------------------------
|
|
39
40
|
|
|
41
|
+
export interface StaticHandlerOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Keep handler in server bundle for live fallback (default: false).
|
|
44
|
+
* false: handler replaced with stub, source-only APIs excluded from bundle.
|
|
45
|
+
* true: handler stays in bundle, renders live at request time.
|
|
46
|
+
*/
|
|
47
|
+
passthrough?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
export interface StaticHandlerDefinition<
|
|
41
51
|
TParams extends Record<string, any> = any,
|
|
42
52
|
> {
|
|
@@ -46,14 +56,16 @@ export interface StaticHandlerDefinition<
|
|
|
46
56
|
/** In dev mode, the actual handler function that layout/path/parallel can call. */
|
|
47
57
|
handler: Handler<TParams>;
|
|
48
58
|
/** Static handler options (passthrough support). */
|
|
49
|
-
options?:
|
|
59
|
+
options?: StaticHandlerOptions;
|
|
60
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
61
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
// -- Function ---------------------------------------------------------------
|
|
53
65
|
|
|
54
66
|
export function Static<TParams extends Record<string, any> = {}>(
|
|
55
67
|
handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
|
|
56
|
-
options?:
|
|
68
|
+
options?: StaticHandlerOptions,
|
|
57
69
|
__injectedId?: string,
|
|
58
70
|
): StaticHandlerDefinition<TParams>;
|
|
59
71
|
|
|
@@ -61,7 +73,7 @@ export function Static<TParams extends Record<string, any> = {}>(
|
|
|
61
73
|
|
|
62
74
|
export function Static<TParams extends Record<string, any>>(
|
|
63
75
|
handler: Function,
|
|
64
|
-
optionsOrId?:
|
|
76
|
+
optionsOrId?: StaticHandlerOptions | string,
|
|
65
77
|
maybeId?: string,
|
|
66
78
|
): StaticHandlerDefinition<TParams> {
|
|
67
79
|
if (isCachedFunction(handler)) {
|
|
@@ -72,13 +84,13 @@ export function Static<TParams extends Record<string, any>>(
|
|
|
72
84
|
);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
let options:
|
|
87
|
+
let options: StaticHandlerOptions | undefined;
|
|
76
88
|
let id: string;
|
|
77
89
|
|
|
78
90
|
if (typeof optionsOrId === "string") {
|
|
79
91
|
id = optionsOrId;
|
|
80
92
|
} else {
|
|
81
|
-
options = optionsOrId as
|
|
93
|
+
options = optionsOrId as StaticHandlerOptions | undefined;
|
|
82
94
|
id = maybeId ?? "";
|
|
83
95
|
}
|
|
84
96
|
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
ResolvedRouteMap,
|
|
20
20
|
} from "./route-config.js";
|
|
21
21
|
import type { LoaderDefinition } from "./loader-types.js";
|
|
22
|
+
import type { UseItems, HandlerUseItem } from "../route-types.js";
|
|
22
23
|
|
|
23
24
|
// Re-export MiddlewareFn for internal/advanced use
|
|
24
25
|
export type { MiddlewareFn } from "../router/middleware.js";
|
|
@@ -135,7 +136,7 @@ export type Handler<
|
|
|
135
136
|
| Record<string, any> = {},
|
|
136
137
|
TRouteMap extends {} = DefaultHandlerRouteMap,
|
|
137
138
|
TEnv = DefaultEnv,
|
|
138
|
-
> = (
|
|
139
|
+
> = ((
|
|
139
140
|
ctx: HandlerContext<
|
|
140
141
|
T extends `.${infer Local}`
|
|
141
142
|
? Local extends keyof TRouteMap
|
|
@@ -160,7 +161,10 @@ export type Handler<
|
|
|
160
161
|
: ExtractSearchFromEntry<DefaultHandlerRouteMap, T>,
|
|
161
162
|
TRouteMap extends DefaultHandlerRouteMap ? never : TRouteMap
|
|
162
163
|
>,
|
|
163
|
-
) => ReactNode | Promise<ReactNode> | Response | Promise<Response
|
|
164
|
+
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>) & {
|
|
165
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
166
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
167
|
+
};
|
|
164
168
|
|
|
165
169
|
/**
|
|
166
170
|
* Context passed to handlers (Hono-inspired type-safe context)
|
|
@@ -205,6 +209,12 @@ export type HandlerContext<
|
|
|
205
209
|
* Live request rendering, including passthrough fallback, uses `false`.
|
|
206
210
|
*/
|
|
207
211
|
build: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* True when running in Vite dev mode, false during production build or
|
|
214
|
+
* live request rendering. Use this to branch on runtime mode without
|
|
215
|
+
* changing build semantics (e.g., skip expensive operations in dev).
|
|
216
|
+
*/
|
|
217
|
+
dev: boolean;
|
|
208
218
|
/**
|
|
209
219
|
* The original incoming Request object (transport URL intact).
|
|
210
220
|
* Use `ctx.url` / `ctx.searchParams` for application logic — those have
|
package/src/types/route-entry.ts
CHANGED
|
@@ -69,7 +69,7 @@ export interface RouteEntry<TEnv = any> {
|
|
|
69
69
|
prerenderRouteKeys?: Set<string>;
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Route keys in this entry that
|
|
72
|
+
* Route keys in this entry that are wrapped with `Passthrough()`.
|
|
73
73
|
* Used by the non-trie match path to set the `pt` flag.
|
|
74
74
|
*/
|
|
75
75
|
passthroughRouteKeys?: Set<string>;
|
|
@@ -37,7 +37,10 @@ import type {
|
|
|
37
37
|
UseItems,
|
|
38
38
|
} from "../route-types.js";
|
|
39
39
|
import type { SearchSchema } from "../search-params.js";
|
|
40
|
-
import type {
|
|
40
|
+
import type {
|
|
41
|
+
PrerenderHandlerDefinition,
|
|
42
|
+
PassthroughHandlerDefinition,
|
|
43
|
+
} from "../prerender.js";
|
|
41
44
|
import type { StaticHandlerDefinition } from "../static-handler.js";
|
|
42
45
|
import type { InterceptWhenFn } from "../server/context";
|
|
43
46
|
import type {
|
|
@@ -70,6 +73,7 @@ export type PathFn<TEnv> = <
|
|
|
70
73
|
ctx: HandlerContext<TParams, TEnv, TSearch>,
|
|
71
74
|
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>)
|
|
72
75
|
| PrerenderHandlerDefinition<TParams>
|
|
76
|
+
| PassthroughHandlerDefinition<TParams, TEnv>
|
|
73
77
|
| StaticHandlerDefinition<TParams>,
|
|
74
78
|
optionsOrUse?: PathOptions<TName, TSearch> | (() => UseItems<RouteUseItem>),
|
|
75
79
|
use?: () => UseItems<RouteUseItem>,
|