@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26
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 +294 -28
- package/dist/bin/rango.js +355 -47
- package/dist/vite/index.js +1658 -1239
- package/package.json +3 -3
- package/skills/cache-guide/SKILL.md +9 -5
- package/skills/caching/SKILL.md +4 -4
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +229 -15
- package/skills/middleware/SKILL.md +109 -30
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +189 -19
- package/skills/rango/SKILL.md +1 -2
- package/skills/response-routes/SKILL.md +3 -3
- package/skills/route/SKILL.md +44 -3
- package/skills/router-setup/SKILL.md +80 -3
- package/skills/theme/SKILL.md +5 -4
- package/skills/typesafety/SKILL.md +59 -16
- package/skills/use-cache/SKILL.md +16 -2
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +56 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +29 -48
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +19 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +66 -443
- package/src/browser/navigation-client.ts +34 -62
- package/src/browser/navigation-store.ts +4 -33
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/partial-update.ts +103 -151
- package/src/browser/prefetch/cache.ts +67 -0
- package/src/browser/prefetch/fetch.ts +137 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +154 -44
- package/src/browser/react/NavigationProvider.tsx +32 -0
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +29 -11
- package/src/browser/react/location-state.ts +6 -4
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +23 -45
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +21 -64
- package/src/browser/react/use-navigation.ts +7 -32
- package/src/browser/react/use-params.ts +5 -34
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +3 -6
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +75 -114
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +46 -22
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +458 -405
- package/src/browser/types.ts +21 -35
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +38 -13
- package/src/build/generate-route-types.ts +4 -0
- package/src/build/index.ts +1 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/codegen.ts +13 -4
- package/src/build/route-types/include-resolution.ts +13 -0
- package/src/build/route-types/per-module-writer.ts +15 -3
- package/src/build/route-types/router-processing.ts +170 -18
- package/src/build/runtime-discovery.ts +13 -1
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +136 -123
- package/src/cache/cache-scope.ts +76 -83
- package/src/cache/cf/cf-cache-store.ts +12 -7
- package/src/cache/document-cache.ts +93 -69
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +43 -69
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +140 -117
- package/src/cache/taint.ts +30 -3
- package/src/cache/types.ts +1 -115
- package/src/client.rsc.tsx +0 -1
- package/src/client.tsx +53 -76
- package/src/errors.ts +6 -1
- package/src/handle.ts +1 -1
- package/src/handles/MetaTags.tsx +5 -2
- package/src/host/cookie-handler.ts +8 -3
- package/src/host/index.ts +0 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +53 -10
- package/src/index.ts +73 -43
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +60 -18
- package/src/prerender.ts +76 -18
- package/src/reverse.ts +11 -7
- package/src/root-error-boundary.tsx +30 -26
- package/src/route-definition/dsl-helpers.ts +9 -6
- package/src/route-definition/index.ts +0 -3
- package/src/route-definition/redirect.ts +15 -3
- package/src/route-map-builder.ts +38 -2
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +7 -0
- package/src/router/content-negotiation.ts +1 -1
- package/src/router/debug-manifest.ts +16 -3
- package/src/router/handler-context.ts +96 -17
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +6 -11
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +62 -54
- package/src/router/match-context.ts +3 -0
- package/src/router/match-handlers.ts +185 -11
- package/src/router/match-middleware/background-revalidation.ts +65 -85
- package/src/router/match-middleware/cache-lookup.ts +78 -10
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/match-result.ts +0 -9
- package/src/router/metrics.ts +233 -13
- package/src/router/middleware-types.ts +34 -39
- package/src/router/middleware.ts +290 -130
- package/src/router/pattern-matching.ts +61 -10
- package/src/router/prerender-match.ts +36 -6
- package/src/router/preview-match.ts +7 -1
- package/src/router/revalidation.ts +61 -2
- package/src/router/router-context.ts +15 -0
- package/src/router/router-interfaces.ts +158 -40
- package/src/router/router-options.ts +223 -1
- package/src/router/router-registry.ts +5 -2
- package/src/router/segment-resolution/fresh.ts +165 -242
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +102 -98
- package/src/router/segment-resolution/revalidation.ts +394 -272
- package/src/router/segment-resolution/static-store.ts +2 -2
- package/src/router/segment-resolution.ts +1 -3
- package/src/router/segment-wrappers.ts +3 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +20 -2
- package/src/router/types.ts +7 -1
- package/src/router.ts +203 -18
- package/src/rsc/handler-context.ts +13 -2
- package/src/rsc/handler.ts +489 -438
- package/src/rsc/helpers.ts +125 -5
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/manifest-init.ts +3 -2
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +245 -19
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +47 -43
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +166 -66
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +20 -2
- package/src/search-params.ts +38 -23
- package/src/server/context.ts +61 -7
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +84 -12
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +275 -49
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +67 -28
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +4 -18
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +6 -1
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +22 -0
- package/src/types/handler-context.ts +103 -16
- package/src/types/index.ts +1 -1
- package/src/types/loader-types.ts +9 -6
- package/src/types/route-config.ts +17 -26
- package/src/types/route-entry.ts +28 -0
- package/src/types/segments.ts +0 -5
- package/src/urls/include-helper.ts +49 -8
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +30 -12
- package/src/urls/path-helper.ts +17 -2
- package/src/urls/pattern-types.ts +21 -1
- package/src/urls/response-types.ts +29 -7
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +27 -9
- package/src/vite/discovery/bundle-postprocess.ts +32 -52
- package/src/vite/discovery/discover-routers.ts +52 -26
- package/src/vite/discovery/prerender-collection.ts +58 -41
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/state.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/index.ts +10 -51
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/expose-internal-ids.ts +4 -3
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/plugins/version-plugin.ts +188 -18
- package/src/vite/rango.ts +61 -36
- package/src/vite/router-discovery.ts +173 -100
- package/src/vite/utils/prerender-utils.ts +81 -0
- package/src/vite/utils/shared-utils.ts +19 -9
- package/skills/testing/SKILL.md +0 -226
- package/src/browser/lru-cache.ts +0 -61
- package/src/browser/react/prefetch.ts +0 -27
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/route-definition/route-function.ts +0 -119
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/{CLAUDE.md → AGENTS.md} +0 -0
package/src/index.ts
CHANGED
|
@@ -34,7 +34,6 @@ export {
|
|
|
34
34
|
export type {
|
|
35
35
|
// Configuration types
|
|
36
36
|
DocumentProps,
|
|
37
|
-
RouterEnv,
|
|
38
37
|
DefaultEnv,
|
|
39
38
|
RouteDefinition,
|
|
40
39
|
RouteConfig,
|
|
@@ -116,25 +115,32 @@ export type {
|
|
|
116
115
|
// Middleware context types
|
|
117
116
|
export type { MiddlewareContext, CookieOptions } from "./router/middleware.js";
|
|
118
117
|
|
|
118
|
+
function serverOnlyStubError(name: string): Error {
|
|
119
|
+
return new Error(
|
|
120
|
+
`${name}() is only available from "@rangojs/router" in a react-server/RSC environment. ` +
|
|
121
|
+
`For client hooks and components, import from "@rangojs/router/client".`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
119
125
|
/**
|
|
120
126
|
* Error-throwing stub for server-only `urls` function.
|
|
121
127
|
*/
|
|
122
128
|
export function urls(): never {
|
|
123
|
-
throw
|
|
129
|
+
throw serverOnlyStubError("urls");
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
/**
|
|
127
133
|
* Error-throwing stub for server-only `createRouter` function.
|
|
128
134
|
*/
|
|
129
135
|
export function createRouter(): never {
|
|
130
|
-
throw
|
|
136
|
+
throw serverOnlyStubError("createRouter");
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
/**
|
|
134
140
|
* Error-throwing stub for server-only `redirect` function.
|
|
135
141
|
*/
|
|
136
142
|
export function redirect(): never {
|
|
137
|
-
throw
|
|
143
|
+
throw serverOnlyStubError("redirect");
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
// Handle API (universal - works on both server and client)
|
|
@@ -150,108 +156,105 @@ export { nonce } from "./rsc/nonce.js";
|
|
|
150
156
|
* Error-throwing stub for server-only `Prerender` function.
|
|
151
157
|
*/
|
|
152
158
|
export function Prerender(): never {
|
|
153
|
-
throw
|
|
159
|
+
throw serverOnlyStubError("Prerender");
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
/**
|
|
157
163
|
* Error-throwing stub for server-only `Static` function.
|
|
158
164
|
*/
|
|
159
165
|
export function Static(): never {
|
|
160
|
-
throw
|
|
166
|
+
throw serverOnlyStubError("Static");
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
/**
|
|
164
170
|
* Error-throwing stub for server-only `getRequestContext` function.
|
|
165
171
|
*/
|
|
166
172
|
export function getRequestContext(): never {
|
|
167
|
-
throw
|
|
168
|
-
"getRequestContext() is server-only and requires RSC context.",
|
|
169
|
-
);
|
|
173
|
+
throw serverOnlyStubError("getRequestContext");
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
/**
|
|
173
|
-
* Error-throwing stub for server-only `
|
|
177
|
+
* Error-throwing stub for server-only `cookies` function.
|
|
174
178
|
*/
|
|
175
|
-
export function
|
|
176
|
-
throw
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
export function cookies(): never {
|
|
180
|
+
throw serverOnlyStubError("cookies");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Error-throwing stub for server-only `headers` function.
|
|
185
|
+
*/
|
|
186
|
+
export function headers(): never {
|
|
187
|
+
throw serverOnlyStubError("headers");
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
/**
|
|
182
191
|
* Error-throwing stub for server-only `createReverse` function.
|
|
183
192
|
*/
|
|
184
193
|
export function createReverse(): never {
|
|
185
|
-
throw
|
|
194
|
+
throw serverOnlyStubError("createReverse");
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
/**
|
|
189
198
|
* Error-throwing stub for server-only `enableMatchDebug` function.
|
|
190
199
|
*/
|
|
191
200
|
export function enableMatchDebug(): never {
|
|
192
|
-
throw
|
|
193
|
-
"enableMatchDebug() is server-only and requires RSC context.",
|
|
194
|
-
);
|
|
201
|
+
throw serverOnlyStubError("enableMatchDebug");
|
|
195
202
|
}
|
|
196
203
|
|
|
197
204
|
/**
|
|
198
205
|
* Error-throwing stub for server-only `getMatchDebugStats` function.
|
|
199
206
|
*/
|
|
200
207
|
export function getMatchDebugStats(): never {
|
|
201
|
-
throw
|
|
202
|
-
"getMatchDebugStats() is server-only and requires RSC context.",
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Error-throwing stub for server-only `track` function.
|
|
208
|
-
*/
|
|
209
|
-
export function track(): never {
|
|
210
|
-
throw new Error("track() is server-only and requires RSC context.");
|
|
208
|
+
throw serverOnlyStubError("getMatchDebugStats");
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
// Error-throwing stubs for server-only route helpers
|
|
214
212
|
export function layout(): never {
|
|
215
|
-
throw
|
|
213
|
+
throw serverOnlyStubError("layout");
|
|
216
214
|
}
|
|
217
215
|
export function cache(): never {
|
|
218
|
-
throw
|
|
216
|
+
throw serverOnlyStubError("cache");
|
|
219
217
|
}
|
|
220
218
|
export function middleware(): never {
|
|
221
|
-
throw
|
|
219
|
+
throw serverOnlyStubError("middleware");
|
|
222
220
|
}
|
|
223
221
|
export function revalidate(): never {
|
|
224
|
-
throw
|
|
222
|
+
throw serverOnlyStubError("revalidate");
|
|
225
223
|
}
|
|
226
224
|
export function loader(): never {
|
|
227
|
-
throw
|
|
225
|
+
throw serverOnlyStubError("loader");
|
|
228
226
|
}
|
|
229
227
|
export function loading(): never {
|
|
230
|
-
throw
|
|
228
|
+
throw serverOnlyStubError("loading");
|
|
231
229
|
}
|
|
232
230
|
export function parallel(): never {
|
|
233
|
-
throw
|
|
231
|
+
throw serverOnlyStubError("parallel");
|
|
234
232
|
}
|
|
235
233
|
export function intercept(): never {
|
|
236
|
-
throw
|
|
234
|
+
throw serverOnlyStubError("intercept");
|
|
237
235
|
}
|
|
238
236
|
export function when(): never {
|
|
239
|
-
throw
|
|
237
|
+
throw serverOnlyStubError("when");
|
|
240
238
|
}
|
|
241
239
|
export function errorBoundary(): never {
|
|
242
|
-
throw
|
|
240
|
+
throw serverOnlyStubError("errorBoundary");
|
|
243
241
|
}
|
|
244
242
|
export function notFoundBoundary(): never {
|
|
245
|
-
throw
|
|
246
|
-
"notFoundBoundary() is server-only and requires RSC context.",
|
|
247
|
-
);
|
|
243
|
+
throw serverOnlyStubError("notFoundBoundary");
|
|
248
244
|
}
|
|
249
245
|
export function transition(): never {
|
|
250
|
-
throw
|
|
246
|
+
throw serverOnlyStubError("transition");
|
|
251
247
|
}
|
|
252
248
|
|
|
253
249
|
// Request context type (safe for client)
|
|
254
|
-
export type { RequestContext } from "./server/request-context.js";
|
|
250
|
+
export type { PublicRequestContext as RequestContext } from "./server/request-context.js";
|
|
251
|
+
|
|
252
|
+
// Cookie store types (safe for client)
|
|
253
|
+
export type {
|
|
254
|
+
CookieStore,
|
|
255
|
+
Cookie,
|
|
256
|
+
ReadonlyHeaders,
|
|
257
|
+
} from "./server/cookie-store.js";
|
|
255
258
|
|
|
256
259
|
// Meta types
|
|
257
260
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
@@ -278,3 +281,30 @@ export {
|
|
|
278
281
|
|
|
279
282
|
// Path-based response type lookup from RegisteredRoutes
|
|
280
283
|
export type { PathResponse } from "./href-client.js";
|
|
284
|
+
|
|
285
|
+
// Telemetry sink
|
|
286
|
+
export { createConsoleSink } from "./router/telemetry.js";
|
|
287
|
+
export { createOTelSink } from "./router/telemetry-otel.js";
|
|
288
|
+
export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
|
|
289
|
+
export type {
|
|
290
|
+
TelemetrySink,
|
|
291
|
+
TelemetryEvent,
|
|
292
|
+
RequestStartEvent,
|
|
293
|
+
RequestEndEvent,
|
|
294
|
+
RequestErrorEvent,
|
|
295
|
+
RequestTimeoutEvent,
|
|
296
|
+
LoaderStartEvent,
|
|
297
|
+
LoaderEndEvent,
|
|
298
|
+
LoaderErrorEvent,
|
|
299
|
+
HandlerErrorEvent,
|
|
300
|
+
CacheDecisionEvent,
|
|
301
|
+
RevalidationDecisionEvent,
|
|
302
|
+
} from "./router/telemetry.js";
|
|
303
|
+
|
|
304
|
+
// Timeout types and error class
|
|
305
|
+
export { RouterTimeoutError } from "./router/timeout.js";
|
|
306
|
+
export type {
|
|
307
|
+
RouterTimeouts,
|
|
308
|
+
TimeoutPhase,
|
|
309
|
+
TimeoutContext,
|
|
310
|
+
} from "./router/timeout.js";
|
package/src/loader.rsc.ts
CHANGED
|
@@ -52,11 +52,19 @@ export function createLoader<T>(
|
|
|
52
52
|
// For fetchable loaders, __injectedId is also passed as a parameter
|
|
53
53
|
const loaderId = __injectedId || "";
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (!loaderId && process.env.NODE_ENV === "development") {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"[rsc-router] Loader is missing $$id. " +
|
|
58
|
+
"Make sure the exposeInternalIds Vite plugin is enabled and " +
|
|
59
|
+
"the loader is exported with: export const MyLoader = createLoader(...)",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If not fetchable, store fn in registry (for SSR ctx.use() resolution)
|
|
64
|
+
// but mark fetchable=false so the _rsc_loader endpoint rejects it.
|
|
57
65
|
if (fetchable === undefined) {
|
|
58
66
|
if (fn && loaderId) {
|
|
59
|
-
registerFetchableLoader(loaderId, fn, []);
|
|
67
|
+
registerFetchableLoader(loaderId, fn, [], false);
|
|
60
68
|
}
|
|
61
69
|
return {
|
|
62
70
|
__brand: "loader",
|
|
@@ -71,7 +79,7 @@ export function createLoader<T>(
|
|
|
71
79
|
// Register the function in the internal registry by $$id (server-side only)
|
|
72
80
|
// The loader fetch handler looks it up by $$id when load() is called from the client.
|
|
73
81
|
if (fn && loaderId) {
|
|
74
|
-
registerFetchableLoader(loaderId, fn, middleware);
|
|
82
|
+
registerFetchableLoader(loaderId, fn, middleware, true);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
return {
|
package/src/loader.ts
CHANGED
|
@@ -49,6 +49,14 @@ export function createLoader<T>(
|
|
|
49
49
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
50
50
|
const loaderId = __injectedId || "";
|
|
51
51
|
|
|
52
|
+
if (!loaderId && process.env.NODE_ENV === "development") {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"[rsc-router] Loader is missing $$id. " +
|
|
55
|
+
"Make sure the exposeInternalIds Vite plugin is enabled and " +
|
|
56
|
+
"the loader is exported with: export const MyLoader = createLoader(...)",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
return {
|
|
53
61
|
__brand: "loader",
|
|
54
62
|
$$id: loaderId,
|
package/src/prerender/store.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Prerender Store
|
|
3
3
|
*
|
|
4
4
|
* Reads pre-rendered segment data from the worker bundle at build time.
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The manifest module is lazily loaded via globalThis.__loadPrerenderManifestModule,
|
|
6
|
+
* a function injected into the RSC entry that returns the manifest module
|
|
7
|
+
* containing a key-to-specifier map and a `loadPrerenderAsset` function
|
|
8
|
+
* that anchors import() resolution relative to the manifest file.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type {
|
|
@@ -21,7 +22,7 @@ export interface PrerenderStore {
|
|
|
21
22
|
get(
|
|
22
23
|
routeName: string,
|
|
23
24
|
paramHash: string,
|
|
24
|
-
meta?: { pathname: string },
|
|
25
|
+
meta?: { pathname: string; isPassthroughRoute?: boolean },
|
|
25
26
|
): PrerenderEntry | null | Promise<PrerenderEntry | null>;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -34,11 +35,20 @@ export interface StaticStore {
|
|
|
34
35
|
get(handlerId: string): Promise<StaticEntry | null>;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
interface PrerenderManifestModule {
|
|
39
|
+
default: Record<string, string>;
|
|
40
|
+
loadPrerenderAsset: (
|
|
41
|
+
specifier: string,
|
|
42
|
+
) => Promise<{ default: PrerenderEntry }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
declare global {
|
|
38
|
-
// Injected by closeBundle post-processing:
|
|
46
|
+
// Injected by closeBundle post-processing: lazy loader for the prerender
|
|
47
|
+
// manifest module. The module exports a key→specifier map and a
|
|
48
|
+
// loadPrerenderAsset function that anchors import() relative to the manifest.
|
|
39
49
|
// eslint-disable-next-line no-var
|
|
40
|
-
var
|
|
41
|
-
|
|
|
50
|
+
var __loadPrerenderManifestModule:
|
|
51
|
+
| (() => Promise<PrerenderManifestModule>)
|
|
42
52
|
| undefined;
|
|
43
53
|
// Injected by closeBundle post-processing: map of handlerId -> () => import("./assets/__st-*.js")
|
|
44
54
|
// Asset default export is either a string (no handles) or { encoded, handles } object.
|
|
@@ -58,11 +68,12 @@ declare global {
|
|
|
58
68
|
*/
|
|
59
69
|
export function createDevPrerenderStore(devUrl: string): PrerenderStore {
|
|
60
70
|
return {
|
|
61
|
-
async get(
|
|
71
|
+
async get(routeName, paramHash, meta) {
|
|
62
72
|
if (!meta?.pathname) return null;
|
|
63
73
|
const isIntercept = paramHash.endsWith("/i");
|
|
64
|
-
let url = `${devUrl}/__rsc_prerender?pathname=${encodeURIComponent(meta.pathname)}`;
|
|
74
|
+
let url = `${devUrl}/__rsc_prerender?pathname=${encodeURIComponent(meta.pathname)}&routeName=${encodeURIComponent(routeName)}`;
|
|
65
75
|
if (isIntercept) url += "&intercept=1";
|
|
76
|
+
if (meta.isPassthroughRoute) url += "&passthrough=1";
|
|
66
77
|
try {
|
|
67
78
|
const res = await fetch(url);
|
|
68
79
|
if (!res.ok) return null;
|
|
@@ -77,17 +88,28 @@ export function createDevPrerenderStore(devUrl: string): PrerenderStore {
|
|
|
77
88
|
/**
|
|
78
89
|
* Create a prerender store.
|
|
79
90
|
* Dev mode: on-demand fetch from Vite dev server (node:fs works there).
|
|
80
|
-
* Production: backed by globalThis.
|
|
91
|
+
* Production: backed by globalThis.__loadPrerenderManifestModule which lazily
|
|
92
|
+
* loads the manifest module on first access.
|
|
81
93
|
* Returns null if no prerender data is available.
|
|
82
94
|
*/
|
|
83
95
|
export function createPrerenderStore(): PrerenderStore | null {
|
|
84
96
|
if (globalThis.__PRERENDER_DEV_URL) {
|
|
85
97
|
return createDevPrerenderStore(globalThis.__PRERENDER_DEV_URL);
|
|
86
98
|
}
|
|
87
|
-
|
|
88
|
-
if (!manifest || Object.keys(manifest).length === 0) return null;
|
|
99
|
+
if (!globalThis.__loadPrerenderManifestModule) return null;
|
|
89
100
|
|
|
90
101
|
const cache = new Map<string, Promise<PrerenderEntry | null>>();
|
|
102
|
+
let manifestModulePromise: Promise<PrerenderManifestModule | null> | null =
|
|
103
|
+
null;
|
|
104
|
+
|
|
105
|
+
function loadManifestModule(): Promise<PrerenderManifestModule | null> {
|
|
106
|
+
if (!manifestModulePromise) {
|
|
107
|
+
manifestModulePromise = globalThis.__loadPrerenderManifestModule!().catch(
|
|
108
|
+
() => null,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return manifestModulePromise;
|
|
112
|
+
}
|
|
91
113
|
|
|
92
114
|
return {
|
|
93
115
|
get(routeName: string, paramHash: string): Promise<PrerenderEntry | null> {
|
|
@@ -95,18 +117,38 @@ export function createPrerenderStore(): PrerenderStore | null {
|
|
|
95
117
|
const cached = cache.get(key);
|
|
96
118
|
if (cached) return cached;
|
|
97
119
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
const promise = loadManifestModule().then((mod) => {
|
|
121
|
+
if (!mod) return null;
|
|
122
|
+
const specifier = mod.default[key];
|
|
123
|
+
if (!specifier) return null;
|
|
124
|
+
return mod
|
|
125
|
+
.loadPrerenderAsset(specifier)
|
|
126
|
+
.then((asset) => asset.default)
|
|
127
|
+
.catch(() => null);
|
|
128
|
+
});
|
|
104
129
|
cache.set(key, promise);
|
|
105
130
|
return promise;
|
|
106
131
|
},
|
|
107
132
|
};
|
|
108
133
|
}
|
|
109
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Load the prerender manifest index for test introspection.
|
|
137
|
+
* Returns the key→specifier map or null if unavailable.
|
|
138
|
+
*/
|
|
139
|
+
export async function loadPrerenderManifestIndex(): Promise<Record<
|
|
140
|
+
string,
|
|
141
|
+
string
|
|
142
|
+
> | null> {
|
|
143
|
+
if (!globalThis.__loadPrerenderManifestModule) return null;
|
|
144
|
+
try {
|
|
145
|
+
const mod = await globalThis.__loadPrerenderManifestModule();
|
|
146
|
+
return mod.default;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
110
152
|
/**
|
|
111
153
|
* Create a static segment store.
|
|
112
154
|
* Production only: backed by globalThis.__STATIC_MANIFEST injected at build time.
|
package/src/prerender.ts
CHANGED
|
@@ -34,10 +34,36 @@ import type {
|
|
|
34
34
|
} from "./types.js";
|
|
35
35
|
import type { Handle } from "./handle.js";
|
|
36
36
|
import type { ContextVar } from "./context-var.js";
|
|
37
|
+
import type { ReverseFunction } from "./reverse.js";
|
|
38
|
+
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
37
39
|
import { isCachedFunction } from "./cache/taint.js";
|
|
38
40
|
|
|
39
41
|
// -- Named route resolution types -------------------------------------------
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Reverse function for build contexts (BuildContext, StaticBuildContext, GetParamsContext).
|
|
45
|
+
* Global names get full autocomplete and param validation from the generated route map.
|
|
46
|
+
* Local `.name` calls are accepted but not validated (the include() scope is unknown
|
|
47
|
+
* at the type level).
|
|
48
|
+
*/
|
|
49
|
+
type BuildReverseFunction = [DefaultReverseRouteMap] extends [
|
|
50
|
+
Record<string, string>,
|
|
51
|
+
]
|
|
52
|
+
? // No generated route map — permissive fallback
|
|
53
|
+
(
|
|
54
|
+
name: string,
|
|
55
|
+
params?: Record<string, string>,
|
|
56
|
+
search?: Record<string, unknown>,
|
|
57
|
+
) => string
|
|
58
|
+
: // Generated route map available — typed globals + permissive locals
|
|
59
|
+
ReverseFunction<DefaultReverseRouteMap> & {
|
|
60
|
+
(
|
|
61
|
+
name: `.${string}`,
|
|
62
|
+
params?: Record<string, string>,
|
|
63
|
+
search?: Record<string, unknown>,
|
|
64
|
+
): string;
|
|
65
|
+
};
|
|
66
|
+
|
|
41
67
|
/**
|
|
42
68
|
* Default route map for Prerender named route resolution.
|
|
43
69
|
* Uses GeneratedRouteMap (from gen file) to avoid circular dependencies.
|
|
@@ -143,11 +169,14 @@ export interface BuildContext<TParams> {
|
|
|
143
169
|
search: {};
|
|
144
170
|
|
|
145
171
|
/** URL generation by route name. */
|
|
146
|
-
reverse:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
reverse: BuildReverseFunction;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Signal that this param set should not produce a local prerender artifact.
|
|
176
|
+
* At runtime the handler runs live instead. Only valid on routes declared
|
|
177
|
+
* with `{ passthrough: true }`.
|
|
178
|
+
*/
|
|
179
|
+
passthrough: () => PrerenderPassthroughResult;
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
/**
|
|
@@ -174,11 +203,7 @@ export interface StaticBuildContext {
|
|
|
174
203
|
use: <T>(handle: Handle<T>) => (data: T) => void;
|
|
175
204
|
|
|
176
205
|
/** URL generation by route name. */
|
|
177
|
-
reverse:
|
|
178
|
-
name: string,
|
|
179
|
-
params?: Record<string, string>,
|
|
180
|
-
search?: Record<string, unknown>,
|
|
181
|
-
) => string;
|
|
206
|
+
reverse: BuildReverseFunction;
|
|
182
207
|
}
|
|
183
208
|
|
|
184
209
|
/**
|
|
@@ -196,11 +221,7 @@ export interface GetParamsContext {
|
|
|
196
221
|
};
|
|
197
222
|
|
|
198
223
|
/** URL generation by route name. */
|
|
199
|
-
reverse:
|
|
200
|
-
name: string,
|
|
201
|
-
params?: Record<string, string>,
|
|
202
|
-
search?: Record<string, unknown>,
|
|
203
|
-
) => string;
|
|
224
|
+
reverse: BuildReverseFunction;
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
/**
|
|
@@ -216,7 +237,9 @@ export interface GetParamsContext {
|
|
|
216
237
|
export type PrerenderPassthroughContext<
|
|
217
238
|
TParams = {},
|
|
218
239
|
TEnv = DefaultEnv,
|
|
219
|
-
> = HandlerContext<TParams, TEnv
|
|
240
|
+
> = HandlerContext<TParams, TEnv> & {
|
|
241
|
+
passthrough: () => PrerenderPassthroughResult;
|
|
242
|
+
};
|
|
220
243
|
|
|
221
244
|
export interface PrerenderHandlerDefinition<
|
|
222
245
|
TParams extends Record<string, any> = any,
|
|
@@ -269,7 +292,10 @@ export function Prerender<
|
|
|
269
292
|
ResolvePrerenderParams<T, TRouteMap>,
|
|
270
293
|
TEnv
|
|
271
294
|
>,
|
|
272
|
-
) =>
|
|
295
|
+
) =>
|
|
296
|
+
| ReactNode
|
|
297
|
+
| PrerenderPassthroughResult
|
|
298
|
+
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
273
299
|
options: PrerenderOptions & { passthrough: true },
|
|
274
300
|
__injectedId?: string,
|
|
275
301
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
@@ -313,7 +339,10 @@ export function Prerender<
|
|
|
313
339
|
ResolvePrerenderParams<T, TRouteMap>,
|
|
314
340
|
TEnv
|
|
315
341
|
>,
|
|
316
|
-
) =>
|
|
342
|
+
) =>
|
|
343
|
+
| ReactNode
|
|
344
|
+
| PrerenderPassthroughResult
|
|
345
|
+
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
317
346
|
options: PrerenderOptions & { passthrough: true },
|
|
318
347
|
__injectedId?: string,
|
|
319
348
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
@@ -388,6 +417,35 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
388
417
|
};
|
|
389
418
|
}
|
|
390
419
|
|
|
420
|
+
// -- Passthrough sentinel ---------------------------------------------------
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Sentinel returned by `ctx.passthrough()` to signal that a specific param set
|
|
424
|
+
* should not produce a local prerender artifact. The build skips writing the
|
|
425
|
+
* entry; at runtime the handler runs live (requires `{ passthrough: true }`).
|
|
426
|
+
*/
|
|
427
|
+
export const PRERENDER_PASSTHROUGH: Readonly<{
|
|
428
|
+
__brand: "prerenderPassthrough";
|
|
429
|
+
}> = Object.freeze({
|
|
430
|
+
__brand: "prerenderPassthrough" as const,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
export type PrerenderPassthroughResult = typeof PRERENDER_PASSTHROUGH;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Type guard to check if a value is the passthrough sentinel.
|
|
437
|
+
*/
|
|
438
|
+
export function isPrerenderPassthrough(
|
|
439
|
+
value: unknown,
|
|
440
|
+
): value is PrerenderPassthroughResult {
|
|
441
|
+
return (
|
|
442
|
+
typeof value === "object" &&
|
|
443
|
+
value !== null &&
|
|
444
|
+
"__brand" in value &&
|
|
445
|
+
(value as { __brand: unknown }).__brand === "prerenderPassthrough"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
391
449
|
// -- Type guard -------------------------------------------------------------
|
|
392
450
|
|
|
393
451
|
/**
|
package/src/reverse.ts
CHANGED
|
@@ -304,13 +304,17 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
304
304
|
let result = pattern;
|
|
305
305
|
if (params) {
|
|
306
306
|
// Replace :param placeholders with actual values
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
// Strip constraint syntax: :param(a|b) -> use "param" as key
|
|
308
|
+
result = result.replace(
|
|
309
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
|
|
310
|
+
(_, key) => {
|
|
311
|
+
const value = params[key];
|
|
312
|
+
if (value === undefined) {
|
|
313
|
+
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
314
|
+
}
|
|
315
|
+
return encodeURIComponent(value);
|
|
316
|
+
},
|
|
317
|
+
);
|
|
314
318
|
}
|
|
315
319
|
|
|
316
320
|
// Append search params as query string
|
|
@@ -109,6 +109,8 @@ function RootErrorFallback({
|
|
|
109
109
|
error,
|
|
110
110
|
reset,
|
|
111
111
|
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
112
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
113
|
+
|
|
112
114
|
return (
|
|
113
115
|
<div
|
|
114
116
|
style={{
|
|
@@ -135,38 +137,40 @@ function RootErrorFallback({
|
|
|
135
137
|
>
|
|
136
138
|
An unexpected error occurred while processing your request.
|
|
137
139
|
</p>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
background: "#fef2f2",
|
|
141
|
-
border: "1px solid #fecaca",
|
|
142
|
-
borderRadius: "0.5rem",
|
|
143
|
-
padding: "1rem",
|
|
144
|
-
marginBottom: "1rem",
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
<p
|
|
140
|
+
{isDev && (
|
|
141
|
+
<div
|
|
148
142
|
style={{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
background: "#fef2f2",
|
|
144
|
+
border: "1px solid #fecaca",
|
|
145
|
+
borderRadius: "0.5rem",
|
|
146
|
+
padding: "1rem",
|
|
147
|
+
marginBottom: "1rem",
|
|
152
148
|
}}
|
|
153
149
|
>
|
|
154
|
-
|
|
155
|
-
</p>
|
|
156
|
-
{error.stack && (
|
|
157
|
-
<pre
|
|
150
|
+
<p
|
|
158
151
|
style={{
|
|
159
|
-
|
|
160
|
-
color: "#
|
|
161
|
-
|
|
162
|
-
whiteSpace: "pre-wrap",
|
|
163
|
-
wordBreak: "break-word",
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
color: "#991b1b",
|
|
154
|
+
marginBottom: "0.5rem",
|
|
164
155
|
}}
|
|
165
156
|
>
|
|
166
|
-
{error.
|
|
167
|
-
</
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
{error.name}: {error.message}
|
|
158
|
+
</p>
|
|
159
|
+
{error.stack && (
|
|
160
|
+
<pre
|
|
161
|
+
style={{
|
|
162
|
+
fontSize: "0.75rem",
|
|
163
|
+
color: "#6b7280",
|
|
164
|
+
overflow: "auto",
|
|
165
|
+
whiteSpace: "pre-wrap",
|
|
166
|
+
wordBreak: "break-word",
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
{error.stack}
|
|
170
|
+
</pre>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
170
174
|
<div style={{ display: "flex", gap: "1rem" }}>
|
|
171
175
|
<button
|
|
172
176
|
type="button"
|