@rangojs/router 0.0.0-experimental.126 → 0.0.0-experimental.128
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/dist/bin/rango.js +5 -1
- package/dist/vite/index.js +55 -40
- package/package.json +23 -19
- package/skills/observability/SKILL.md +39 -4
- package/skills/prerender/SKILL.md +30 -11
- package/skills/router-setup/SKILL.md +23 -3
- package/src/build/route-types/codegen.ts +12 -1
- package/src/cache/cache-scope.ts +20 -0
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +112 -0
- package/src/index.rsc.ts +19 -2
- package/src/index.ts +16 -1
- package/src/route-definition/dsl-helpers.ts +19 -0
- package/src/router/instrument.ts +440 -0
- package/src/router/loader-resolution.ts +15 -10
- package/src/router/match-middleware/cache-lookup.ts +9 -14
- package/src/router/match-middleware/cache-store.ts +12 -0
- package/src/router/middleware.ts +23 -2
- package/src/router/prerender-match.ts +5 -2
- package/src/router/router-context.ts +2 -1
- package/src/router/router-interfaces.ts +8 -0
- package/src/router/router-options.ts +58 -4
- package/src/router/segment-resolution/fresh.ts +15 -18
- package/src/router/segment-resolution/helpers.ts +6 -0
- package/src/router/segment-resolution/loader-cache.ts +5 -0
- package/src/router/segment-resolution/revalidation.ts +9 -18
- package/src/router/segment-wrappers.ts +3 -2
- package/src/router/telemetry-otel.ts +161 -179
- package/src/router/tracing.ts +203 -0
- package/src/router.ts +9 -0
- package/src/rsc/handler.ts +140 -134
- package/src/rsc/loader-fetch.ts +7 -1
- package/src/rsc/progressive-enhancement.ts +9 -2
- package/src/rsc/rsc-rendering.ts +38 -14
- package/src/rsc/server-action.ts +28 -7
- package/src/segment-system.tsx +4 -1
- package/src/server/request-context.ts +23 -5
- package/src/vite/discovery/prerender-collection.ts +26 -37
- package/src/vite/discovery/state.ts +6 -0
- package/src/vite/plugin-types.ts +25 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +10 -0
- package/src/vite/rango.ts +1 -0
- package/src/vite/router-discovery.ts +9 -3
- package/src/vite/utils/prerender-utils.ts +36 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
runWithConcurrency,
|
|
14
14
|
groupByConcurrency,
|
|
15
15
|
notifyOnError,
|
|
16
|
+
resolvePrerenderError,
|
|
16
17
|
stageBuildAssetModule,
|
|
17
18
|
} from "../utils/prerender-utils.js";
|
|
18
19
|
import type { DiscoveryState } from "./state.js";
|
|
@@ -231,6 +232,10 @@ export async function expandPrerenderRoutes(
|
|
|
231
232
|
const manifestEntries: Record<string, string> = {};
|
|
232
233
|
let doneCount = 0;
|
|
233
234
|
let skipCount = 0;
|
|
235
|
+
// #587: a render error reaches here (matchForPrerender now re-throws it rather
|
|
236
|
+
// than baking the error boundary). Default "fail" fails the build; "warn" logs
|
|
237
|
+
// and skips baking the URL.
|
|
238
|
+
const prerenderOnError = state.opts?.prerenderOnError ?? "fail";
|
|
234
239
|
const startTotal = performance.now();
|
|
235
240
|
|
|
236
241
|
// Group entries by concurrency for batched rendering.
|
|
@@ -297,35 +302,22 @@ export async function expandPrerenderRoutes(
|
|
|
297
302
|
doneCount++;
|
|
298
303
|
break;
|
|
299
304
|
} catch (err: any) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
`[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
304
|
-
);
|
|
305
|
-
skipCount++;
|
|
306
|
-
notifyOnError(
|
|
307
|
-
registry,
|
|
308
|
-
err,
|
|
309
|
-
"prerender",
|
|
310
|
-
entry.routeName,
|
|
311
|
-
entry.urlPath,
|
|
312
|
-
true,
|
|
313
|
-
);
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
// Regular error: log, notify, and fail the build
|
|
305
|
+
// Skip, or a render error under prerender.onError "warn": skip the
|
|
306
|
+
// URL (never bake the error page, #587). A render error under the
|
|
307
|
+
// default "fail" re-throws below to fail the build.
|
|
317
308
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
318
|
-
|
|
319
|
-
`[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
320
|
-
);
|
|
321
|
-
notifyOnError(
|
|
309
|
+
resolvePrerenderError(
|
|
322
310
|
registry,
|
|
323
311
|
err,
|
|
312
|
+
prerenderOnError,
|
|
313
|
+
entry.urlPath.padEnd(40),
|
|
314
|
+
elapsed,
|
|
324
315
|
"prerender",
|
|
325
316
|
entry.routeName,
|
|
326
317
|
entry.urlPath,
|
|
327
318
|
);
|
|
328
|
-
|
|
319
|
+
skipCount++;
|
|
320
|
+
break;
|
|
329
321
|
}
|
|
330
322
|
}
|
|
331
323
|
},
|
|
@@ -378,6 +370,7 @@ export async function renderStaticHandlers(
|
|
|
378
370
|
let staticDone = 0;
|
|
379
371
|
let staticSkip = 0;
|
|
380
372
|
let totalStaticCount = 0;
|
|
373
|
+
const prerenderOnError = state.opts?.prerenderOnError ?? "fail";
|
|
381
374
|
|
|
382
375
|
// Count handlers for the log header
|
|
383
376
|
for (const [, exportNames] of state.resolvedStaticModules) {
|
|
@@ -434,23 +427,19 @@ export async function renderStaticHandlers(
|
|
|
434
427
|
break;
|
|
435
428
|
}
|
|
436
429
|
} catch (err: any) {
|
|
437
|
-
|
|
438
|
-
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
439
|
-
console.log(
|
|
440
|
-
`[rango] SKIP ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`,
|
|
441
|
-
);
|
|
442
|
-
staticSkip++;
|
|
443
|
-
notifyOnError(registry, err, "static", undefined, undefined, true);
|
|
444
|
-
handled = true;
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
// Regular error: log, notify, and fail the build
|
|
430
|
+
// Same Skip/warn/fail policy as the prerender loop (shared helper).
|
|
448
431
|
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
449
|
-
|
|
450
|
-
|
|
432
|
+
resolvePrerenderError(
|
|
433
|
+
registry,
|
|
434
|
+
err,
|
|
435
|
+
prerenderOnError,
|
|
436
|
+
name.padEnd(40),
|
|
437
|
+
elapsed,
|
|
438
|
+
"static",
|
|
451
439
|
);
|
|
452
|
-
|
|
453
|
-
|
|
440
|
+
staticSkip++;
|
|
441
|
+
handled = true;
|
|
442
|
+
break;
|
|
454
443
|
}
|
|
455
444
|
}
|
|
456
445
|
if (!handled) {
|
|
@@ -25,6 +25,12 @@ export interface PluginOptions {
|
|
|
25
25
|
* Compiled into `DiscoveryState.scanFilter` once `projectRoot` is known.
|
|
26
26
|
*/
|
|
27
27
|
discovery?: { include?: string[]; exclude?: string[] };
|
|
28
|
+
/**
|
|
29
|
+
* What to do when a Prerender/Static render throws at build time, from
|
|
30
|
+
* rango({ prerender: { onError } }). "fail" (default) fails the build; "warn"
|
|
31
|
+
* logs and skips baking the URL. See {@link import("../plugin-types.js").RangoOptions}.
|
|
32
|
+
*/
|
|
33
|
+
prerenderOnError?: "fail" | "warn";
|
|
28
34
|
/**
|
|
29
35
|
* Shared context the built-in clientChunks strategy reads. Discovery populates
|
|
30
36
|
* it (registered fallback hashes + single-router name) before the client build
|
package/src/vite/plugin-types.ts
CHANGED
|
@@ -139,6 +139,31 @@ interface RangoBaseOptions {
|
|
|
139
139
|
include?: string[];
|
|
140
140
|
exclude?: string[];
|
|
141
141
|
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* What to do when a `Prerender` route's or `Static` handler's render throws at
|
|
145
|
+
* build time. Otherwise the route error boundary catches it and the rendered
|
|
146
|
+
* error page is baked as the artifact, then served as an HTTP 200 — a silent,
|
|
147
|
+
* user-visible breakage (issue #587). Independent of this setting, a render may
|
|
148
|
+
* `throw new Skip()` (from `@rangojs/router`) to skip a single URL/handler.
|
|
149
|
+
*
|
|
150
|
+
* - `"fail"` (**default**): fail the build, naming the URL/handler and the
|
|
151
|
+
* original render error.
|
|
152
|
+
* - `"warn"`: log a warning and skip baking the artifact — it is never served as a
|
|
153
|
+
* baked 200 error page. `"warn"` is a build-unblock, NOT a runtime contract for
|
|
154
|
+
* the skipped entry: the route falls through to normal resolution, which may
|
|
155
|
+
* render live (its handler is still bundled — e.g. when nothing else baked) or
|
|
156
|
+
* 404 (once prerender handler eviction has run for other baked entries), so the
|
|
157
|
+
* outcome depends on the rest of the build, and a skipped `Static()` handler's
|
|
158
|
+
* evicted code can surface as an error. For DEFINED runtime behavior use
|
|
159
|
+
* `Passthrough()` (a live fallback) or `throw new Skip()` (an intentional skip);
|
|
160
|
+
* otherwise prefer the default `"fail"`.
|
|
161
|
+
*
|
|
162
|
+
* @default "fail"
|
|
163
|
+
*/
|
|
164
|
+
prerender?: {
|
|
165
|
+
onError?: "fail" | "warn";
|
|
166
|
+
};
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
/**
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { normalizePath, findMatchingParen } from "../expose-id-utils.js";
|
|
6
6
|
import { getImportedFnNames } from "./export-analysis.js";
|
|
7
|
+
import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
|
|
7
8
|
import { createRangoDebugger, createCounter, NS } from "../../debug.js";
|
|
8
9
|
|
|
9
10
|
const debug = createRangoDebugger(NS.transform);
|
|
@@ -28,7 +29,16 @@ export function transformRouter(
|
|
|
28
29
|
const routeNamesImport = `./${basename}.named-routes.gen.js`;
|
|
29
30
|
const routeNamesVar = `__rsc_rn`;
|
|
30
31
|
|
|
32
|
+
// Only inject at call sites in REAL code, not inside comments or string
|
|
33
|
+
// literals — e.g. a `createRouter({ ... })` snippet in a JSDoc example must
|
|
34
|
+
// not get a `$$id`/`.named-routes.gen` import injected. The sibling analysis
|
|
35
|
+
// path (export-analysis.ts) already uses this same comment/string-aware scan;
|
|
36
|
+
// the raw `pat.exec(code)` loop below would otherwise match in comments too.
|
|
37
|
+
const codeOffsets = new Set(codeMatchIndices(code, pat));
|
|
38
|
+
pat.lastIndex = 0;
|
|
39
|
+
|
|
31
40
|
while ((match = pat.exec(code)) !== null) {
|
|
41
|
+
if (!codeOffsets.has(match.index)) continue;
|
|
32
42
|
const callStart = match.index;
|
|
33
43
|
const parenPos = match.index + match[0].length - 1;
|
|
34
44
|
|
package/src/vite/rango.ts
CHANGED
|
@@ -428,6 +428,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
428
428
|
enableBuildPrerender: prerenderEnabled,
|
|
429
429
|
buildEnv: options?.buildEnv,
|
|
430
430
|
preset,
|
|
431
|
+
prerenderOnError: options?.prerender?.onError,
|
|
431
432
|
discovery: options?.discovery,
|
|
432
433
|
clientChunkCtx,
|
|
433
434
|
}),
|
|
@@ -912,9 +912,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
912
912
|
logResult(200, `match ${result.routeName}`);
|
|
913
913
|
return;
|
|
914
914
|
} catch (err: any) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
)
|
|
915
|
+
// matchForPrerender now re-throws render failures instead of baking
|
|
916
|
+
// an error page (issue #587). In dev there is no frozen artifact, so
|
|
917
|
+
// we fall through (404 -> live handler). A `throw new Skip()` is the
|
|
918
|
+
// expected "skip this URL" signal, not a failure, so it stays quiet.
|
|
919
|
+
if (err?.name !== "Skip") {
|
|
920
|
+
console.warn(
|
|
921
|
+
`[rango] Dev prerender error for ${pathname} (serving live instead): ${err.message}`,
|
|
922
|
+
);
|
|
923
|
+
}
|
|
918
924
|
}
|
|
919
925
|
}
|
|
920
926
|
|
|
@@ -137,6 +137,42 @@ export function notifyOnError(
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Resolve a thrown build-time render error into the prerender build's policy and
|
|
142
|
+
* log a per-entry line. A `Skip` (or any render error under `prerender.onError:
|
|
143
|
+
* "warn"`) logs and returns so the caller skips the entry; a render error under
|
|
144
|
+
* the default "fail" logs FAIL, notifies `onError`, and re-throws to fail the
|
|
145
|
+
* build. Shared by `expandPrerenderRoutes` (prerender) and `renderStaticHandlers`
|
|
146
|
+
* (static) so the Skip/warn/fail policy lives in one place. `label` is the padded
|
|
147
|
+
* URL / handler name for the log line; `elapsed` is the per-entry duration string.
|
|
148
|
+
*/
|
|
149
|
+
export function resolvePrerenderError(
|
|
150
|
+
registry: Map<string, any>,
|
|
151
|
+
error: any,
|
|
152
|
+
onError: "fail" | "warn",
|
|
153
|
+
label: string,
|
|
154
|
+
elapsed: string,
|
|
155
|
+
phase: "prerender" | "static",
|
|
156
|
+
routeKey?: string,
|
|
157
|
+
pathname?: string,
|
|
158
|
+
): void {
|
|
159
|
+
const isSkip = error?.name === "Skip";
|
|
160
|
+
if (isSkip || onError === "warn") {
|
|
161
|
+
if (isSkip) {
|
|
162
|
+
console.log(`[rango] SKIP ${label} (${elapsed}ms) - ${error.message}`);
|
|
163
|
+
} else {
|
|
164
|
+
console.warn(
|
|
165
|
+
`[rango] WARN ${label} (${elapsed}ms) - render error, not pre-rendered (prerender.onError: "warn"): ${error.message}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
notifyOnError(registry, error, phase, routeKey, pathname, true);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
console.error(`[rango] FAIL ${label} (${elapsed}ms) - ${error.message}`);
|
|
172
|
+
notifyOnError(registry, error, phase, routeKey, pathname);
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
|
|
140
176
|
function getStagedAssetDir(projectRoot: string): string {
|
|
141
177
|
return resolve(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
|
|
142
178
|
}
|