@rangojs/router 0.0.0-experimental.8678bb02 → 0.0.0-experimental.87
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 +126 -38
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +847 -384
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +5 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- 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 +35 -2
- 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 +59 -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 +24 -0
- package/skills/router-setup/SKILL.md +35 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +3 -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/navigation-bridge.ts +87 -6
- package/src/browser/navigation-client.ts +128 -77
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/partial-update.ts +60 -7
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +156 -18
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +72 -8
- package/src/browser/react/NavigationProvider.tsx +57 -11
- 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 +60 -9
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/server-action-bridge.ts +8 -18
- package/src/browser/types.ts +33 -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 +211 -72
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cf/cf-cache-store.ts +5 -7
- package/src/client.tsx +84 -230
- package/src/deps/browser.ts +0 -1
- 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 +210 -35
- package/src/route-definition/helpers-types.ts +61 -14
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +70 -17
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/lazy-includes.ts +6 -6
- package/src/router/loader-resolution.ts +153 -21
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +127 -192
- package/src/router/match-middleware/cache-lookup.ts +28 -8
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +82 -4
- package/src/router/middleware-types.ts +2 -28
- package/src/router/middleware.ts +32 -7
- 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-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +70 -5
- package/src/router/segment-resolution/revalidation.ts +87 -9
- package/src/router/trie-matching.ts +10 -4
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +54 -7
- package/src/rsc/handler.ts +478 -399
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -3
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +15 -2
- package/src/rsc/server-action.ts +10 -2
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +6 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +11 -61
- package/src/server/context.ts +65 -5
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +142 -55
- package/src/ssr/index.tsx +3 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +17 -43
- package/src/types/loader-types.ts +37 -11
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +47 -12
- 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/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 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- 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-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 +64 -206
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +40 -18
- package/src/vite/router-discovery.ts +237 -37
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/package-resolution.ts +1 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
- package/src/browser/debug-channel.ts +0 -93
|
@@ -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) {
|
|
@@ -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;
|
|
@@ -51,93 +51,144 @@ export async function expandPrerenderRoutes(
|
|
|
51
51
|
return substituteRouteParams(pattern, params);
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
let resolvedRoutes = 0;
|
|
55
|
+
let totalDynamic = 0;
|
|
56
|
+
|
|
57
|
+
// Count dynamic routes upfront for progress reporting
|
|
54
58
|
for (const { manifest } of allManifests) {
|
|
55
59
|
if (!manifest.prerenderRoutes) continue;
|
|
56
|
-
const defs = manifest._prerenderDefs || {};
|
|
57
60
|
for (const routeName of manifest.prerenderRoutes) {
|
|
58
61
|
const pattern = manifest.routeManifest[routeName];
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
62
|
+
if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
|
|
63
|
+
totalDynamic++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Periodic progress log so long getParams() calls don't look stalled
|
|
69
|
+
const paramsStart = performance.now();
|
|
70
|
+
const progressInterval =
|
|
71
|
+
totalDynamic > 0
|
|
72
|
+
? setInterval(() => {
|
|
73
|
+
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
74
|
+
console.log(
|
|
75
|
+
`[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
76
|
+
);
|
|
77
|
+
}, 5000)
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
for (const { manifest } of allManifests) {
|
|
82
|
+
if (!manifest.prerenderRoutes) continue;
|
|
83
|
+
const defs = manifest._prerenderDefs || {};
|
|
84
|
+
const passthroughSet = new Set(manifest.passthroughRoutes || []);
|
|
85
|
+
for (const routeName of manifest.prerenderRoutes) {
|
|
86
|
+
const pattern = manifest.routeManifest[routeName];
|
|
87
|
+
if (!pattern) continue;
|
|
88
|
+
const def = defs[routeName];
|
|
89
|
+
const isPassthroughRoute = passthroughSet.has(routeName);
|
|
90
|
+
const hasDynamic = pattern.includes(":") || pattern.includes("*");
|
|
91
|
+
if (!hasDynamic) {
|
|
92
|
+
// Static route: use pattern directly (strip trailing slash for URL)
|
|
93
|
+
entries.push({
|
|
94
|
+
urlPath: pattern.replace(/\/$/, "") || "/",
|
|
95
|
+
routeName,
|
|
96
|
+
concurrency: 1,
|
|
97
|
+
isPassthroughRoute,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
// Dynamic route: call getParams() to enumerate param combinations
|
|
101
|
+
if (def?.getParams) {
|
|
102
|
+
try {
|
|
103
|
+
const buildVars: Record<string, any> = {};
|
|
104
|
+
const buildEnv = state.resolvedBuildEnv;
|
|
105
|
+
const getParamsCtx = {
|
|
106
|
+
build: true as const,
|
|
107
|
+
dev: !state.isBuildMode,
|
|
108
|
+
set: ((keyOrVar: any, value: any) => {
|
|
109
|
+
contextSet(buildVars, keyOrVar, value);
|
|
110
|
+
}) as any,
|
|
111
|
+
reverse: getParamsReverse,
|
|
112
|
+
get env() {
|
|
113
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
114
|
+
throw new Error(
|
|
115
|
+
"[rsc-router] ctx.env is not available during build-time getParams(). " +
|
|
116
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const paramsList = await def.getParams(getParamsCtx);
|
|
121
|
+
const concurrency = def.options?.concurrency ?? 1;
|
|
122
|
+
const hasBuildVars =
|
|
123
|
+
Object.keys(buildVars).length > 0 ||
|
|
124
|
+
Object.getOwnPropertySymbols(buildVars).length > 0;
|
|
125
|
+
for (const params of paramsList) {
|
|
126
|
+
let url = substituteRouteParams(
|
|
127
|
+
pattern,
|
|
128
|
+
params as Record<string, string>,
|
|
129
|
+
encodePathParam,
|
|
130
|
+
);
|
|
131
|
+
// Anonymous wildcard fallback: use conventional keys if provided
|
|
132
|
+
if (url.includes("*")) {
|
|
133
|
+
const wildcardValue =
|
|
134
|
+
(params as Record<string, string>)["*"] ??
|
|
135
|
+
(params as Record<string, string>).splat;
|
|
136
|
+
if (wildcardValue !== undefined) {
|
|
137
|
+
url = url.replace(
|
|
138
|
+
/\*[^/]*$/,
|
|
139
|
+
encodePathParam(wildcardValue),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
101
142
|
}
|
|
143
|
+
entries.push({
|
|
144
|
+
urlPath: url.replace(/\/$/, "") || "/",
|
|
145
|
+
routeName,
|
|
146
|
+
concurrency,
|
|
147
|
+
...(hasBuildVars ? { buildVars } : {}),
|
|
148
|
+
isPassthroughRoute,
|
|
149
|
+
});
|
|
102
150
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
resolvedRoutes++;
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
resolvedRoutes++;
|
|
154
|
+
// Skip in getParams() skips the entire route
|
|
155
|
+
if (err.name === "Skip") {
|
|
156
|
+
console.log(
|
|
157
|
+
`[rsc-router] SKIP route "${routeName}" - ${err.message}`,
|
|
158
|
+
);
|
|
159
|
+
notifyOnError(
|
|
160
|
+
registry,
|
|
161
|
+
err,
|
|
162
|
+
"prerender",
|
|
163
|
+
routeName,
|
|
164
|
+
undefined,
|
|
165
|
+
true,
|
|
166
|
+
);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Regular error: fail the build
|
|
170
|
+
console.error(
|
|
171
|
+
`[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`,
|
|
124
172
|
);
|
|
125
|
-
|
|
173
|
+
notifyOnError(registry, err, "prerender", routeName);
|
|
174
|
+
throw err;
|
|
126
175
|
}
|
|
127
|
-
|
|
128
|
-
console.
|
|
129
|
-
`[rsc-router]
|
|
176
|
+
} else {
|
|
177
|
+
console.warn(
|
|
178
|
+
`[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`,
|
|
130
179
|
);
|
|
131
|
-
notifyOnError(registry, err, "prerender", routeName);
|
|
132
|
-
throw err;
|
|
133
180
|
}
|
|
134
|
-
} else {
|
|
135
|
-
console.warn(
|
|
136
|
-
`[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`,
|
|
137
|
-
);
|
|
138
181
|
}
|
|
139
182
|
}
|
|
140
183
|
}
|
|
184
|
+
} finally {
|
|
185
|
+
if (progressInterval) {
|
|
186
|
+
clearInterval(progressInterval);
|
|
187
|
+
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
188
|
+
console.log(
|
|
189
|
+
`[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
141
192
|
}
|
|
142
193
|
|
|
143
194
|
if (entries.length === 0) return;
|
|
@@ -175,6 +226,7 @@ export async function expandPrerenderRoutes(
|
|
|
175
226
|
{},
|
|
176
227
|
entry.buildVars,
|
|
177
228
|
entry.isPassthroughRoute,
|
|
229
|
+
state.resolvedBuildEnv,
|
|
178
230
|
);
|
|
179
231
|
if (!result) continue;
|
|
180
232
|
|
|
@@ -326,6 +378,8 @@ export async function renderStaticHandlers(
|
|
|
326
378
|
def.handler,
|
|
327
379
|
def.$$id,
|
|
328
380
|
(def as any).$$routePrefix,
|
|
381
|
+
state.resolvedBuildEnv,
|
|
382
|
+
!state.isBuildMode,
|
|
329
383
|
);
|
|
330
384
|
if (result) {
|
|
331
385
|
const hasHandles = Object.keys(result.handles).length > 0;
|
|
@@ -16,6 +16,10 @@ export interface PluginOptions {
|
|
|
16
16
|
// Mutable ref for deferred auto-discovery (node preset).
|
|
17
17
|
// The auto-discover config() hook populates this before configResolved.
|
|
18
18
|
routerPathRef?: { path?: string };
|
|
19
|
+
/** Build-time env option from rango() config. */
|
|
20
|
+
buildEnv?: import("../plugin-types.js").BuildEnvOption;
|
|
21
|
+
/** Deployment preset (needed for buildEnv "auto" resolution). */
|
|
22
|
+
preset?: "node" | "cloudflare";
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export interface PrecomputedEntry {
|
|
@@ -56,8 +60,8 @@ export interface DiscoveryState {
|
|
|
56
60
|
|
|
57
61
|
prerenderManifestEntries: Record<string, string> | null;
|
|
58
62
|
staticManifestEntries: Record<string, string> | null;
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
handlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
64
|
+
staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
61
65
|
rscEntryFileName: string | null;
|
|
62
66
|
resolvedPrerenderModules: Map<string, string[]> | undefined;
|
|
63
67
|
resolvedStaticModules: Map<string, string[]> | undefined;
|
|
@@ -67,6 +71,11 @@ export interface DiscoveryState {
|
|
|
67
71
|
devServer: any;
|
|
68
72
|
selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
|
|
69
73
|
SELF_WRITE_WINDOW_MS: number;
|
|
74
|
+
|
|
75
|
+
/** Resolved build-time env bindings (set during buildStart/configureServer). */
|
|
76
|
+
resolvedBuildEnv?: Record<string, unknown>;
|
|
77
|
+
/** Cleanup function for build-time env resources (e.g., miniflare). */
|
|
78
|
+
buildEnvDispose?: (() => Promise<void> | void) | null;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
export function createDiscoveryState(
|
|
@@ -93,8 +102,8 @@ export function createDiscoveryState(
|
|
|
93
102
|
|
|
94
103
|
prerenderManifestEntries: null,
|
|
95
104
|
staticManifestEntries: null,
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
handlerChunkInfoMap: new Map(),
|
|
106
|
+
staticHandlerChunkInfoMap: new Map(),
|
|
98
107
|
rscEntryFileName: null,
|
|
99
108
|
resolvedPrerenderModules: undefined,
|
|
100
109
|
resolvedStaticModules: undefined,
|