@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bf1b128c
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 +138 -50
- package/dist/vite/index.js +2018 -732
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +7 -5
- 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 +119 -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 +192 -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 +88 -9
- 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 +109 -15
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- 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 +36 -17
- 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 +73 -20
- 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 +58 -13
- 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/revalidation.ts +15 -1
- 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 +452 -307
- 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 +17 -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 +145 -68
- 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 +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +36 -4
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +175 -74
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- 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/cjs-to-esm.ts +5 -0
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +16 -4
- 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 +53 -31
- 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-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +563 -316
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +56 -43
- package/src/vite/plugins/version-injector.ts +37 -11
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +732 -94
- 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
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
stageBuildAssetModule,
|
|
17
17
|
} from "../utils/prerender-utils.js";
|
|
18
18
|
import type { DiscoveryState } from "./state.js";
|
|
19
|
+
import { createRangoDebugger, NS } from "../debug.js";
|
|
20
|
+
|
|
21
|
+
const debug = createRangoDebugger(NS.prerender);
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Expand prerender routes into concrete URLs and render them via the
|
|
@@ -30,6 +33,12 @@ export async function expandPrerenderRoutes(
|
|
|
30
33
|
): Promise<void> {
|
|
31
34
|
if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
|
|
32
35
|
|
|
36
|
+
const overallStart = debug ? performance.now() : 0;
|
|
37
|
+
debug?.(
|
|
38
|
+
"expandPrerenderRoutes: start (%d router manifest(s))",
|
|
39
|
+
allManifests.length,
|
|
40
|
+
);
|
|
41
|
+
|
|
33
42
|
type PrerenderEntry = {
|
|
34
43
|
urlPath: string;
|
|
35
44
|
routeName: string;
|
|
@@ -51,96 +60,160 @@ export async function expandPrerenderRoutes(
|
|
|
51
60
|
return substituteRouteParams(pattern, params);
|
|
52
61
|
};
|
|
53
62
|
|
|
63
|
+
let resolvedRoutes = 0;
|
|
64
|
+
let totalDynamic = 0;
|
|
65
|
+
|
|
66
|
+
// Count dynamic routes upfront for progress reporting
|
|
54
67
|
for (const { manifest } of allManifests) {
|
|
55
68
|
if (!manifest.prerenderRoutes) continue;
|
|
56
|
-
const defs = manifest._prerenderDefs || {};
|
|
57
69
|
for (const routeName of manifest.prerenderRoutes) {
|
|
58
70
|
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
|
-
|
|
71
|
+
if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
|
|
72
|
+
totalDynamic++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Periodic progress log so long getParams() calls don't look stalled
|
|
78
|
+
const paramsStart = performance.now();
|
|
79
|
+
const progressInterval =
|
|
80
|
+
totalDynamic > 0
|
|
81
|
+
? setInterval(() => {
|
|
82
|
+
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
83
|
+
console.log(
|
|
84
|
+
`[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
85
|
+
);
|
|
86
|
+
}, 5000)
|
|
87
|
+
: undefined;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
for (const { manifest } of allManifests) {
|
|
91
|
+
if (!manifest.prerenderRoutes) continue;
|
|
92
|
+
const defs = manifest._prerenderDefs || {};
|
|
93
|
+
const passthroughSet = new Set(manifest.passthroughRoutes || []);
|
|
94
|
+
for (const routeName of manifest.prerenderRoutes) {
|
|
95
|
+
const pattern = manifest.routeManifest[routeName];
|
|
96
|
+
if (!pattern) continue;
|
|
97
|
+
const def = defs[routeName];
|
|
98
|
+
const isPassthroughRoute = passthroughSet.has(routeName);
|
|
99
|
+
const hasDynamic = pattern.includes(":") || pattern.includes("*");
|
|
100
|
+
if (!hasDynamic) {
|
|
101
|
+
// Static route: use pattern directly (strip trailing slash for URL)
|
|
102
|
+
entries.push({
|
|
103
|
+
urlPath: pattern.replace(/\/$/, "") || "/",
|
|
104
|
+
routeName,
|
|
105
|
+
concurrency: 1,
|
|
106
|
+
isPassthroughRoute,
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
// Dynamic route: call getParams() to enumerate param combinations
|
|
110
|
+
if (def?.getParams) {
|
|
111
|
+
const getParamsStart = debug ? performance.now() : 0;
|
|
112
|
+
try {
|
|
113
|
+
const buildVars: Record<string, any> = {};
|
|
114
|
+
const buildEnv = state.resolvedBuildEnv;
|
|
115
|
+
const getParamsCtx = {
|
|
116
|
+
build: true as const,
|
|
117
|
+
dev: !state.isBuildMode,
|
|
118
|
+
set: ((keyOrVar: any, value: any) => {
|
|
119
|
+
contextSet(buildVars, keyOrVar, value);
|
|
120
|
+
}) as any,
|
|
121
|
+
reverse: getParamsReverse,
|
|
122
|
+
get env() {
|
|
123
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
124
|
+
throw new Error(
|
|
125
|
+
"[rsc-router] ctx.env is not available during build-time getParams(). " +
|
|
126
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
const paramsList = await def.getParams(getParamsCtx);
|
|
131
|
+
debug?.(
|
|
132
|
+
"getParams %s -> %d params (%sms)",
|
|
133
|
+
routeName,
|
|
134
|
+
paramsList.length,
|
|
135
|
+
(performance.now() - getParamsStart).toFixed(1),
|
|
93
136
|
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
137
|
+
const concurrency = def.options?.concurrency ?? 1;
|
|
138
|
+
const hasBuildVars =
|
|
139
|
+
Object.keys(buildVars).length > 0 ||
|
|
140
|
+
Object.getOwnPropertySymbols(buildVars).length > 0;
|
|
141
|
+
for (const params of paramsList) {
|
|
142
|
+
let url = substituteRouteParams(
|
|
143
|
+
pattern,
|
|
144
|
+
params as Record<string, string>,
|
|
145
|
+
encodePathParam,
|
|
146
|
+
);
|
|
147
|
+
// Anonymous wildcard fallback: use conventional keys if provided
|
|
148
|
+
if (url.includes("*")) {
|
|
149
|
+
const wildcardValue =
|
|
150
|
+
(params as Record<string, string>)["*"] ??
|
|
151
|
+
(params as Record<string, string>).splat;
|
|
152
|
+
if (wildcardValue !== undefined) {
|
|
153
|
+
url = url.replace(
|
|
154
|
+
/\*[^/]*$/,
|
|
155
|
+
encodePathParam(wildcardValue),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
101
158
|
}
|
|
159
|
+
entries.push({
|
|
160
|
+
urlPath: url.replace(/\/$/, "") || "/",
|
|
161
|
+
routeName,
|
|
162
|
+
concurrency,
|
|
163
|
+
...(hasBuildVars ? { buildVars } : {}),
|
|
164
|
+
isPassthroughRoute,
|
|
165
|
+
});
|
|
102
166
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
167
|
+
resolvedRoutes++;
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
resolvedRoutes++;
|
|
170
|
+
// Skip in getParams() skips the entire route
|
|
171
|
+
if (err.name === "Skip") {
|
|
172
|
+
console.log(
|
|
173
|
+
`[rsc-router] SKIP route "${routeName}" - ${err.message}`,
|
|
174
|
+
);
|
|
175
|
+
notifyOnError(
|
|
176
|
+
registry,
|
|
177
|
+
err,
|
|
178
|
+
"prerender",
|
|
179
|
+
routeName,
|
|
180
|
+
undefined,
|
|
181
|
+
true,
|
|
182
|
+
);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Regular error: fail the build
|
|
186
|
+
console.error(
|
|
187
|
+
`[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`,
|
|
124
188
|
);
|
|
125
|
-
|
|
189
|
+
notifyOnError(registry, err, "prerender", routeName);
|
|
190
|
+
throw err;
|
|
126
191
|
}
|
|
127
|
-
|
|
128
|
-
console.
|
|
129
|
-
`[rsc-router]
|
|
192
|
+
} else {
|
|
193
|
+
console.warn(
|
|
194
|
+
`[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`,
|
|
130
195
|
);
|
|
131
|
-
notifyOnError(registry, err, "prerender", routeName);
|
|
132
|
-
throw err;
|
|
133
196
|
}
|
|
134
|
-
} else {
|
|
135
|
-
console.warn(
|
|
136
|
-
`[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`,
|
|
137
|
-
);
|
|
138
197
|
}
|
|
139
198
|
}
|
|
140
199
|
}
|
|
200
|
+
} finally {
|
|
201
|
+
if (progressInterval) {
|
|
202
|
+
clearInterval(progressInterval);
|
|
203
|
+
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
204
|
+
console.log(
|
|
205
|
+
`[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
141
208
|
}
|
|
142
209
|
|
|
143
|
-
if (entries.length === 0)
|
|
210
|
+
if (entries.length === 0) {
|
|
211
|
+
debug?.(
|
|
212
|
+
"no prerender entries (done in %sms)",
|
|
213
|
+
(performance.now() - overallStart).toFixed(1),
|
|
214
|
+
);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
144
217
|
|
|
145
218
|
// Determine the max concurrency for the log header
|
|
146
219
|
const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
|
|
@@ -149,6 +222,11 @@ export async function expandPrerenderRoutes(
|
|
|
149
222
|
console.log(
|
|
150
223
|
`[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`,
|
|
151
224
|
);
|
|
225
|
+
debug?.(
|
|
226
|
+
"prerender loop: %d entries, max concurrency %d",
|
|
227
|
+
entries.length,
|
|
228
|
+
maxConcurrency,
|
|
229
|
+
);
|
|
152
230
|
|
|
153
231
|
const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
|
|
154
232
|
|
|
@@ -175,6 +253,7 @@ export async function expandPrerenderRoutes(
|
|
|
175
253
|
{},
|
|
176
254
|
entry.buildVars,
|
|
177
255
|
entry.isPassthroughRoute,
|
|
256
|
+
state.resolvedBuildEnv,
|
|
178
257
|
);
|
|
179
258
|
if (!result) continue;
|
|
180
259
|
|
|
@@ -265,6 +344,13 @@ export async function expandPrerenderRoutes(
|
|
|
265
344
|
console.log(
|
|
266
345
|
`[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`,
|
|
267
346
|
);
|
|
347
|
+
debug?.(
|
|
348
|
+
"expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
|
|
349
|
+
doneCount,
|
|
350
|
+
skipCount,
|
|
351
|
+
totalElapsed,
|
|
352
|
+
(performance.now() - overallStart).toFixed(1),
|
|
353
|
+
);
|
|
268
354
|
}
|
|
269
355
|
|
|
270
356
|
/**
|
|
@@ -285,6 +371,12 @@ export async function renderStaticHandlers(
|
|
|
285
371
|
)
|
|
286
372
|
return;
|
|
287
373
|
|
|
374
|
+
const overallStart = debug ? performance.now() : 0;
|
|
375
|
+
debug?.(
|
|
376
|
+
"renderStaticHandlers: start (%d static module(s))",
|
|
377
|
+
state.resolvedStaticModules.size,
|
|
378
|
+
);
|
|
379
|
+
|
|
288
380
|
const manifestEntries: Record<string, string> = {};
|
|
289
381
|
let staticDone = 0;
|
|
290
382
|
let staticSkip = 0;
|
|
@@ -326,6 +418,8 @@ export async function renderStaticHandlers(
|
|
|
326
418
|
def.handler,
|
|
327
419
|
def.$$id,
|
|
328
420
|
(def as any).$$routePrefix,
|
|
421
|
+
state.resolvedBuildEnv,
|
|
422
|
+
!state.isBuildMode,
|
|
329
423
|
);
|
|
330
424
|
if (result) {
|
|
331
425
|
const hasHandles = Object.keys(result.handles).length > 0;
|
|
@@ -382,4 +476,11 @@ export async function renderStaticHandlers(
|
|
|
382
476
|
console.log(
|
|
383
477
|
`[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`,
|
|
384
478
|
);
|
|
479
|
+
debug?.(
|
|
480
|
+
"renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
|
|
481
|
+
staticDone,
|
|
482
|
+
staticSkip,
|
|
483
|
+
totalStaticElapsed,
|
|
484
|
+
(performance.now() - overallStart).toFixed(1),
|
|
485
|
+
);
|
|
385
486
|
}
|
|
@@ -22,6 +22,32 @@ export function markSelfGenWrite(
|
|
|
22
22
|
export function consumeSelfGenWrite(
|
|
23
23
|
state: DiscoveryState,
|
|
24
24
|
filePath: string,
|
|
25
|
+
): boolean {
|
|
26
|
+
return checkSelfGenWrite(state, filePath, true);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Non-consuming variant. Used by the `handleHotUpdate` plugin hook to
|
|
31
|
+
* suppress vite's HMR cascade for our own gen-file writes WITHOUT
|
|
32
|
+
* consuming the entry — `consumeSelfGenWrite` (called later from the
|
|
33
|
+
* chokidar `change` handler in `handleRouteFileChange`) still needs to
|
|
34
|
+
* see and consume the same entry to short-circuit our regen path.
|
|
35
|
+
*
|
|
36
|
+
* Both hooks fire for the same file change event:
|
|
37
|
+
* - `handleHotUpdate` runs first (vite's HMR pipeline).
|
|
38
|
+
* - chokidar `change` callback runs after (filesystem watcher).
|
|
39
|
+
*/
|
|
40
|
+
export function peekSelfGenWrite(
|
|
41
|
+
state: DiscoveryState,
|
|
42
|
+
filePath: string,
|
|
43
|
+
): boolean {
|
|
44
|
+
return checkSelfGenWrite(state, filePath, false);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkSelfGenWrite(
|
|
48
|
+
state: DiscoveryState,
|
|
49
|
+
filePath: string,
|
|
50
|
+
consume: boolean,
|
|
25
51
|
): boolean {
|
|
26
52
|
const info = state.selfWrittenGenFiles.get(filePath);
|
|
27
53
|
if (!info) return false;
|
|
@@ -33,7 +59,7 @@ export function consumeSelfGenWrite(
|
|
|
33
59
|
const current = readFileSync(filePath, "utf-8");
|
|
34
60
|
const currentHash = createHash("sha256").update(current).digest("hex");
|
|
35
61
|
if (currentHash === info.hash) {
|
|
36
|
-
state.selfWrittenGenFiles.delete(filePath);
|
|
62
|
+
if (consume) state.selfWrittenGenFiles.delete(filePath);
|
|
37
63
|
return true;
|
|
38
64
|
}
|
|
39
65
|
// Hash mismatch: file was changed externally. Keep the entry so a
|
|
@@ -13,11 +13,13 @@ export const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
|
|
|
13
13
|
export interface PluginOptions {
|
|
14
14
|
enableBuildPrerender?: boolean;
|
|
15
15
|
staticRouteTypesGeneration?: boolean;
|
|
16
|
-
include?: string[];
|
|
17
|
-
exclude?: string[];
|
|
18
16
|
// Mutable ref for deferred auto-discovery (node preset).
|
|
19
17
|
// The auto-discover config() hook populates this before configResolved.
|
|
20
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";
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface PrecomputedEntry {
|
|
@@ -58,8 +60,8 @@ export interface DiscoveryState {
|
|
|
58
60
|
|
|
59
61
|
prerenderManifestEntries: Record<string, string> | null;
|
|
60
62
|
staticManifestEntries: Record<string, string> | null;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
handlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
64
|
+
staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
63
65
|
rscEntryFileName: string | null;
|
|
64
66
|
resolvedPrerenderModules: Map<string, string[]> | undefined;
|
|
65
67
|
resolvedStaticModules: Map<string, string[]> | undefined;
|
|
@@ -69,6 +71,11 @@ export interface DiscoveryState {
|
|
|
69
71
|
devServer: any;
|
|
70
72
|
selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
|
|
71
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;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
export function createDiscoveryState(
|
|
@@ -95,8 +102,8 @@ export function createDiscoveryState(
|
|
|
95
102
|
|
|
96
103
|
prerenderManifestEntries: null,
|
|
97
104
|
staticManifestEntries: null,
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
handlerChunkInfoMap: new Map(),
|
|
106
|
+
staticHandlerChunkInfoMap: new Map(),
|
|
100
107
|
rscEntryFileName: null,
|
|
101
108
|
resolvedPrerenderModules: undefined,
|
|
102
109
|
resolvedStaticModules: undefined,
|
package/src/vite/index.ts
CHANGED
package/src/vite/plugin-types.ts
CHANGED
|
@@ -1,38 +1,53 @@
|
|
|
1
|
+
// -- Build-time environment types -------------------------------------------
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
+
* Context passed to a buildEnv factory function.
|
|
5
|
+
* Provides Vite config details for conditional env setup.
|
|
4
6
|
*/
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export interface BuildEnvFactoryContext {
|
|
8
|
+
/** Vite project root directory. */
|
|
9
|
+
root: string;
|
|
10
|
+
/** Vite mode (e.g. "development", "production"). */
|
|
11
|
+
mode: string;
|
|
12
|
+
/** Vite command ("serve" for dev, "build" for production). */
|
|
13
|
+
command: "serve" | "build";
|
|
14
|
+
/** Router deployment preset. */
|
|
15
|
+
preset: "node" | "cloudflare";
|
|
16
|
+
}
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Factory function that creates build-time environment bindings.
|
|
20
|
+
* Called once at plugin startup. Return `dispose` to clean up resources.
|
|
21
|
+
*/
|
|
22
|
+
export type BuildEnvFactory = (
|
|
23
|
+
ctx: BuildEnvFactoryContext,
|
|
24
|
+
) => Promise<BuildEnvResult> | BuildEnvResult;
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Result of resolving build-time environment bindings.
|
|
28
|
+
*/
|
|
29
|
+
export interface BuildEnvResult {
|
|
30
|
+
/** Environment bindings available to Prerender/Static handlers via ctx.env. */
|
|
31
|
+
env: Record<string, unknown>;
|
|
32
|
+
/** Called after build completes to clean up resources (e.g., miniflare). */
|
|
33
|
+
dispose?: () => Promise<void> | void;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
/**
|
|
26
|
-
*
|
|
37
|
+
* Build-time environment configuration for Prerender and Static handlers.
|
|
38
|
+
*
|
|
39
|
+
* - `false` (default): no build-time env, `ctx.env` throws.
|
|
40
|
+
* - `"auto"`: calls `wrangler.getPlatformProxy()` (cloudflare preset only).
|
|
41
|
+
* - Object: used directly as `ctx.env` during build.
|
|
42
|
+
* - Factory: called once at startup, must return `{ env, dispose? }`.
|
|
27
43
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
44
|
+
export type BuildEnvOption =
|
|
45
|
+
| false
|
|
46
|
+
| "auto"
|
|
47
|
+
| Record<string, unknown>
|
|
48
|
+
| BuildEnvFactory;
|
|
49
|
+
|
|
50
|
+
// -- Plugin options ---------------------------------------------------------
|
|
36
51
|
|
|
37
52
|
/**
|
|
38
53
|
* Base options shared by all presets
|
|
@@ -45,27 +60,16 @@ interface RangoBaseOptions {
|
|
|
45
60
|
banner?: boolean;
|
|
46
61
|
|
|
47
62
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* Glob patterns for files to include in route type scanning.
|
|
57
|
-
* Only files matching at least one pattern will be scanned.
|
|
58
|
-
* Patterns are relative to the project root.
|
|
59
|
-
* When unset, all .ts/.tsx files are scanned.
|
|
60
|
-
*/
|
|
61
|
-
include?: string[];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Glob patterns for files to exclude from route type scanning.
|
|
65
|
-
* Takes precedence over `include`. Patterns are relative to the project root.
|
|
66
|
-
* Defaults to common test/build directories.
|
|
63
|
+
* Environment bindings available to Prerender and Static handlers at build
|
|
64
|
+
* time via `ctx.env`. Applies to both production build and dev on-demand
|
|
65
|
+
* prerender (`/__rsc_prerender`).
|
|
66
|
+
*
|
|
67
|
+
* This is the build-time env supplied by the Vite plugin, not the live
|
|
68
|
+
* request env. It is shared across all prerender invocations for the build.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
67
71
|
*/
|
|
68
|
-
|
|
72
|
+
buildEnv?: BuildEnvOption;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
@@ -76,38 +80,6 @@ export interface RangoNodeOptions extends RangoBaseOptions {
|
|
|
76
80
|
* Deployment preset. Defaults to 'node' when not specified.
|
|
77
81
|
*/
|
|
78
82
|
preset?: "node";
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Path to your router configuration file that exports the route tree.
|
|
82
|
-
* This file must export a `router` object created with `createRouter()`.
|
|
83
|
-
*
|
|
84
|
-
* When omitted, auto-discovers the router by scanning for files containing
|
|
85
|
-
* `createRouter`. If exactly one is found, it is used automatically.
|
|
86
|
-
* If multiple are found, an error is thrown with the list of candidates.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```ts
|
|
90
|
-
* rango({ router: './src/router.tsx' })
|
|
91
|
-
* // or simply:
|
|
92
|
-
* rango()
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
router?: string;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* RSC plugin configuration. By default, rsc-router includes @vitejs/plugin-rsc
|
|
99
|
-
* with sensible defaults.
|
|
100
|
-
*
|
|
101
|
-
* Entry files (browser, ssr, rsc) are optional - if they don't exist,
|
|
102
|
-
* virtual defaults are used.
|
|
103
|
-
*
|
|
104
|
-
* - Omit or pass `true`/`{}` to use defaults (recommended)
|
|
105
|
-
* - Pass `{ entries: {...} }` to customize entry paths
|
|
106
|
-
* - Pass `false` to disable (for manual @vitejs/plugin-rsc configuration)
|
|
107
|
-
*
|
|
108
|
-
* @default true
|
|
109
|
-
*/
|
|
110
|
-
rsc?: boolean | RscPluginOptions;
|
|
111
83
|
}
|
|
112
84
|
|
|
113
85
|
/**
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
import { createRangoDebugger, NS } from "../debug.js";
|
|
3
|
+
|
|
4
|
+
const debug = createRangoDebugger(NS.transform);
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Transform CJS vendor files from @vitejs/plugin-rsc to ESM for browser compatibility.
|
|
@@ -21,6 +24,7 @@ export function createCjsToEsmPlugin(): Plugin {
|
|
|
21
24
|
? "./cjs/react-server-dom-webpack-client.browser.production.js"
|
|
22
25
|
: "./cjs/react-server-dom-webpack-client.browser.development.js";
|
|
23
26
|
|
|
27
|
+
debug?.("cjs-to-esm entry redirect %s", id);
|
|
24
28
|
return {
|
|
25
29
|
code: `export * from "${cjsFile}";`,
|
|
26
30
|
map: null,
|
|
@@ -81,6 +85,7 @@ export function createCjsToEsmPlugin(): Plugin {
|
|
|
81
85
|
// Reconstruct with license at the top
|
|
82
86
|
transformed = license + "\n" + transformed;
|
|
83
87
|
|
|
88
|
+
debug?.("cjs-to-esm body rewrite %s", id);
|
|
84
89
|
return {
|
|
85
90
|
code: transformed,
|
|
86
91
|
map: null,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
|
+
import { createRangoDebugger, NS } from "../debug.js";
|
|
3
|
+
|
|
4
|
+
const debug = createRangoDebugger(NS.transform);
|
|
2
5
|
|
|
3
6
|
const CLIENT_IN_SERVER_PROXY_PREFIX =
|
|
4
7
|
"virtual:vite-rsc/client-in-server-package-proxy/";
|
|
@@ -62,6 +65,7 @@ export function extractPackageName(absolutePath: string): string | null {
|
|
|
62
65
|
*/
|
|
63
66
|
export function clientRefDedup(): Plugin {
|
|
64
67
|
let clientExclude: string[] = [];
|
|
68
|
+
const dedupedPackages = new Set<string>();
|
|
65
69
|
|
|
66
70
|
return {
|
|
67
71
|
name: "@rangojs/router:client-ref-dedup",
|
|
@@ -76,6 +80,16 @@ export function clientRefDedup(): Plugin {
|
|
|
76
80
|
clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
|
|
77
81
|
},
|
|
78
82
|
|
|
83
|
+
buildEnd() {
|
|
84
|
+
if (debug && dedupedPackages.size > 0) {
|
|
85
|
+
debug(
|
|
86
|
+
"client-ref-dedup: redirected %d package(s) (%s)",
|
|
87
|
+
dedupedPackages.size,
|
|
88
|
+
[...dedupedPackages].join(","),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
79
93
|
resolveId(source, importer, options) {
|
|
80
94
|
// Only intercept in the client environment
|
|
81
95
|
if (this.environment?.name !== "client") return;
|
|
@@ -95,6 +109,8 @@ export function clientRefDedup(): Plugin {
|
|
|
95
109
|
// Don't redirect packages that are excluded from optimization
|
|
96
110
|
if (clientExclude.includes(packageName)) return;
|
|
97
111
|
|
|
112
|
+
if (debug) dedupedPackages.add(packageName);
|
|
113
|
+
|
|
98
114
|
// Return a virtual module that re-exports via bare specifier
|
|
99
115
|
return `\0rango:dedup/${packageName}`;
|
|
100
116
|
},
|