@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1
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 +9 -9
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +914 -485
- package/package.json +55 -11
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +214 -18
- package/skills/host-router/SKILL.md +45 -20
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +149 -6
- package/skills/middleware/SKILL.md +13 -9
- package/skills/migrate-nextjs/SKILL.md +1 -1
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +5 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +58 -9
- package/skills/route/SKILL.md +13 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +53 -41
- package/skills/testing/SKILL.md +599 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/event-controller.ts +42 -66
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +6 -6
- package/src/browser/navigation-client.ts +12 -15
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +9 -19
- package/src/browser/react/NavigationProvider.tsx +29 -40
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-params.ts +3 -4
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +14 -1
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +30 -16
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +2 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +49 -6
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +6 -4
- package/src/index.ts +13 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +238 -263
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +37 -14
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +2 -2
- package/src/router/loader-resolution.ts +16 -2
- package/src/router/match-handlers.ts +62 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +32 -30
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +1 -1
- package/src/router/middleware.ts +46 -78
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +43 -1
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +19 -6
- package/src/router/segment-resolution/revalidation.ts +19 -6
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/types.ts +8 -0
- package/src/router.ts +37 -21
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +20 -65
- package/src/rsc/helpers.ts +22 -2
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/response-route-handler.ts +32 -52
- package/src/rsc/rsc-rendering.ts +27 -53
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +13 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +2 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +121 -65
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +10 -0
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +193 -0
- package/src/testing/render-route.tsx +536 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +170 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +56 -11
- package/src/types/index.ts +1 -0
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +70 -48
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +19 -25
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +3 -7
- package/src/vite/plugins/client-ref-hashing.ts +12 -1
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
- package/src/vite/plugins/expose-action-id.ts +2 -2
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-internal-ids.ts +47 -67
- package/src/vite/plugins/performance-tracks.ts +12 -16
- package/src/vite/plugins/use-cache-transform.ts +13 -11
- package/src/vite/plugins/version-injector.ts +2 -12
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +67 -15
- package/src/vite/router-discovery.ts +208 -63
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -20,7 +20,13 @@ import {
|
|
|
20
20
|
expandPrerenderRoutes,
|
|
21
21
|
renderStaticHandlers,
|
|
22
22
|
} from "./prerender-collection.js";
|
|
23
|
+
import {
|
|
24
|
+
resolveHostRouterHandlers,
|
|
25
|
+
DiscoveryError,
|
|
26
|
+
type CaughtDiscoveryError,
|
|
27
|
+
} from "./discovery-errors.js";
|
|
23
28
|
import { createRangoDebugger, timed, NS } from "../debug.js";
|
|
29
|
+
import { computeProductionHash } from "../plugins/client-ref-hashing.js";
|
|
24
30
|
|
|
25
31
|
const debug = createRangoDebugger(NS.discovery);
|
|
26
32
|
|
|
@@ -56,32 +62,25 @@ export async function discoverRouters(
|
|
|
56
62
|
if (!registry || registry.size === 0) {
|
|
57
63
|
// No RSC routers found directly. Check for host routers with lazy handlers
|
|
58
64
|
// that need to be resolved to trigger sub-app createRouter() calls.
|
|
65
|
+
//
|
|
66
|
+
// Handler failures are collected rather than swallowed: when the registry
|
|
67
|
+
// is still empty afterwards, these errors (typically a sub-app whose router
|
|
68
|
+
// module failed to import) are the most likely cause and are surfaced in
|
|
69
|
+
// the terminal "No routers found" error below.
|
|
70
|
+
const discoveryErrors: CaughtDiscoveryError[] = [];
|
|
59
71
|
try {
|
|
60
72
|
const hostRegistry: Map<string, any> | undefined =
|
|
61
73
|
serverMod.HostRouterRegistry;
|
|
62
74
|
|
|
63
75
|
if (hostRegistry && hostRegistry.size > 0) {
|
|
64
76
|
console.log(
|
|
65
|
-
`[
|
|
77
|
+
`[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
|
|
66
78
|
);
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await route.handler();
|
|
73
|
-
} catch {
|
|
74
|
-
// Lazy handler may fail in temp server context, that's OK
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (entry.fallback && typeof entry.fallback.handler === "function") {
|
|
79
|
-
try {
|
|
80
|
-
await entry.fallback.handler();
|
|
81
|
-
} catch {
|
|
82
|
-
// Fallback handler may fail in temp server context
|
|
83
|
-
}
|
|
84
|
-
}
|
|
80
|
+
const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
|
|
81
|
+
discoveryErrors.push(...handlerErrors);
|
|
82
|
+
for (const { context, error } of handlerErrors) {
|
|
83
|
+
debug?.("caught error while resolving %s: %O", context, error);
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
// Re-read RouterRegistry - sub-app createRouter() calls should have populated it
|
|
@@ -96,15 +95,15 @@ export async function discoverRouters(
|
|
|
96
95
|
registry = freshRegistry;
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
|
-
} catch {
|
|
100
|
-
// Host-router discovery is best-effort;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Host-router discovery is best-effort; record the failure so it can be
|
|
100
|
+
// surfaced if no routers are found.
|
|
101
|
+
discoveryErrors.push({ context: "host-router discovery", error });
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// If still no routers after host router resolution, fail
|
|
104
105
|
if (!registry || registry.size === 0) {
|
|
105
|
-
throw new
|
|
106
|
-
`[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`,
|
|
107
|
-
);
|
|
106
|
+
throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
|
|
@@ -145,6 +144,28 @@ export async function discoverRouters(
|
|
|
145
144
|
// Collect all manifests for trie building (avoid re-running generateManifest)
|
|
146
145
|
const allManifests: Array<{ id: string; manifest: any }> = [];
|
|
147
146
|
|
|
147
|
+
// Built-in clientChunks context (present only when the built-in strategy is
|
|
148
|
+
// active). Collect the production hashes of "use client" error/notFound
|
|
149
|
+
// fallback modules so the strategy can route them into app-fallback.
|
|
150
|
+
const clientChunkCtx = state.opts?.clientChunkCtx;
|
|
151
|
+
const collectClientFallbackRef = clientChunkCtx
|
|
152
|
+
? (refKey: string) =>
|
|
153
|
+
clientChunkCtx.fallbackRefs.add(
|
|
154
|
+
computeProductionHash(state.projectRoot, refKey),
|
|
155
|
+
)
|
|
156
|
+
: undefined;
|
|
157
|
+
// Router-level boundary defaults (`createRouter({ defaultErrorBoundary, ... })`)
|
|
158
|
+
// are NOT in EntryData, so generateManifestFull's walk misses them. Collect any
|
|
159
|
+
// "use client" default boundary directly off the router instance. The value is
|
|
160
|
+
// commonly a handler function wrapping the client boundary in server providers,
|
|
161
|
+
// so collectFallbackClientRefs invokes + walks the tree. Routed through buildMod
|
|
162
|
+
// so it runs in the same RSC runner realm the boundary value came from.
|
|
163
|
+
const collectFromBoundaryNode = (node: unknown): void => {
|
|
164
|
+
if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
|
|
165
|
+
buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
148
169
|
const manifestGenStart = debug ? performance.now() : 0;
|
|
149
170
|
for (const [id, router] of registry) {
|
|
150
171
|
if (!router.urlpatterns || !generateManifestFull) {
|
|
@@ -154,10 +175,23 @@ export async function discoverRouters(
|
|
|
154
175
|
const manifest = generateManifestFull(
|
|
155
176
|
router.urlpatterns,
|
|
156
177
|
routerMountIndex,
|
|
157
|
-
|
|
178
|
+
{
|
|
179
|
+
...(router.__basename ? { urlPrefix: router.__basename } : {}),
|
|
180
|
+
...(collectClientFallbackRef ? { collectClientFallbackRef } : {}),
|
|
181
|
+
},
|
|
158
182
|
);
|
|
159
183
|
routerMountIndex++;
|
|
160
184
|
allManifests.push({ id, manifest });
|
|
185
|
+
|
|
186
|
+
// Router-level "use client" boundary defaults -> app-fallback (the
|
|
187
|
+
// route-tree errorBoundary()/notFoundBoundary() helpers are already
|
|
188
|
+
// collected inside generateManifestFull via collectClientFallbackRef).
|
|
189
|
+
if (collectClientFallbackRef) {
|
|
190
|
+
collectFromBoundaryNode(router.__defaultErrorBoundary);
|
|
191
|
+
collectFromBoundaryNode(router.__defaultNotFoundBoundary);
|
|
192
|
+
collectFromBoundaryNode(router.__notFound);
|
|
193
|
+
}
|
|
194
|
+
|
|
161
195
|
const routeCount = Object.keys(manifest.routeManifest).length;
|
|
162
196
|
const staticRoutes = Object.values(manifest.routeManifest).filter(
|
|
163
197
|
(p: any) => !p.includes(":") && !p.includes("*"),
|
|
@@ -226,7 +260,7 @@ export async function discoverRouters(
|
|
|
226
260
|
newPerRouterPrecomputedMap.set(id, routerPrecomputed);
|
|
227
261
|
|
|
228
262
|
console.log(
|
|
229
|
-
`[
|
|
263
|
+
`[rango] Router "${id}" -> ${routeCount} routes ` +
|
|
230
264
|
`(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
|
|
231
265
|
);
|
|
232
266
|
}
|
|
@@ -242,7 +276,7 @@ export async function discoverRouters(
|
|
|
242
276
|
);
|
|
243
277
|
if (autoIds.length > 1) {
|
|
244
278
|
console.warn(
|
|
245
|
-
`[
|
|
279
|
+
`[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
|
|
246
280
|
`In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
|
|
247
281
|
`to ensure per-router manifest data is matched correctly at runtime. ` +
|
|
248
282
|
`Example: createRouter({ id: "site", ... })`,
|
|
@@ -294,18 +328,16 @@ export async function discoverRouters(
|
|
|
294
328
|
}
|
|
295
329
|
}
|
|
296
330
|
|
|
331
|
+
// buildRouteTrie reads these via ?.has / ?.[] — empty is observationally
|
|
332
|
+
// identical to undefined, so no empty->undefined coercion is needed.
|
|
297
333
|
newMergedRouteTrie = buildRouteTrie(
|
|
298
334
|
newMergedRouteManifest,
|
|
299
335
|
mergedRouteAncestry,
|
|
300
336
|
routeToStaticPrefix,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
passthroughRouteNames.size > 0 ? passthroughRouteNames : undefined,
|
|
306
|
-
Object.keys(mergedResponseTypeRoutes).length > 0
|
|
307
|
-
? mergedResponseTypeRoutes
|
|
308
|
-
: undefined,
|
|
337
|
+
mergedRouteTrailingSlash,
|
|
338
|
+
prerenderRouteNames,
|
|
339
|
+
passthroughRouteNames,
|
|
340
|
+
mergedResponseTypeRoutes,
|
|
309
341
|
);
|
|
310
342
|
|
|
311
343
|
// Build per-router tries for multi-router isolation.
|
|
@@ -332,20 +364,10 @@ export async function discoverRouters(
|
|
|
332
364
|
manifest.routeManifest,
|
|
333
365
|
manifest._routeAncestry,
|
|
334
366
|
perRouterStaticPrefix,
|
|
335
|
-
manifest.routeTrailingSlash
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
perRouterPrerenderNames && perRouterPrerenderNames.size > 0
|
|
340
|
-
? perRouterPrerenderNames
|
|
341
|
-
: undefined,
|
|
342
|
-
perRouterPassthroughNames && perRouterPassthroughNames.size > 0
|
|
343
|
-
? perRouterPassthroughNames
|
|
344
|
-
: undefined,
|
|
345
|
-
manifest.responseTypeRoutes &&
|
|
346
|
-
Object.keys(manifest.responseTypeRoutes).length > 0
|
|
347
|
-
? manifest.responseTypeRoutes
|
|
348
|
-
: undefined,
|
|
367
|
+
manifest.routeTrailingSlash,
|
|
368
|
+
perRouterPrerenderNames,
|
|
369
|
+
perRouterPassthroughNames,
|
|
370
|
+
manifest.responseTypeRoutes,
|
|
349
371
|
);
|
|
350
372
|
newPerRouterTrieMap.set(id, perRouterTrie);
|
|
351
373
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router discovery error aggregation.
|
|
3
|
+
*
|
|
4
|
+
* During host-router discovery the lazy mounts registered by a host router are
|
|
5
|
+
* invoked to trigger each sub-app's createRouter() registration. Some mount
|
|
6
|
+
* failures are expected in the temporary discovery server context (a sub-app may
|
|
7
|
+
* reference runtime-only bindings), so each is invoked defensively and its error
|
|
8
|
+
* is collected rather than thrown.
|
|
9
|
+
*
|
|
10
|
+
* Previously these errors were discarded with an empty `catch {}`. When a real
|
|
11
|
+
* failure - typically a sub-app whose router module fails to import - left the
|
|
12
|
+
* registry empty, discovery reported the misleading "No routers found" message
|
|
13
|
+
* with no trace of the underlying cause. The collected errors are now surfaced
|
|
14
|
+
* via the `DiscoveryError` thrown at the end of discovery (issue #499).
|
|
15
|
+
*
|
|
16
|
+
* Which entries to invoke is taken from the consumer's declared intent, not
|
|
17
|
+
* inferred from the function's shape. A host route is registered either with
|
|
18
|
+
* `.map((request) => Response)` (an inline request handler, `kind: "handler"`)
|
|
19
|
+
* or `.lazy(() => import("./sub-app"))` (a lazy mount, `kind: "lazy"`). Only
|
|
20
|
+
* `kind === "lazy"` entries are invoked here; inline handlers are never invoked
|
|
21
|
+
* during discovery (they need a Request and register no routers). Because a lazy
|
|
22
|
+
* entry is known to be a module loader, ANY failure it produces - a synchronous
|
|
23
|
+
* throw or a rejected promise - is a genuine discovery failure and is collected.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** An error caught (and previously swallowed) while resolving host routers. */
|
|
27
|
+
export interface CaughtDiscoveryError {
|
|
28
|
+
/** Human-readable description of where the error was caught. */
|
|
29
|
+
context: string;
|
|
30
|
+
/** The caught value (an Error or otherwise). */
|
|
31
|
+
error: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Minimal shape of a host registry entry needed for mount resolution.
|
|
36
|
+
* Mirrors the runtime HostRouterRegistry value without coupling to its type.
|
|
37
|
+
*/
|
|
38
|
+
interface HostRegistryRoute {
|
|
39
|
+
handler?: unknown;
|
|
40
|
+
kind?: string;
|
|
41
|
+
}
|
|
42
|
+
interface HostRegistryEntry {
|
|
43
|
+
routes: HostRegistryRoute[];
|
|
44
|
+
fallback?: HostRegistryRoute | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Indent every non-empty line of `text` by `pad`. */
|
|
48
|
+
function indent(text: string, pad: string): string {
|
|
49
|
+
return text
|
|
50
|
+
.split("\n")
|
|
51
|
+
.map((line) => (line.length > 0 ? pad + line : line))
|
|
52
|
+
.join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Invoke a single lazy mount to trigger its sub-app import (and createRouter()
|
|
57
|
+
* registration), collecting any failure under `context`. The entry is known to
|
|
58
|
+
* be a loader (`kind === "lazy"`), so both a synchronous throw and a rejected
|
|
59
|
+
* promise are genuine failures - no shape heuristics are needed.
|
|
60
|
+
*/
|
|
61
|
+
async function invokeLazyMount(
|
|
62
|
+
loader: () => unknown,
|
|
63
|
+
context: string,
|
|
64
|
+
errors: CaughtDiscoveryError[],
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
try {
|
|
67
|
+
await loader();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
errors.push({ context, error });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Whether a registry route is a `.lazy()` mount with an invokable loader. */
|
|
74
|
+
function isLazyMount(
|
|
75
|
+
route: HostRegistryRoute | null | undefined,
|
|
76
|
+
): route is { handler: () => unknown; kind: "lazy" } {
|
|
77
|
+
return (
|
|
78
|
+
!!route && route.kind === "lazy" && typeof route.handler === "function"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Invoke every lazy mount in the host registry to trigger sub-app
|
|
84
|
+
* createRouter() registration, collecting (not throwing) any failures.
|
|
85
|
+
*
|
|
86
|
+
* Only `.lazy()` entries are invoked; `.map()` inline request handlers are
|
|
87
|
+
* skipped (they need a Request and register no routers). Failures are returned
|
|
88
|
+
* rather than thrown because some mounts legitimately fail in the temporary
|
|
89
|
+
* discovery server context; the caller decides whether the failures matter,
|
|
90
|
+
* which is only when discovery finds no routers at all.
|
|
91
|
+
*/
|
|
92
|
+
export async function resolveHostRouterHandlers(
|
|
93
|
+
hostRegistry: Map<string, HostRegistryEntry>,
|
|
94
|
+
): Promise<CaughtDiscoveryError[]> {
|
|
95
|
+
const errors: CaughtDiscoveryError[] = [];
|
|
96
|
+
|
|
97
|
+
for (const [hostId, entry] of hostRegistry) {
|
|
98
|
+
for (const route of entry.routes) {
|
|
99
|
+
if (isLazyMount(route)) {
|
|
100
|
+
await invokeLazyMount(
|
|
101
|
+
route.handler,
|
|
102
|
+
`host "${hostId}" route handler`,
|
|
103
|
+
errors,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (isLazyMount(entry.fallback)) {
|
|
108
|
+
await invokeLazyMount(
|
|
109
|
+
entry.fallback.handler,
|
|
110
|
+
`host "${hostId}" fallback handler`,
|
|
111
|
+
errors,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return errors;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Build the terminal "No routers found" message, appending any errors caught
|
|
121
|
+
* during host-router discovery so the real cause is visible.
|
|
122
|
+
*
|
|
123
|
+
* The aggregated errors are inlined into the message (in addition to being
|
|
124
|
+
* attached via `cause` on `DiscoveryError`) so they survive every caller: the
|
|
125
|
+
* dev/HMR paths log `err.message`, and the build path re-throws using
|
|
126
|
+
* `err.stack`, which begins with the message. None of those callers traverse
|
|
127
|
+
* `cause`, so the message must carry the detail. Each error includes its stack
|
|
128
|
+
* when available.
|
|
129
|
+
*/
|
|
130
|
+
export function formatNoRoutersError(
|
|
131
|
+
entryPath: string | undefined,
|
|
132
|
+
errors: CaughtDiscoveryError[],
|
|
133
|
+
): string {
|
|
134
|
+
const base = `[rango] No routers found in registry after importing ${entryPath}`;
|
|
135
|
+
if (errors.length === 0) {
|
|
136
|
+
return base;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const formatted = errors
|
|
140
|
+
.map(({ context, error }) => {
|
|
141
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
142
|
+
const detail = err.stack ?? err.message;
|
|
143
|
+
return ` - while resolving ${context}:\n${indent(detail, " ")}`;
|
|
144
|
+
})
|
|
145
|
+
.join("\n");
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
`${base}\n\n` +
|
|
149
|
+
`${errors.length} error(s) were caught during host-router discovery and ` +
|
|
150
|
+
`likely explain why no routers were registered:\n${formatted}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Reduce the caught errors to an `ErrorOptions.cause`: a single failure becomes
|
|
156
|
+
* the direct cause; multiple failures are wrapped in an `AggregateError` so
|
|
157
|
+
* each underlying error remains reachable. No errors -> no cause.
|
|
158
|
+
*/
|
|
159
|
+
function toCause(errors: CaughtDiscoveryError[]): unknown {
|
|
160
|
+
if (errors.length === 0) return undefined;
|
|
161
|
+
if (errors.length === 1) return errors[0].error;
|
|
162
|
+
return new AggregateError(
|
|
163
|
+
errors.map((e) => e.error),
|
|
164
|
+
"Multiple host-router handlers failed during discovery",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Thrown when router discovery completes without finding any routers.
|
|
170
|
+
*
|
|
171
|
+
* Carries the entry path and the individual failures caught while resolving
|
|
172
|
+
* host-router lazy handlers. The formatted detail is embedded in `message` (for
|
|
173
|
+
* callers that log `err.message`/`err.stack`) and the underlying error(s) are
|
|
174
|
+
* also attached via `cause` (a single failure directly, multiple wrapped in an
|
|
175
|
+
* `AggregateError`) for cause-aware tooling such as the Vite error overlay.
|
|
176
|
+
*/
|
|
177
|
+
export class DiscoveryError extends Error {
|
|
178
|
+
/** The entry file that was imported before discovery gave up. */
|
|
179
|
+
readonly entryPath: string | undefined;
|
|
180
|
+
/** Individual failures caught while resolving host-router handlers. */
|
|
181
|
+
readonly caught: CaughtDiscoveryError[];
|
|
182
|
+
|
|
183
|
+
constructor(entryPath: string | undefined, caught: CaughtDiscoveryError[]) {
|
|
184
|
+
super(formatNoRoutersError(entryPath, caught));
|
|
185
|
+
const cause = toCause(caught);
|
|
186
|
+
if (cause !== undefined) {
|
|
187
|
+
this.cause = cause;
|
|
188
|
+
}
|
|
189
|
+
this.name = "DiscoveryError";
|
|
190
|
+
this.entryPath = entryPath;
|
|
191
|
+
this.caught = caught;
|
|
192
|
+
Object.setPrototypeOf(this, DiscoveryError.prototype);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -81,7 +81,7 @@ export async function expandPrerenderRoutes(
|
|
|
81
81
|
? setInterval(() => {
|
|
82
82
|
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
83
83
|
console.log(
|
|
84
|
-
`[
|
|
84
|
+
`[rango] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
85
85
|
);
|
|
86
86
|
}, 5000)
|
|
87
87
|
: undefined;
|
|
@@ -122,7 +122,7 @@ export async function expandPrerenderRoutes(
|
|
|
122
122
|
get env() {
|
|
123
123
|
if (buildEnv !== undefined) return buildEnv;
|
|
124
124
|
throw new Error(
|
|
125
|
-
"[
|
|
125
|
+
"[rango] ctx.env is not available during build-time getParams(). " +
|
|
126
126
|
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
127
127
|
);
|
|
128
128
|
},
|
|
@@ -170,7 +170,7 @@ export async function expandPrerenderRoutes(
|
|
|
170
170
|
// Skip in getParams() skips the entire route
|
|
171
171
|
if (err.name === "Skip") {
|
|
172
172
|
console.log(
|
|
173
|
-
`[
|
|
173
|
+
`[rango] SKIP route "${routeName}" - ${err.message}`,
|
|
174
174
|
);
|
|
175
175
|
notifyOnError(
|
|
176
176
|
registry,
|
|
@@ -184,14 +184,14 @@ export async function expandPrerenderRoutes(
|
|
|
184
184
|
}
|
|
185
185
|
// Regular error: fail the build
|
|
186
186
|
console.error(
|
|
187
|
-
`[
|
|
187
|
+
`[rango] Failed to get params for prerender route "${routeName}": ${err.message}`,
|
|
188
188
|
);
|
|
189
189
|
notifyOnError(registry, err, "prerender", routeName);
|
|
190
190
|
throw err;
|
|
191
191
|
}
|
|
192
192
|
} else {
|
|
193
193
|
console.warn(
|
|
194
|
-
`[
|
|
194
|
+
`[rango] Dynamic prerender route "${routeName}" has no getParams(), skipping`,
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -202,7 +202,7 @@ export async function expandPrerenderRoutes(
|
|
|
202
202
|
clearInterval(progressInterval);
|
|
203
203
|
const elapsed = ((performance.now() - paramsStart) / 1000).toFixed(1);
|
|
204
204
|
console.log(
|
|
205
|
-
`[
|
|
205
|
+
`[rango] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`,
|
|
206
206
|
);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
@@ -220,7 +220,7 @@ export async function expandPrerenderRoutes(
|
|
|
220
220
|
const concurrencyNote =
|
|
221
221
|
maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
|
|
222
222
|
console.log(
|
|
223
|
-
`[
|
|
223
|
+
`[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`,
|
|
224
224
|
);
|
|
225
225
|
debug?.(
|
|
226
226
|
"prerender loop: %d entries, max concurrency %d",
|
|
@@ -261,7 +261,7 @@ export async function expandPrerenderRoutes(
|
|
|
261
261
|
if (result.passthrough) {
|
|
262
262
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
263
263
|
console.log(
|
|
264
|
-
`[
|
|
264
|
+
`[rango] PASS ${entry.urlPath.padEnd(40)} (${elapsed}ms) - live fallback`,
|
|
265
265
|
);
|
|
266
266
|
doneCount++;
|
|
267
267
|
break;
|
|
@@ -295,7 +295,7 @@ export async function expandPrerenderRoutes(
|
|
|
295
295
|
}
|
|
296
296
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
297
297
|
console.log(
|
|
298
|
-
`[
|
|
298
|
+
`[rango] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`,
|
|
299
299
|
);
|
|
300
300
|
doneCount++;
|
|
301
301
|
break;
|
|
@@ -303,7 +303,7 @@ export async function expandPrerenderRoutes(
|
|
|
303
303
|
if (err.name === "Skip") {
|
|
304
304
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
305
305
|
console.log(
|
|
306
|
-
`[
|
|
306
|
+
`[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
307
307
|
);
|
|
308
308
|
skipCount++;
|
|
309
309
|
notifyOnError(
|
|
@@ -319,7 +319,7 @@ export async function expandPrerenderRoutes(
|
|
|
319
319
|
// Regular error: log, notify, and fail the build
|
|
320
320
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
321
321
|
console.error(
|
|
322
|
-
`[
|
|
322
|
+
`[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
323
323
|
);
|
|
324
324
|
notifyOnError(
|
|
325
325
|
registry,
|
|
@@ -342,7 +342,7 @@ export async function expandPrerenderRoutes(
|
|
|
342
342
|
const parts = [`${doneCount} done`];
|
|
343
343
|
if (skipCount > 0) parts.push(`${skipCount} skipped`);
|
|
344
344
|
console.log(
|
|
345
|
-
`[
|
|
345
|
+
`[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`,
|
|
346
346
|
);
|
|
347
347
|
debug?.(
|
|
348
348
|
"expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
|
|
@@ -387,9 +387,7 @@ export async function renderStaticHandlers(
|
|
|
387
387
|
totalStaticCount += exportNames.length;
|
|
388
388
|
}
|
|
389
389
|
const startStatic = performance.now();
|
|
390
|
-
console.log(
|
|
391
|
-
`[rsc-router] Rendering ${totalStaticCount} static handler(s)...`,
|
|
392
|
-
);
|
|
390
|
+
console.log(`[rango] Rendering ${totalStaticCount} static handler(s)...`);
|
|
393
391
|
|
|
394
392
|
for (const [moduleId, exportNames] of state.resolvedStaticModules) {
|
|
395
393
|
let mod: any;
|
|
@@ -397,7 +395,7 @@ export async function renderStaticHandlers(
|
|
|
397
395
|
mod = await rscEnv!.runner.import(moduleId);
|
|
398
396
|
} catch (err: any) {
|
|
399
397
|
console.error(
|
|
400
|
-
`[
|
|
398
|
+
`[rango] Failed to import static module ${moduleId}: ${err.message}`,
|
|
401
399
|
);
|
|
402
400
|
notifyOnError(registry, err, "static");
|
|
403
401
|
throw err;
|
|
@@ -432,9 +430,7 @@ export async function renderStaticHandlers(
|
|
|
432
430
|
exportValue,
|
|
433
431
|
);
|
|
434
432
|
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
435
|
-
console.log(
|
|
436
|
-
`[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`,
|
|
437
|
-
);
|
|
433
|
+
console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
|
|
438
434
|
staticDone++;
|
|
439
435
|
handled = true;
|
|
440
436
|
break;
|
|
@@ -443,7 +439,7 @@ export async function renderStaticHandlers(
|
|
|
443
439
|
if (err.name === "Skip") {
|
|
444
440
|
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
445
441
|
console.log(
|
|
446
|
-
`[
|
|
442
|
+
`[rango] SKIP ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
447
443
|
);
|
|
448
444
|
staticSkip++;
|
|
449
445
|
notifyOnError(registry, err, "static", undefined, undefined, true);
|
|
@@ -453,16 +449,14 @@ export async function renderStaticHandlers(
|
|
|
453
449
|
// Regular error: log, notify, and fail the build
|
|
454
450
|
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
455
451
|
console.error(
|
|
456
|
-
`[
|
|
452
|
+
`[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
457
453
|
);
|
|
458
454
|
notifyOnError(registry, err, "static");
|
|
459
455
|
throw err;
|
|
460
456
|
}
|
|
461
457
|
}
|
|
462
458
|
if (!handled) {
|
|
463
|
-
console.warn(
|
|
464
|
-
`[rsc-router] No router could render static handler "${name}"`,
|
|
465
|
-
);
|
|
459
|
+
console.warn(`[rango] No router could render static handler "${name}"`);
|
|
466
460
|
}
|
|
467
461
|
}
|
|
468
462
|
}
|
|
@@ -474,7 +468,7 @@ export async function renderStaticHandlers(
|
|
|
474
468
|
const staticParts = [`${staticDone} done`];
|
|
475
469
|
if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
|
|
476
470
|
console.log(
|
|
477
|
-
`[
|
|
471
|
+
`[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`,
|
|
478
472
|
);
|
|
479
473
|
debug?.(
|
|
480
474
|
"renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
|