@rangojs/router 0.0.0-experimental.55 → 0.0.0-experimental.56
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/bin/rango.js +128 -46
- package/dist/vite/index.js +119 -43
- package/package.json +1 -1
- package/skills/links/SKILL.md +3 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/router-setup/SKILL.md +35 -0
- package/src/browser/navigation-bridge.ts +6 -0
- package/src/browser/navigation-client.ts +4 -0
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +17 -1
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/react/Link.tsx +43 -8
- package/src/browser/react/NavigationProvider.tsx +7 -0
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/use-router.ts +20 -8
- package/src/browser/rsc-router.tsx +8 -0
- package/src/browser/server-action-bridge.ts +5 -0
- package/src/browser/types.ts +18 -4
- package/src/build/generate-manifest.ts +3 -0
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +211 -72
- package/src/route-definition/redirect.ts +9 -1
- package/src/router/handler-context.ts +5 -9
- package/src/router/intercept-resolution.ts +6 -2
- package/src/router/loader-resolution.ts +3 -2
- package/src/router/middleware-types.ts +0 -6
- package/src/router/middleware.ts +0 -3
- package/src/router/prerender-match.ts +2 -2
- package/src/router/router-interfaces.ts +25 -4
- package/src/router/router-options.ts +37 -11
- package/src/router.ts +40 -4
- package/src/rsc/handler.ts +10 -1
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/rsc-rendering.ts +5 -0
- package/src/rsc/server-action.ts +2 -0
- package/src/rsc/ssr-setup.ts +1 -1
- package/src/rsc/types.ts +5 -0
- package/src/server/request-context.ts +8 -4
- package/src/ssr/index.tsx +3 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +5 -9
- package/src/types/loader-types.ts +0 -1
- package/src/urls/pattern-types.ts +12 -0
- package/src/vite/discovery/discover-routers.ts +5 -1
|
@@ -157,13 +157,26 @@ export function formatNestedRouterConflictError(
|
|
|
157
157
|
// ---------------------------------------------------------------------------
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
160
|
+
* Result of extracting URL patterns from a router file.
|
|
161
|
+
* - "variable": a named variable reference (e.g., `.routes(patterns)` or `urls: patterns`)
|
|
162
|
+
* - "inline": an inline builder function (e.g., `.routes(({ path }) => [...])` or `urls: ({ path }) => [...]`)
|
|
163
|
+
*/
|
|
164
|
+
export type UrlsExtractionResult =
|
|
165
|
+
| { kind: "variable"; name: string }
|
|
166
|
+
| { kind: "inline"; block: string };
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract the url patterns from a router file using AST.
|
|
170
|
+
* Detects four patterns:
|
|
162
171
|
* 1. createRouter(...).routes(variableName)
|
|
163
172
|
* 2. createRouter({ urls: variableName, ... })
|
|
164
|
-
*
|
|
173
|
+
* 3. createRouter(...).routes(({ path, ... }) => [...])
|
|
174
|
+
* 4. createRouter({ urls: ({ path, ... }) => [...], ... })
|
|
175
|
+
* Returns either a variable name or an inline code block.
|
|
165
176
|
*/
|
|
166
|
-
export function
|
|
177
|
+
export function extractUrlsFromRouter(
|
|
178
|
+
code: string,
|
|
179
|
+
): UrlsExtractionResult | null {
|
|
167
180
|
const sourceFile = ts.createSourceFile(
|
|
168
181
|
"router.tsx",
|
|
169
182
|
code,
|
|
@@ -171,7 +184,7 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
171
184
|
true,
|
|
172
185
|
ts.ScriptKind.TSX,
|
|
173
186
|
);
|
|
174
|
-
let result:
|
|
187
|
+
let result: UrlsExtractionResult | null = null;
|
|
175
188
|
|
|
176
189
|
function isCreateRouterCall(node: ts.Node): boolean {
|
|
177
190
|
if (!ts.isCallExpression(node)) return false;
|
|
@@ -179,44 +192,108 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
179
192
|
return ts.isIdentifier(callee) && callee.text === "createRouter";
|
|
180
193
|
}
|
|
181
194
|
|
|
195
|
+
/** Check if a node is an arrow/function expression (inline builder). */
|
|
196
|
+
function isInlineBuilder(node: ts.Node): boolean {
|
|
197
|
+
return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Check if a .routes() call chains from createRouter(). */
|
|
201
|
+
function isRoutesOnCreateRouter(node: ts.CallExpression): boolean {
|
|
202
|
+
if (
|
|
203
|
+
!ts.isPropertyAccessExpression(node.expression) ||
|
|
204
|
+
node.expression.name.text !== "routes"
|
|
205
|
+
)
|
|
206
|
+
return false;
|
|
207
|
+
let inner: ts.Expression = node.expression.expression;
|
|
208
|
+
while (
|
|
209
|
+
ts.isCallExpression(inner) &&
|
|
210
|
+
ts.isPropertyAccessExpression(inner.expression)
|
|
211
|
+
) {
|
|
212
|
+
inner = inner.expression.expression;
|
|
213
|
+
}
|
|
214
|
+
return isCreateRouterCall(inner);
|
|
215
|
+
}
|
|
216
|
+
|
|
182
217
|
function visit(node: ts.Node) {
|
|
183
218
|
if (result) return;
|
|
184
219
|
|
|
185
|
-
// Pattern 1: createRouter(...).routes(variableName)
|
|
186
|
-
// The AST shape is CallExpression(.routes) -> PropertyAccessExpression -> CallExpression(createRouter)
|
|
220
|
+
// Pattern 1 & 3: createRouter(...).routes(variableName | builder)
|
|
187
221
|
if (
|
|
188
222
|
ts.isCallExpression(node) &&
|
|
189
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
190
|
-
node.expression.name.text === "routes" &&
|
|
191
223
|
node.arguments.length >= 1 &&
|
|
192
|
-
|
|
224
|
+
isRoutesOnCreateRouter(node)
|
|
193
225
|
) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
ts.isPropertyAccessExpression(inner.expression)
|
|
200
|
-
) {
|
|
201
|
-
inner = inner.expression.expression;
|
|
202
|
-
}
|
|
203
|
-
if (isCreateRouterCall(inner)) {
|
|
204
|
-
result = (node.arguments[0] as ts.Identifier).text;
|
|
205
|
-
return;
|
|
226
|
+
const arg = node.arguments[0];
|
|
227
|
+
if (ts.isIdentifier(arg)) {
|
|
228
|
+
result = { kind: "variable", name: arg.text };
|
|
229
|
+
} else if (isInlineBuilder(arg)) {
|
|
230
|
+
result = { kind: "inline", block: arg.getText(sourceFile) };
|
|
206
231
|
}
|
|
232
|
+
return;
|
|
207
233
|
}
|
|
208
234
|
|
|
209
|
-
// Pattern 2: createRouter({ urls: variableName, ... })
|
|
235
|
+
// Pattern 2 & 4: createRouter({ urls: variableName | builder, ... })
|
|
210
236
|
if (isCreateRouterCall(node)) {
|
|
211
237
|
const callExpr = node as ts.CallExpression;
|
|
212
|
-
for (const
|
|
238
|
+
for (const callArg of callExpr.arguments) {
|
|
239
|
+
if (ts.isObjectLiteralExpression(callArg)) {
|
|
240
|
+
for (const prop of callArg.properties) {
|
|
241
|
+
if (
|
|
242
|
+
ts.isPropertyAssignment(prop) &&
|
|
243
|
+
ts.isIdentifier(prop.name) &&
|
|
244
|
+
prop.name.text === "urls"
|
|
245
|
+
) {
|
|
246
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
247
|
+
result = { kind: "variable", name: prop.initializer.text };
|
|
248
|
+
} else if (isInlineBuilder(prop.initializer)) {
|
|
249
|
+
result = {
|
|
250
|
+
kind: "inline",
|
|
251
|
+
block: prop.initializer.getText(sourceFile),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
ts.forEachChild(node, visit);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
visit(sourceFile);
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Extract the `basename` string literal from createRouter({ basename: "..." }).
|
|
270
|
+
* Returns the basename value or undefined if not present.
|
|
271
|
+
*/
|
|
272
|
+
export function extractBasenameFromRouter(code: string): string | undefined {
|
|
273
|
+
const sourceFile = ts.createSourceFile(
|
|
274
|
+
"router.tsx",
|
|
275
|
+
code,
|
|
276
|
+
ts.ScriptTarget.Latest,
|
|
277
|
+
true,
|
|
278
|
+
ts.ScriptKind.TSX,
|
|
279
|
+
);
|
|
280
|
+
let result: string | undefined;
|
|
281
|
+
|
|
282
|
+
function visit(node: ts.Node) {
|
|
283
|
+
if (result !== undefined) return;
|
|
284
|
+
if (
|
|
285
|
+
ts.isCallExpression(node) &&
|
|
286
|
+
ts.isIdentifier(node.expression) &&
|
|
287
|
+
node.expression.text === "createRouter"
|
|
288
|
+
) {
|
|
289
|
+
for (const arg of node.arguments) {
|
|
213
290
|
if (ts.isObjectLiteralExpression(arg)) {
|
|
214
291
|
for (const prop of arg.properties) {
|
|
215
292
|
if (
|
|
216
293
|
ts.isPropertyAssignment(prop) &&
|
|
217
294
|
ts.isIdentifier(prop.name) &&
|
|
218
|
-
prop.name.text === "
|
|
219
|
-
ts.
|
|
295
|
+
prop.name.text === "basename" &&
|
|
296
|
+
ts.isStringLiteral(prop.initializer)
|
|
220
297
|
) {
|
|
221
298
|
result = prop.initializer.text;
|
|
222
299
|
return;
|
|
@@ -225,7 +302,6 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
225
302
|
}
|
|
226
303
|
}
|
|
227
304
|
}
|
|
228
|
-
|
|
229
305
|
ts.forEachChild(node, visit);
|
|
230
306
|
}
|
|
231
307
|
|
|
@@ -233,9 +309,40 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
233
309
|
return result;
|
|
234
310
|
}
|
|
235
311
|
|
|
312
|
+
/** @deprecated Use extractUrlsFromRouter instead */
|
|
313
|
+
export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
314
|
+
const result = extractUrlsFromRouter(code);
|
|
315
|
+
return result?.kind === "variable" ? result.name : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Apply a basename prefix to all route patterns in a result set. */
|
|
319
|
+
function applyBasenameToRoutes(
|
|
320
|
+
result: {
|
|
321
|
+
routes: Record<string, string>;
|
|
322
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
323
|
+
},
|
|
324
|
+
basename: string,
|
|
325
|
+
): {
|
|
326
|
+
routes: Record<string, string>;
|
|
327
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
328
|
+
} {
|
|
329
|
+
const prefixed: Record<string, string> = {};
|
|
330
|
+
for (const [name, pattern] of Object.entries(result.routes)) {
|
|
331
|
+
if (pattern === "/") {
|
|
332
|
+
prefixed[name] = basename;
|
|
333
|
+
} else if (basename.endsWith("/") && pattern.startsWith("/")) {
|
|
334
|
+
prefixed[name] = basename + pattern.slice(1);
|
|
335
|
+
} else {
|
|
336
|
+
prefixed[name] = basename + pattern;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { routes: prefixed, searchSchemas: result.searchSchemas };
|
|
340
|
+
}
|
|
341
|
+
|
|
236
342
|
/**
|
|
237
343
|
* Resolve routes and search schemas from a router source file by following the
|
|
238
|
-
* variable passed to `.routes(...)` or `urls: ...` in createRouter options
|
|
344
|
+
* variable passed to `.routes(...)` or `urls: ...` in createRouter options,
|
|
345
|
+
* or by parsing an inline builder function directly.
|
|
239
346
|
*/
|
|
240
347
|
export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
|
|
241
348
|
routes: Record<string, string>;
|
|
@@ -248,21 +355,54 @@ export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
|
|
|
248
355
|
return { routes: {}, searchSchemas: {} };
|
|
249
356
|
}
|
|
250
357
|
|
|
251
|
-
const
|
|
252
|
-
if (!
|
|
358
|
+
const extraction = extractUrlsFromRouter(routerSource);
|
|
359
|
+
if (!extraction) {
|
|
253
360
|
return { routes: {}, searchSchemas: {} };
|
|
254
361
|
}
|
|
255
362
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
363
|
+
// Detect basename from createRouter({ basename: "..." })
|
|
364
|
+
const rawBasename = extractBasenameFromRouter(routerSource);
|
|
365
|
+
const basename = rawBasename
|
|
366
|
+
? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "")
|
|
367
|
+
: undefined;
|
|
368
|
+
|
|
369
|
+
let result: {
|
|
370
|
+
routes: Record<string, string>;
|
|
371
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Inline builder: extract routes directly from the function body
|
|
375
|
+
if (extraction.kind === "inline") {
|
|
376
|
+
result = buildCombinedRouteMapWithSearch(
|
|
377
|
+
routerFilePath,
|
|
378
|
+
undefined,
|
|
379
|
+
undefined,
|
|
380
|
+
undefined,
|
|
381
|
+
extraction.block,
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
// Variable reference: follow imports or same-file declaration
|
|
385
|
+
const imported = resolveImportedVariable(routerSource, extraction.name);
|
|
386
|
+
if (imported) {
|
|
387
|
+
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
388
|
+
if (!targetFile) {
|
|
389
|
+
return { routes: {}, searchSchemas: {} };
|
|
390
|
+
}
|
|
391
|
+
result = buildCombinedRouteMapWithSearch(
|
|
392
|
+
targetFile,
|
|
393
|
+
imported.exportedName,
|
|
394
|
+
);
|
|
395
|
+
} else {
|
|
396
|
+
result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
|
|
261
397
|
}
|
|
262
|
-
return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
|
|
263
398
|
}
|
|
264
399
|
|
|
265
|
-
|
|
400
|
+
// Apply basename prefix to all extracted route patterns
|
|
401
|
+
if (basename) {
|
|
402
|
+
result = applyBasenameToRoutes(result, basename);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return result;
|
|
266
406
|
}
|
|
267
407
|
|
|
268
408
|
// ---------------------------------------------------------------------------
|
|
@@ -285,12 +425,26 @@ export function detectUnresolvableIncludes(
|
|
|
285
425
|
return [];
|
|
286
426
|
}
|
|
287
427
|
|
|
288
|
-
// Extract the urls
|
|
289
|
-
const
|
|
290
|
-
if (!
|
|
428
|
+
// Extract the urls source from the router file
|
|
429
|
+
const extraction = extractUrlsFromRouter(source);
|
|
430
|
+
if (!extraction) return [];
|
|
291
431
|
|
|
292
|
-
|
|
293
|
-
|
|
432
|
+
const diagnostics: UnresolvableInclude[] = [];
|
|
433
|
+
|
|
434
|
+
if (extraction.kind === "inline") {
|
|
435
|
+
// Inline builder: parse directly
|
|
436
|
+
buildCombinedRouteMapWithSearch(
|
|
437
|
+
realPath,
|
|
438
|
+
undefined,
|
|
439
|
+
new Set(),
|
|
440
|
+
diagnostics,
|
|
441
|
+
extraction.block,
|
|
442
|
+
);
|
|
443
|
+
return diagnostics;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Variable reference: resolve where it comes from
|
|
447
|
+
const imported = resolveImportedVariable(source, extraction.name);
|
|
294
448
|
let targetFile: string;
|
|
295
449
|
let exportedName: string | undefined;
|
|
296
450
|
|
|
@@ -312,10 +466,9 @@ export function detectUnresolvableIncludes(
|
|
|
312
466
|
} else {
|
|
313
467
|
// Same-file urls() definition
|
|
314
468
|
targetFile = realPath;
|
|
315
|
-
exportedName =
|
|
469
|
+
exportedName = extraction.name;
|
|
316
470
|
}
|
|
317
471
|
|
|
318
|
-
const diagnostics: UnresolvableInclude[] = [];
|
|
319
472
|
buildCombinedRouteMapWithSearch(
|
|
320
473
|
targetFile,
|
|
321
474
|
exportedName,
|
|
@@ -397,34 +550,20 @@ export function writeCombinedRouteTypes(
|
|
|
397
550
|
}
|
|
398
551
|
|
|
399
552
|
for (const routerFilePath of routerFilePaths) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const imported = resolveImportedVariable(routerSource, urlsVarName);
|
|
417
|
-
if (imported) {
|
|
418
|
-
// Variable is imported from another module
|
|
419
|
-
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
420
|
-
if (!targetFile) continue;
|
|
421
|
-
result = buildCombinedRouteMapWithSearch(
|
|
422
|
-
targetFile,
|
|
423
|
-
imported.exportedName,
|
|
424
|
-
);
|
|
425
|
-
} else {
|
|
426
|
-
// Variable is defined in the same file
|
|
427
|
-
result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
|
|
553
|
+
const result = buildCombinedRouteMapForRouterFile(routerFilePath);
|
|
554
|
+
if (
|
|
555
|
+
Object.keys(result.routes).length === 0 &&
|
|
556
|
+
Object.keys(result.searchSchemas).length === 0
|
|
557
|
+
) {
|
|
558
|
+
// Check if the file even has a createRouter call — if not, skip entirely.
|
|
559
|
+
// If it does, fall through to write an empty placeholder below.
|
|
560
|
+
let routerSource: string;
|
|
561
|
+
try {
|
|
562
|
+
routerSource = readFileSync(routerFilePath, "utf-8");
|
|
563
|
+
} catch {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (!extractUrlsFromRouter(routerSource)) continue;
|
|
428
567
|
}
|
|
429
568
|
|
|
430
569
|
const routerBasename = pathBasename(routerFilePath).replace(
|
|
@@ -2,6 +2,7 @@ import type { LocationStateEntry } from "../browser/react/location-state-shared.
|
|
|
2
2
|
import {
|
|
3
3
|
requireRequestContext,
|
|
4
4
|
getRequestContext,
|
|
5
|
+
_getRequestContext,
|
|
5
6
|
} from "../server/request-context.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -83,10 +84,17 @@ export function redirect(
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
// Auto-prefix root-relative URLs with basename for app-local redirects.
|
|
88
|
+
const bn = _getRequestContext()?._basename;
|
|
89
|
+
let resolvedUrl = url;
|
|
90
|
+
if (bn && url.startsWith("/") && !url.startsWith(bn + "/") && url !== bn) {
|
|
91
|
+
resolvedUrl = url === "/" ? bn : bn + url;
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
return new Response(null, {
|
|
87
95
|
status,
|
|
88
96
|
headers: {
|
|
89
|
-
Location:
|
|
97
|
+
Location: resolvedUrl,
|
|
90
98
|
"X-RSC-Redirect": "soft",
|
|
91
99
|
},
|
|
92
100
|
});
|
|
@@ -207,7 +207,7 @@ export function createHandlerContext<TEnv>(
|
|
|
207
207
|
// Get variables from request context - this is the unified context
|
|
208
208
|
// shared between middleware and route handlers
|
|
209
209
|
const requestContext = _getRequestContext();
|
|
210
|
-
const variables: any = requestContext?.
|
|
210
|
+
const variables: any = requestContext?._variables ?? {};
|
|
211
211
|
|
|
212
212
|
// If route has a search schema, parse URLSearchParams into typed object
|
|
213
213
|
const searchSchema = routeName ? getSearchSchema(routeName) : undefined;
|
|
@@ -257,7 +257,7 @@ export function createHandlerContext<TEnv>(
|
|
|
257
257
|
url,
|
|
258
258
|
originalUrl: new URL(request.url),
|
|
259
259
|
env: bindings,
|
|
260
|
-
|
|
260
|
+
_variables: variables,
|
|
261
261
|
get: ((keyOrVar: any) => {
|
|
262
262
|
// Read-time guard: non-cacheable var inside cache() → throw.
|
|
263
263
|
// Works for both ContextVar tokens and string keys.
|
|
@@ -320,7 +320,7 @@ export function createHandlerContext<TEnv>(
|
|
|
320
320
|
*
|
|
321
321
|
* Returns an InternalHandlerContext where params, pathname, url, searchParams,
|
|
322
322
|
* search, reverse, and use(handle) work. Request-time properties
|
|
323
|
-
* (request, env, headers, cookies,
|
|
323
|
+
* (request, env, headers, cookies, get, set, res) throw with a clear error.
|
|
324
324
|
*/
|
|
325
325
|
export function createPrerenderContext<TEnv>(
|
|
326
326
|
params: Record<string, string>,
|
|
@@ -354,9 +354,7 @@ export function createPrerenderContext<TEnv>(
|
|
|
354
354
|
get env(): TEnv {
|
|
355
355
|
return throwUnavailable("env");
|
|
356
356
|
},
|
|
357
|
-
|
|
358
|
-
return throwUnavailable("var");
|
|
359
|
-
},
|
|
357
|
+
_variables: variables,
|
|
360
358
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
361
359
|
set: ((keyOrVar: any, value: any) => {
|
|
362
360
|
contextSet(variables, keyOrVar, value);
|
|
@@ -438,9 +436,7 @@ export function createStaticContext<TEnv>(
|
|
|
438
436
|
get env(): TEnv {
|
|
439
437
|
return throwUnavailable("env");
|
|
440
438
|
},
|
|
441
|
-
|
|
442
|
-
return throwUnavailable("var");
|
|
443
|
-
},
|
|
439
|
+
_variables: variables,
|
|
444
440
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
445
441
|
set: ((keyOrVar: any, value: any) => {
|
|
446
442
|
contextSet(variables, keyOrVar, value);
|
|
@@ -11,7 +11,11 @@ import type {
|
|
|
11
11
|
InterceptEntry,
|
|
12
12
|
InterceptSelectorContext,
|
|
13
13
|
} from "../server/context";
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
HandlerContext,
|
|
16
|
+
InternalHandlerContext,
|
|
17
|
+
ResolvedSegment,
|
|
18
|
+
} from "../types";
|
|
15
19
|
import { evaluateRevalidation } from "./revalidation.js";
|
|
16
20
|
import { getRequestContext } from "../server/request-context.js";
|
|
17
21
|
import { executeInterceptMiddleware } from "./middleware.js";
|
|
@@ -134,7 +138,7 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
134
138
|
context.request,
|
|
135
139
|
context.env,
|
|
136
140
|
params,
|
|
137
|
-
context
|
|
141
|
+
(context as InternalHandlerContext<any, TEnv>)._variables,
|
|
138
142
|
requestCtx.res,
|
|
139
143
|
createReverseFunction(getGlobalRouteMap()),
|
|
140
144
|
);
|
|
@@ -242,6 +242,7 @@ function createLoaderExecutor<TEnv>(
|
|
|
242
242
|
pendingLoaders.add(loader.$$id);
|
|
243
243
|
|
|
244
244
|
const currentLoaderId = loader.$$id;
|
|
245
|
+
const variables = (ctx as InternalHandlerContext<any, TEnv>)._variables;
|
|
245
246
|
// Loader functions are always fresh (never cached), so they get an
|
|
246
247
|
// unguarded get that bypasses non-cacheable read guards. This applies
|
|
247
248
|
// to ALL loaders — DSL and handler-called — because the loader
|
|
@@ -256,8 +257,8 @@ function createLoaderExecutor<TEnv>(
|
|
|
256
257
|
pathname: ctx.pathname,
|
|
257
258
|
url: ctx.url,
|
|
258
259
|
env: ctx.env,
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
get: ((keyOrVar: any) =>
|
|
261
|
+
contextGet(variables, keyOrVar)) as typeof ctx.get,
|
|
261
262
|
use: <TDep, TDepParams = any>(
|
|
262
263
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
263
264
|
): Promise<TDep> => {
|
|
@@ -95,12 +95,6 @@ export interface MiddlewareContext<
|
|
|
95
95
|
/** Set a context variable (shared with route handlers) */
|
|
96
96
|
set: SetVariableFn;
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* Middleware-injected variables.
|
|
100
|
-
* Same shared dictionary as `ctx.get()`/`ctx.set()`.
|
|
101
|
-
*/
|
|
102
|
-
var: DefaultVars;
|
|
103
|
-
|
|
104
98
|
/**
|
|
105
99
|
* Set a response header - can be called before or after `next()`.
|
|
106
100
|
*
|
package/src/router/middleware.ts
CHANGED
|
@@ -207,9 +207,6 @@ export function createMiddlewareContext<TEnv>(
|
|
|
207
207
|
set: ((keyOrVar: any, value: unknown, options?: any) => {
|
|
208
208
|
contextSet(variables, keyOrVar, value, options);
|
|
209
209
|
}) as MiddlewareContext<TEnv>["set"],
|
|
210
|
-
|
|
211
|
-
var: variables as MiddlewareContext<TEnv>["var"],
|
|
212
|
-
|
|
213
210
|
header(name: string, value: string): void {
|
|
214
211
|
// Before next(): delegate to shared RequestContext stub
|
|
215
212
|
if (isPreNext()) {
|
|
@@ -104,7 +104,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
104
104
|
originalUrl: new URL("http://prerender" + pathname),
|
|
105
105
|
pathname,
|
|
106
106
|
searchParams: new URLSearchParams(),
|
|
107
|
-
|
|
107
|
+
_variables: variables,
|
|
108
108
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
109
109
|
set: ((keyOrVar: any, value: any) => {
|
|
110
110
|
contextSet(variables, keyOrVar, value);
|
|
@@ -336,7 +336,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
336
336
|
originalUrl: syntheticUrl,
|
|
337
337
|
pathname: "/",
|
|
338
338
|
searchParams: syntheticUrl.searchParams,
|
|
339
|
-
|
|
339
|
+
_variables: {},
|
|
340
340
|
get: () => undefined as any,
|
|
341
341
|
set: () => {},
|
|
342
342
|
params: {},
|
|
@@ -2,6 +2,7 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
2
2
|
import type { SerializedManifest } from "../debug.js";
|
|
3
3
|
import type { ReverseFunction } from "../reverse.js";
|
|
4
4
|
import type { UrlPatterns } from "../urls.js";
|
|
5
|
+
import type { UrlBuilder } from "../urls/pattern-types.js";
|
|
5
6
|
import type { EntryData } from "../server/context";
|
|
6
7
|
import type { ErrorInfo, MatchResult } from "../types";
|
|
7
8
|
import type { NonceProvider } from "../rsc/types.js";
|
|
@@ -68,12 +69,24 @@ export interface RSCRouter<
|
|
|
68
69
|
readonly id: string;
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
+
* URL prefix applied to all routes. Undefined when no basename is configured.
|
|
73
|
+
*/
|
|
74
|
+
readonly basename: string | undefined;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Register routes using URL patterns from urls() or a builder function
|
|
72
78
|
*
|
|
73
79
|
* @example
|
|
74
80
|
* ```typescript
|
|
75
|
-
*
|
|
76
|
-
*
|
|
81
|
+
* // With urls()
|
|
82
|
+
* createRouter({}).routes(urlpatterns)
|
|
83
|
+
*
|
|
84
|
+
* // With builder function (urls() is implicit)
|
|
85
|
+
* createRouter({}).routes(({ path, layout }) => [
|
|
86
|
+
* layout(RootLayout, () => [
|
|
87
|
+
* path("/", HomePage),
|
|
88
|
+
* ]),
|
|
89
|
+
* ])
|
|
77
90
|
* ```
|
|
78
91
|
*/
|
|
79
92
|
routes<T extends UrlPatterns<TEnv, any>>(
|
|
@@ -85,6 +98,7 @@ export interface RSCRouter<
|
|
|
85
98
|
? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
|
|
86
99
|
: Record<string, string>)
|
|
87
100
|
>;
|
|
101
|
+
routes(builder: UrlBuilder<TEnv>): RSCRouter<TEnv, TRoutes>;
|
|
88
102
|
|
|
89
103
|
/**
|
|
90
104
|
* Add global middleware that runs on all routes
|
|
@@ -188,8 +202,11 @@ export interface RSCRouterInternal<
|
|
|
188
202
|
*/
|
|
189
203
|
readonly id: string;
|
|
190
204
|
|
|
205
|
+
/** URL prefix applied to all routes. */
|
|
206
|
+
readonly basename: string | undefined;
|
|
207
|
+
|
|
191
208
|
/**
|
|
192
|
-
* Register routes using URL patterns from urls()
|
|
209
|
+
* Register routes using URL patterns from urls() or a builder function
|
|
193
210
|
*/
|
|
194
211
|
routes<T extends UrlPatterns<TEnv, any>>(
|
|
195
212
|
patterns: T,
|
|
@@ -200,6 +217,7 @@ export interface RSCRouterInternal<
|
|
|
200
217
|
? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
|
|
201
218
|
: Record<string, string>)
|
|
202
219
|
>;
|
|
220
|
+
routes(builder: UrlBuilder<TEnv>): RSCRouter<TEnv, TRoutes>;
|
|
203
221
|
|
|
204
222
|
/**
|
|
205
223
|
* Add global middleware that runs on all routes
|
|
@@ -338,6 +356,9 @@ export interface RSCRouterInternal<
|
|
|
338
356
|
*/
|
|
339
357
|
readonly __sourceFile?: string;
|
|
340
358
|
|
|
359
|
+
/** @internal basename for runtime manifest generation */
|
|
360
|
+
readonly __basename?: string;
|
|
361
|
+
|
|
341
362
|
match(
|
|
342
363
|
request: Request,
|
|
343
364
|
input?: RouterRequestInput<TEnv>,
|