@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
|
@@ -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>,
|
|
@@ -229,12 +233,27 @@ export type PathHelpers<TEnv> = {
|
|
|
229
233
|
include: IncludeFn<TEnv>;
|
|
230
234
|
|
|
231
235
|
/**
|
|
232
|
-
* Define parallel routes that render simultaneously in named slots
|
|
236
|
+
* Define parallel routes that render simultaneously in named slots.
|
|
237
|
+
*
|
|
238
|
+
* A slot value can be a Handler / ReactNode / StaticHandlerDefinition
|
|
239
|
+
* (legacy form, broadcast use applies to every slot) or a slot descriptor
|
|
240
|
+
* `{ handler, use? }` whose `use` is scoped to that slot only. Per-slot
|
|
241
|
+
* merge order is `handler.use` → shared `use` → slot-local `use`, with
|
|
242
|
+
* narrowest scope winning for last-write-wins items like `loading()`.
|
|
233
243
|
*/
|
|
234
244
|
parallel: <
|
|
235
245
|
TSlots extends Record<
|
|
236
246
|
`@${string}`,
|
|
237
|
-
Handler<any, any, TEnv>
|
|
247
|
+
| Handler<any, any, TEnv>
|
|
248
|
+
| ReactNode
|
|
249
|
+
| StaticHandlerDefinition
|
|
250
|
+
| {
|
|
251
|
+
handler:
|
|
252
|
+
| Handler<any, any, TEnv>
|
|
253
|
+
| ReactNode
|
|
254
|
+
| StaticHandlerDefinition;
|
|
255
|
+
use?: () => ParallelUseItem[];
|
|
256
|
+
}
|
|
238
257
|
>,
|
|
239
258
|
>(
|
|
240
259
|
slots: TSlots,
|
|
@@ -260,9 +279,20 @@ export type PathHelpers<TEnv> = {
|
|
|
260
279
|
) => InterceptItem;
|
|
261
280
|
|
|
262
281
|
/**
|
|
263
|
-
* Attach middleware to the current route/layout
|
|
282
|
+
* Attach middleware to the current route/layout, or wrap child segments
|
|
264
283
|
*/
|
|
265
|
-
middleware:
|
|
284
|
+
middleware: {
|
|
285
|
+
(fn: MiddlewareFn<TEnv>): MiddlewareItem;
|
|
286
|
+
(
|
|
287
|
+
fn: MiddlewareFn<TEnv>,
|
|
288
|
+
children: () => UseItems<LayoutUseItem>,
|
|
289
|
+
): MiddlewareItem;
|
|
290
|
+
(fns: MiddlewareFn<TEnv>[]): MiddlewareItem;
|
|
291
|
+
(
|
|
292
|
+
fns: MiddlewareFn<TEnv>[],
|
|
293
|
+
children: () => UseItems<LayoutUseItem>,
|
|
294
|
+
): MiddlewareItem;
|
|
295
|
+
};
|
|
266
296
|
|
|
267
297
|
/**
|
|
268
298
|
* Control when a segment should revalidate during navigation
|
|
@@ -280,7 +310,10 @@ export type PathHelpers<TEnv> = {
|
|
|
280
310
|
/**
|
|
281
311
|
* Attach a loading component to the current route/layout
|
|
282
312
|
*/
|
|
283
|
-
loading: (
|
|
313
|
+
loading: (
|
|
314
|
+
component: ReactNode | (() => ReactNode),
|
|
315
|
+
options?: { ssr?: boolean },
|
|
316
|
+
) => LoadingItem;
|
|
284
317
|
|
|
285
318
|
/**
|
|
286
319
|
* Attach an error boundary to catch errors in this segment
|
package/src/urls/path-helper.ts
CHANGED
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
getNamePrefix,
|
|
13
13
|
getRootScoped,
|
|
14
14
|
} from "../server/context";
|
|
15
|
-
import { invariant } from "../errors";
|
|
15
|
+
import { invariant, DataNotFoundError } from "../errors";
|
|
16
16
|
import { validateUserRouteName } from "../route-name.js";
|
|
17
17
|
import {
|
|
18
18
|
isPrerenderHandler,
|
|
19
|
+
isPassthroughHandler,
|
|
19
20
|
type PrerenderHandlerDefinition,
|
|
20
21
|
} from "../prerender.js";
|
|
21
22
|
import {
|
|
@@ -34,6 +35,10 @@ import type {
|
|
|
34
35
|
JsonResponsePathFn,
|
|
35
36
|
TextResponsePathFn,
|
|
36
37
|
} from "./path-helper-types.js";
|
|
38
|
+
import {
|
|
39
|
+
resolveHandlerUse,
|
|
40
|
+
mergeHandlerUse,
|
|
41
|
+
} from "../route-definition/resolve-handler-use.js";
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* Check if a value is a valid use item
|
|
@@ -142,6 +147,12 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
142
147
|
use = maybeUse;
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
// Merge handler.use() defaults with explicit use()
|
|
151
|
+
// Response routes (path.json, path.text, etc.) only allow middleware + cache
|
|
152
|
+
const handlerUseFn = resolveHandlerUse(handler);
|
|
153
|
+
const mountSite = resolveResponseType(options) ? "response" : "path";
|
|
154
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, mountSite);
|
|
155
|
+
|
|
145
156
|
// Get prefixes from context (set by include())
|
|
146
157
|
const urlPrefix = getUrlPrefix();
|
|
147
158
|
const namePrefix = getNamePrefix();
|
|
@@ -176,14 +187,31 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
// Ensure handler is always a function (wrap ReactNode or extract from prerender/static def)
|
|
190
|
+
// For prerender stubs (production builds where handler code is evicted),
|
|
191
|
+
// handler.handler is undefined — provide a notFound fallback so requests
|
|
192
|
+
// for non-prerendered params get 404 instead of "handler is not a function".
|
|
179
193
|
const wrappedHandler: Handler<any, any, TEnv> =
|
|
180
194
|
typeof handler === "function"
|
|
181
195
|
? (handler as Handler<any, any, TEnv>)
|
|
182
|
-
:
|
|
183
|
-
?
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
: isPassthroughHandler(handler)
|
|
197
|
+
? typeof handler.prerenderDef.handler === "function"
|
|
198
|
+
? (handler.prerenderDef.handler as Handler<any, any, TEnv>)
|
|
199
|
+
: () => {
|
|
200
|
+
throw new DataNotFoundError(
|
|
201
|
+
"No prerender data found for this route",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
: isPrerenderHandler(handler)
|
|
205
|
+
? typeof handler.handler === "function"
|
|
206
|
+
? (handler.handler as Handler<any, any, TEnv>)
|
|
207
|
+
: () => {
|
|
208
|
+
throw new DataNotFoundError(
|
|
209
|
+
"No prerender data found for this route",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
: isStaticHandler(handler)
|
|
213
|
+
? (handler.handler as Handler<any, any, TEnv>)
|
|
214
|
+
: () => handler;
|
|
187
215
|
|
|
188
216
|
const entry = {
|
|
189
217
|
id: namespace,
|
|
@@ -199,16 +227,23 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
199
227
|
errorBoundary: [],
|
|
200
228
|
notFoundBoundary: [],
|
|
201
229
|
layout: [],
|
|
202
|
-
parallel:
|
|
230
|
+
parallel: {},
|
|
203
231
|
intercept: [],
|
|
204
232
|
loader: [],
|
|
205
233
|
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
206
|
-
...(
|
|
234
|
+
...(isPassthroughHandler(handler)
|
|
207
235
|
? {
|
|
208
236
|
isPrerender: true as const,
|
|
209
|
-
prerenderDef: handler as PrerenderHandlerDefinition,
|
|
237
|
+
prerenderDef: handler.prerenderDef as PrerenderHandlerDefinition,
|
|
238
|
+
isPassthrough: true as const,
|
|
239
|
+
liveHandler: handler.liveHandler as Handler<any, any, TEnv>,
|
|
210
240
|
}
|
|
211
|
-
:
|
|
241
|
+
: isPrerenderHandler(handler)
|
|
242
|
+
? {
|
|
243
|
+
isPrerender: true as const,
|
|
244
|
+
prerenderDef: handler as PrerenderHandlerDefinition,
|
|
245
|
+
}
|
|
246
|
+
: {}),
|
|
212
247
|
...(isStaticHandler(handler)
|
|
213
248
|
? {
|
|
214
249
|
isStaticPrerender: true as const,
|
|
@@ -264,9 +299,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
264
299
|
registerSearchSchema(routeName, options.search);
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
// Run use callback if
|
|
268
|
-
if (
|
|
269
|
-
const result = store.run(namespace, entry,
|
|
302
|
+
// Run merged use callback (handler.use defaults + explicit use) if present
|
|
303
|
+
if (mergedUse) {
|
|
304
|
+
const result = store.run(namespace, entry, mergedUse)?.flat(3);
|
|
270
305
|
invariant(
|
|
271
306
|
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
272
307
|
`path() use() callback must return an array of use items [${namespace}]`,
|
|
@@ -7,6 +7,18 @@ import type {
|
|
|
7
7
|
} from "../route-types.js";
|
|
8
8
|
import type { SearchSchema } from "../search-params.js";
|
|
9
9
|
import { RESPONSE_TYPE } from "./response-types.js";
|
|
10
|
+
import type { DefaultEnv } from "../types.js";
|
|
11
|
+
import type { PathHelpers } from "./path-helper-types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builder function accepted by urls() and as a shorthand for routes()/urls option.
|
|
15
|
+
* When passed directly to routes() or createRouter({ urls }), it is wrapped in urls() automatically.
|
|
16
|
+
*/
|
|
17
|
+
export type UrlBuilder<
|
|
18
|
+
TEnv = DefaultEnv,
|
|
19
|
+
TItems extends readonly (AllUseItems | readonly AllUseItems[])[] =
|
|
20
|
+
readonly AllUseItems[],
|
|
21
|
+
> = (helpers: PathHelpers<TEnv>) => TItems;
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* Sentinel type for unnamed routes.
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
DefaultReverseRouteMap,
|
|
5
5
|
DefaultVars,
|
|
6
6
|
} from "../types/global-namespace.js";
|
|
7
|
+
import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
|
|
8
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Reverse function for response handler contexts.
|
|
@@ -38,9 +40,12 @@ export const RESPONSE_TYPE: unique symbol = Symbol.for(
|
|
|
38
40
|
* Handler that must return Response (not ReactNode).
|
|
39
41
|
* Used by path.image(), path.stream(), path.any() (binary/streaming data).
|
|
40
42
|
*/
|
|
41
|
-
export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = (
|
|
43
|
+
export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = ((
|
|
42
44
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
43
|
-
) => Response | Promise<Response
|
|
45
|
+
) => Response | Promise<Response>) & {
|
|
46
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
47
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
48
|
+
};
|
|
44
49
|
|
|
45
50
|
/**
|
|
46
51
|
* JSON-serializable value type for auto-wrap support.
|
|
@@ -60,9 +65,12 @@ export type JsonValue =
|
|
|
60
65
|
export type JsonResponseHandler<
|
|
61
66
|
TParams = Record<string, string>,
|
|
62
67
|
TEnv = any,
|
|
63
|
-
> = (
|
|
68
|
+
> = ((
|
|
64
69
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
65
|
-
) => JsonValue | Response | Promise<JsonValue | Response
|
|
70
|
+
) => JsonValue | Response | Promise<JsonValue | Response>) & {
|
|
71
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
72
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
73
|
+
};
|
|
66
74
|
|
|
67
75
|
/**
|
|
68
76
|
* Handler for text-based response routes (text, html, xml).
|
|
@@ -71,9 +79,12 @@ export type JsonResponseHandler<
|
|
|
71
79
|
export type TextResponseHandler<
|
|
72
80
|
TParams = Record<string, string>,
|
|
73
81
|
TEnv = any,
|
|
74
|
-
> = (
|
|
82
|
+
> = ((
|
|
75
83
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
76
|
-
) => string | Response | Promise<string | Response
|
|
84
|
+
) => string | Response | Promise<string | Response>) & {
|
|
85
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
86
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
87
|
+
};
|
|
77
88
|
|
|
78
89
|
/**
|
|
79
90
|
* Lighter handler context for response routes.
|
|
@@ -83,19 +94,10 @@ export type TextResponseHandler<
|
|
|
83
94
|
export interface ResponseHandlerContext<
|
|
84
95
|
TParams = Record<string, string>,
|
|
85
96
|
TEnv = any,
|
|
86
|
-
> {
|
|
87
|
-
request: Request;
|
|
97
|
+
> extends RequestScope<TEnv> {
|
|
88
98
|
params: TParams;
|
|
89
99
|
/** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
|
|
90
100
|
readonly _paramCheck?: (params: TParams) => TParams;
|
|
91
|
-
/** Platform bindings (DB, KV, secrets, etc.). */
|
|
92
|
-
env: TEnv;
|
|
93
|
-
/** Query parameters from the URL (system params like `_rsc*` are filtered). */
|
|
94
|
-
searchParams: URLSearchParams;
|
|
95
|
-
/** The full URL object (with system params filtered). */
|
|
96
|
-
url: URL;
|
|
97
|
-
/** The pathname portion of the request URL. */
|
|
98
|
-
pathname: string;
|
|
99
101
|
reverse: ResponseReverseFunction;
|
|
100
102
|
/** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
|
|
101
103
|
get: {
|
package/src/use-loader.tsx
CHANGED
|
@@ -1,9 +1,71 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isValidElement,
|
|
5
|
+
startTransition,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
} from "react";
|
|
4
14
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
5
15
|
import type { LoaderDefinition, LoadOptions } from "./types.js";
|
|
6
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Extract a specific loader's data from a content ReactNode.
|
|
19
|
+
*
|
|
20
|
+
* When a route registers loaders via loader(), the resolved data lives in
|
|
21
|
+
* the route's OutletProvider (rendered as <Outlet /> content). Parallel
|
|
22
|
+
* slots are siblings of <Outlet />, so they can't find it by walking
|
|
23
|
+
* the parent context chain. This helper traverses wrapper elements
|
|
24
|
+
* (MountContextProvider, ViewTransition, etc.) to reach the OutletProvider
|
|
25
|
+
* and extract the loader data directly.
|
|
26
|
+
*/
|
|
27
|
+
const NOT_FOUND = Symbol("not-found");
|
|
28
|
+
|
|
29
|
+
function extractContentLoaderData(
|
|
30
|
+
node: ReactNode,
|
|
31
|
+
loaderId: string,
|
|
32
|
+
): unknown | typeof NOT_FOUND {
|
|
33
|
+
if (!isValidElement(node)) return NOT_FOUND;
|
|
34
|
+
const props = node.props as Record<string, any> | undefined;
|
|
35
|
+
if (!props) return NOT_FOUND;
|
|
36
|
+
|
|
37
|
+
// Direct OutletProvider with loaderData
|
|
38
|
+
if (props.loaderData && loaderId in props.loaderData) {
|
|
39
|
+
return props.loaderData[loaderId];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// LoaderBoundary: loaderIds + loaderDataPromise (already resolved array).
|
|
43
|
+
// When the segment has loading(), loaderData is resolved inside
|
|
44
|
+
// LoaderBoundary via use(). If the promise was pre-awaited (forceAwait
|
|
45
|
+
// or isAction), the prop is a raw array we can index into.
|
|
46
|
+
if (
|
|
47
|
+
props.loaderIds &&
|
|
48
|
+
Array.isArray(props.loaderIds) &&
|
|
49
|
+
props.loaderDataPromise &&
|
|
50
|
+
!(props.loaderDataPromise instanceof Promise)
|
|
51
|
+
) {
|
|
52
|
+
const idx = (props.loaderIds as string[]).indexOf(loaderId);
|
|
53
|
+
if (idx !== -1) {
|
|
54
|
+
const data = (props.loaderDataPromise as any[])[idx];
|
|
55
|
+
// loaderDataPromise entries may be { ok, data } result objects
|
|
56
|
+
if (data && typeof data === "object" && "ok" in data) {
|
|
57
|
+
return data.ok ? data.data : NOT_FOUND;
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Traverse into wrapper elements (MountContextProvider, ViewTransition,
|
|
64
|
+
// Suspense wrappers, etc.)
|
|
65
|
+
if (props.children) return extractContentLoaderData(props.children, loaderId);
|
|
66
|
+
return NOT_FOUND;
|
|
67
|
+
}
|
|
68
|
+
|
|
7
69
|
/**
|
|
8
70
|
* Payload returned by loader RSC requests
|
|
9
71
|
*/
|
|
@@ -71,19 +133,27 @@ function useLoaderInternal<T>(
|
|
|
71
133
|
const context = useContext(OutletContext);
|
|
72
134
|
|
|
73
135
|
// Get data from context (SSR/navigation)
|
|
74
|
-
const
|
|
136
|
+
const contextData = useMemo((): T | undefined => {
|
|
75
137
|
let current: OutletContextValue | null | undefined = context;
|
|
76
138
|
while (current) {
|
|
77
139
|
if (current.loaderData && loader.$$id in current.loaderData) {
|
|
78
140
|
return current.loaderData[loader.$$id] as T;
|
|
79
141
|
}
|
|
142
|
+
// Check content element — the route's OutletProvider is rendered as
|
|
143
|
+
// <Outlet /> content (a child), so its loaderData isn't in the parent
|
|
144
|
+
// chain. Parallel slots need to reach into it to find route-level loaders.
|
|
145
|
+
const contentData = extractContentLoaderData(
|
|
146
|
+
current.content,
|
|
147
|
+
loader.$$id,
|
|
148
|
+
);
|
|
149
|
+
if (contentData !== NOT_FOUND) {
|
|
150
|
+
return contentData as T;
|
|
151
|
+
}
|
|
80
152
|
current = current.parent;
|
|
81
153
|
}
|
|
82
154
|
return undefined;
|
|
83
155
|
}, [context, loader.$$id]);
|
|
84
156
|
|
|
85
|
-
const contextData = getContextData();
|
|
86
|
-
|
|
87
157
|
// Local state for fetched data (from load() calls)
|
|
88
158
|
const [fetchedData, setFetchedData] = useState<T | undefined>(undefined);
|
|
89
159
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -215,7 +285,9 @@ function useLoaderInternal<T>(
|
|
|
215
285
|
|
|
216
286
|
const result = payload.loaderResult;
|
|
217
287
|
if (requestId === requestIdRef.current) {
|
|
218
|
-
|
|
288
|
+
startTransition(() => {
|
|
289
|
+
setFetchedData(result);
|
|
290
|
+
});
|
|
219
291
|
}
|
|
220
292
|
return result;
|
|
221
293
|
} catch (e) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logging for the Rango Vite plugin.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over the `debug` package (the same one Vite uses for its
|
|
5
|
+
* own `vite:*` namespaces). Enable via `DEBUG=rango:*` or `vite --debug rango`.
|
|
6
|
+
*
|
|
7
|
+
* Returns `undefined` when the namespace is not enabled, so call sites can
|
|
8
|
+
* guard expensive diagnostics with a simple truthiness check:
|
|
9
|
+
*
|
|
10
|
+
* const debug = createRangoDebugger("rango:routes");
|
|
11
|
+
* if (debug) debug("built manifest (%d routes) in %dms", n, ms);
|
|
12
|
+
*
|
|
13
|
+
* Back-compat: INTERNAL_RANGO_DEBUG=1 still enables all rango namespaces
|
|
14
|
+
* (sets DEBUG=rango:* for the current process if nothing is already set).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import debugFactory from "debug";
|
|
18
|
+
|
|
19
|
+
// Back-compat: the legacy INTERNAL_RANGO_DEBUG env var enabled per-site
|
|
20
|
+
// console.logs in this plugin. Map it to `rango:*` so those call sites can
|
|
21
|
+
// be migrated to the `debug` pipeline without breaking existing setups.
|
|
22
|
+
// Uses debug.enable() rather than mutating process.env because the `debug`
|
|
23
|
+
// package already snapshotted DEBUG when it was imported above.
|
|
24
|
+
if (process.env.INTERNAL_RANGO_DEBUG) {
|
|
25
|
+
const existing = debugFactory.disable();
|
|
26
|
+
debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Debugger = (formatter: string, ...args: unknown[]) => void;
|
|
30
|
+
|
|
31
|
+
export function createRangoDebugger(namespace: string): Debugger | undefined {
|
|
32
|
+
const instance = debugFactory(namespace);
|
|
33
|
+
return instance.enabled ? (instance as unknown as Debugger) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Measure a sync or async block and log its duration via `debug`. No-ops
|
|
38
|
+
* (still runs `fn`) when the namespace is disabled, so production cost is a
|
|
39
|
+
* single `.enabled` check per call.
|
|
40
|
+
*
|
|
41
|
+
* await timed(debug, "discover routers", () => discoverRouters(state));
|
|
42
|
+
*/
|
|
43
|
+
export async function timed<T>(
|
|
44
|
+
debug: Debugger | undefined,
|
|
45
|
+
label: string,
|
|
46
|
+
fn: () => T | Promise<T>,
|
|
47
|
+
): Promise<T> {
|
|
48
|
+
if (!debug) return await fn();
|
|
49
|
+
const start = performance.now();
|
|
50
|
+
try {
|
|
51
|
+
return await fn();
|
|
52
|
+
} finally {
|
|
53
|
+
debug("%s (%sms)", label, (performance.now() - start).toFixed(1));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -31,25 +31,25 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
31
31
|
state.rscEntryFileName ?? "index.js",
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
// 1. Evict handler code from
|
|
35
|
-
//
|
|
34
|
+
// 1. Evict handler code from whichever chunks contain handler exports.
|
|
35
|
+
// handlerChunkInfoMap/staticHandlerChunkInfoMap are populated by generateBundle
|
|
36
36
|
// after the production RSC build. In Vite 6 multi-environment builds, the
|
|
37
|
-
// RSC build runs twice (analysis + production).
|
|
38
|
-
//
|
|
37
|
+
// RSC build runs twice (analysis + production). The maps are cleared at the
|
|
38
|
+
// start of each generateBundle pass so only production data is used here.
|
|
39
39
|
const evictionTargets: Array<{
|
|
40
|
-
|
|
40
|
+
infos: Iterable<import("./state.js").ChunkInfo>;
|
|
41
41
|
fnName: string;
|
|
42
42
|
brand: string;
|
|
43
43
|
label: string;
|
|
44
44
|
}> = [
|
|
45
45
|
{
|
|
46
|
-
|
|
46
|
+
infos: state.handlerChunkInfoMap.values(),
|
|
47
47
|
fnName: "Prerender",
|
|
48
48
|
brand: "prerenderHandler",
|
|
49
49
|
label: "handler code from RSC bundle",
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
|
|
52
|
+
infos: state.staticHandlerChunkInfoMap.values(),
|
|
53
53
|
fnName: "Static",
|
|
54
54
|
brand: "staticHandler",
|
|
55
55
|
label: "static handler code",
|
|
@@ -57,35 +57,32 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
57
57
|
];
|
|
58
58
|
|
|
59
59
|
for (const target of evictionTargets) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
for (const info of target.infos) {
|
|
61
|
+
const chunkPath = resolve(state.projectRoot, "dist/rsc", info.fileName);
|
|
62
|
+
try {
|
|
63
|
+
const code = readFileSync(chunkPath, "utf-8");
|
|
64
|
+
const result = evictHandlerCode(
|
|
65
|
+
code,
|
|
66
|
+
info.exports,
|
|
67
|
+
target.fnName,
|
|
68
|
+
target.brand,
|
|
69
|
+
);
|
|
70
|
+
if (result) {
|
|
71
|
+
writeFileSync(chunkPath, result.code);
|
|
72
|
+
const savedKB = (result.savedBytes / 1024).toFixed(1);
|
|
73
|
+
console.log(
|
|
74
|
+
`[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} catch (replaceErr: any) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
|
-
} catch (replaceErr: any) {
|
|
82
|
-
console.warn(
|
|
83
|
-
`[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
|
|
84
|
-
);
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
|
-
state.
|
|
88
|
-
state.
|
|
84
|
+
state.handlerChunkInfoMap.clear();
|
|
85
|
+
state.staticHandlerChunkInfoMap.clear();
|
|
89
86
|
|
|
90
87
|
// 2. Write prerender data as separate importable asset modules
|
|
91
88
|
// and inject a lazy manifest loader into the RSC entry.
|
|
@@ -138,7 +135,7 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
138
135
|
// and inject a __STATIC_MANIFEST import into the RSC entry.
|
|
139
136
|
if (hasStaticData && existsSync(rscEntryPath)) {
|
|
140
137
|
const rscCode = readFileSync(rscEntryPath, "utf-8");
|
|
141
|
-
if (!rscCode.includes("
|
|
138
|
+
if (!rscCode.includes("__static-manifest.js")) {
|
|
142
139
|
try {
|
|
143
140
|
const manifestEntries: string[] = [];
|
|
144
141
|
let totalBytes = copyStagedBuildAssets(
|
|
@@ -135,7 +135,11 @@ export async function discoverRouters(
|
|
|
135
135
|
continue;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const manifest = generateManifestFull(
|
|
138
|
+
const manifest = generateManifestFull(
|
|
139
|
+
router.urlpatterns,
|
|
140
|
+
routerMountIndex,
|
|
141
|
+
router.__basename ? { urlPrefix: router.__basename } : undefined,
|
|
142
|
+
);
|
|
139
143
|
routerMountIndex++;
|
|
140
144
|
allManifests.push({ id, manifest });
|
|
141
145
|
const routeCount = Object.keys(manifest.routeManifest).length;
|