@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125
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 +10 -6
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +55 -48
- package/package.json +61 -21
- package/skills/caching/SKILL.md +2 -1
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +16 -2
- package/skills/intercept/SKILL.md +4 -2
- package/skills/layout/SKILL.md +11 -6
- package/skills/loader/SKILL.md +6 -2
- package/skills/middleware/SKILL.md +4 -2
- package/skills/migrate-nextjs/SKILL.md +3 -1
- package/skills/parallel/SKILL.md +9 -4
- package/skills/rango/SKILL.md +12 -0
- package/skills/route/SKILL.md +10 -2
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +118 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +14 -1
- package/src/browser/navigation-client.ts +14 -1
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +26 -51
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +1 -83
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/fetch.ts +7 -0
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -99
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +12 -4
- package/src/browser/server-action-bridge.ts +77 -15
- package/src/browser/types.ts +7 -2
- package/src/browser/validate-redirect-origin.ts +4 -5
- package/src/build/route-trie.ts +3 -0
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +27 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +94 -46
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +11 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +2 -48
- package/src/cache/profile-registry.ts +7 -3
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +1 -22
- package/src/client.tsx +14 -38
- package/src/component-utils.ts +19 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +28 -18
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +42 -3
- package/src/index.ts +31 -1
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +58 -3
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +11 -1
- package/src/route-map-builder.ts +0 -16
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -30
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +3 -2
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +1 -54
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -21
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-cookies.ts +0 -13
- package/src/router/middleware-types.ts +0 -115
- package/src/router/middleware.ts +7 -30
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +1 -33
- package/src/router/prerender-match.ts +33 -45
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +5 -58
- package/src/router/router-context.ts +0 -26
- package/src/router/router-interfaces.ts +7 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +10 -13
- package/src/router/segment-resolution/revalidation.ts +5 -42
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +63 -40
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +40 -9
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/helpers.ts +34 -0
- package/src/rsc/origin-guard.ts +0 -12
- package/src/rsc/progressive-enhancement.ts +4 -1
- package/src/rsc/rsc-rendering.ts +4 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +30 -28
- package/src/rsc/types.ts +2 -1
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/cookie-store.ts +52 -1
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +74 -77
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +119 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +127 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +186 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +98 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +311 -0
- package/src/testing/render-route.tsx +504 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -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 +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +16 -13
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +6 -7
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +3 -1
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/use-cache-transform.ts +0 -36
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +1 -108
- package/src/vite/router-discovery.ts +2 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
package/src/prerender/store.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prerender Store
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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.
|
|
2
|
+
* Prerender Store — reads pre-rendered segment data from the worker bundle.
|
|
3
|
+
* Manifest module (injected via globalThis.__loadPrerenderManifestModule)
|
|
4
|
+
* contains key-to-specifier map and loadPrerenderAsset for import() resolution.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
import type { SerializedSegmentData } from "../cache/types.js";
|
|
@@ -101,13 +97,20 @@ export function createPrerenderStore(): PrerenderStore | null {
|
|
|
101
97
|
if (!globalThis.__loadPrerenderManifestModule) return null;
|
|
102
98
|
|
|
103
99
|
const cache = new Map<string, Promise<PrerenderEntry | null>>();
|
|
104
|
-
let manifestModulePromise: Promise<PrerenderManifestModule | null
|
|
105
|
-
null;
|
|
100
|
+
let manifestModulePromise: Promise<PrerenderManifestModule> | null = null;
|
|
106
101
|
|
|
107
|
-
function loadManifestModule(): Promise<PrerenderManifestModule
|
|
102
|
+
function loadManifestModule(): Promise<PrerenderManifestModule> {
|
|
108
103
|
if (!manifestModulePromise) {
|
|
104
|
+
// Do not cache a failed manifest-module load: clear the memoized promise
|
|
105
|
+
// on rejection so the next get() retries, and let the error propagate
|
|
106
|
+
// (consistent with the per-asset load policy below) instead of caching a
|
|
107
|
+
// null for the isolate lifetime, which would silently degrade every
|
|
108
|
+
// prerendered route to a miss after one transient failure.
|
|
109
109
|
manifestModulePromise = globalThis.__loadPrerenderManifestModule!().catch(
|
|
110
|
-
() =>
|
|
110
|
+
(err) => {
|
|
111
|
+
manifestModulePromise = null;
|
|
112
|
+
throw err;
|
|
113
|
+
},
|
|
111
114
|
);
|
|
112
115
|
}
|
|
113
116
|
return manifestModulePromise;
|
|
@@ -120,7 +123,6 @@ export function createPrerenderStore(): PrerenderStore | null {
|
|
|
120
123
|
if (cached) return cached;
|
|
121
124
|
|
|
122
125
|
const promise = loadManifestModule().then((mod) => {
|
|
123
|
-
if (!mod) return null;
|
|
124
126
|
const specifier = mod.default[key];
|
|
125
127
|
if (!specifier) return null;
|
|
126
128
|
// Let asset load errors propagate — a missing/corrupted artifact
|
|
@@ -129,29 +131,20 @@ export function createPrerenderStore(): PrerenderStore | null {
|
|
|
129
131
|
// (which the handler stub would misreport as a 404).
|
|
130
132
|
return mod.loadPrerenderAsset(specifier).then((asset) => asset.default);
|
|
131
133
|
});
|
|
132
|
-
|
|
134
|
+
// Only memoize once the manifest module resolved: a manifest-load
|
|
135
|
+
// rejection must not poison the per-key cache, or the retry above is moot.
|
|
136
|
+
cache.set(
|
|
137
|
+
key,
|
|
138
|
+
promise.catch((err) => {
|
|
139
|
+
cache.delete(key);
|
|
140
|
+
throw err;
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
133
143
|
return promise;
|
|
134
144
|
},
|
|
135
145
|
};
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
/**
|
|
139
|
-
* Load the prerender manifest index for test introspection.
|
|
140
|
-
* Returns the key→specifier map or null if unavailable.
|
|
141
|
-
*/
|
|
142
|
-
export async function loadPrerenderManifestIndex(): Promise<Record<
|
|
143
|
-
string,
|
|
144
|
-
string
|
|
145
|
-
> | null> {
|
|
146
|
-
if (!globalThis.__loadPrerenderManifestModule) return null;
|
|
147
|
-
try {
|
|
148
|
-
const mod = await globalThis.__loadPrerenderManifestModule();
|
|
149
|
-
return mod.default;
|
|
150
|
-
} catch {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
148
|
/**
|
|
156
149
|
* Create a static segment store.
|
|
157
150
|
* Production only: backed by globalThis.__STATIC_MANIFEST injected at build time.
|
package/src/prerender.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type { ReverseFunction } from "./reverse.js";
|
|
|
38
38
|
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
39
39
|
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
40
40
|
import { isCachedFunction } from "./cache/taint.js";
|
|
41
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
41
42
|
|
|
42
43
|
// -- Named route resolution types -------------------------------------------
|
|
43
44
|
|
|
@@ -273,6 +274,11 @@ export interface PrerenderHandlerDefinition<
|
|
|
273
274
|
use?: () => UseItems<HandlerUseItem>;
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
// Process-stable fallback id counter (mirrors createHandle / createLoader). Only
|
|
278
|
+
// assigned in a bare unit test where the Vite plugin did not inject an id; never
|
|
279
|
+
// fires in a real build (the plugin always injects).
|
|
280
|
+
let runtimePrerenderIdCounter = 0;
|
|
281
|
+
|
|
276
282
|
// -- Overloads --------------------------------------------------------------
|
|
277
283
|
//
|
|
278
284
|
// T accepts: named route string (global or .local) OR explicit param object.
|
|
@@ -376,12 +382,27 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
376
382
|
);
|
|
377
383
|
}
|
|
378
384
|
|
|
379
|
-
|
|
385
|
+
// Throw unless under a test runner. The plugin always injects $$id for a
|
|
386
|
+
// supported `export const` Prerender on every build, so a missing id means
|
|
387
|
+
// either no plugin (a bare test — fall back below) or an UNSUPPORTED shape the
|
|
388
|
+
// plugin silently skipped (dev OR a real build — fail loud; a synthetic id
|
|
389
|
+
// would degrade to a silent prerender miss). The message is already small (no
|
|
390
|
+
// stack-parsing diagnostic), so it ships as-is. isUnderTestRunner() is
|
|
391
|
+
// runtime-safe — never a bare `process.env` access.
|
|
392
|
+
if (!id && !isUnderTestRunner()) {
|
|
380
393
|
throw new Error(
|
|
381
|
-
"[rango] Prerender: missing $$id. " +
|
|
382
|
-
"
|
|
394
|
+
"[rango] Prerender: missing $$id. Use `export const X = Prerender(...)` " +
|
|
395
|
+
"and ensure the exposeInternalIds Vite plugin is configured.",
|
|
383
396
|
);
|
|
384
397
|
}
|
|
398
|
+
// Under vitest with no plugin id: assign a process-stable runtime id so a
|
|
399
|
+
// whole-app router with Prerender routes constructs in a bare test (for
|
|
400
|
+
// dispatch / assertGeneratedRoutesMatch). Never reached in a real build (the
|
|
401
|
+
// throw above fires there); prerender storage/lookup keys on routeName +
|
|
402
|
+
// paramHash, never $$id (mirrors createHandle / createLoader).
|
|
403
|
+
if (!id) {
|
|
404
|
+
id = `__rango_runtime_prerender_${runtimePrerenderIdCounter++}`;
|
|
405
|
+
}
|
|
385
406
|
|
|
386
407
|
return {
|
|
387
408
|
__brand: "prerenderHandler" as const,
|
|
@@ -421,6 +442,40 @@ export function isPrerenderPassthrough(
|
|
|
421
442
|
);
|
|
422
443
|
}
|
|
423
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Detect whether any resolved segment carries the passthrough sentinel.
|
|
447
|
+
*
|
|
448
|
+
* A build handler signals passthrough by returning `ctx.passthrough()` (the
|
|
449
|
+
* PRERENDER_PASSTHROUGH sentinel), which lands on the segment's `component`.
|
|
450
|
+
* But when the route declares `loading()`, the handler result is deferred
|
|
451
|
+
* upstream (segment-resolution/fresh.ts), so `component` is a thenable resolving
|
|
452
|
+
* to the sentinel rather than the sentinel itself — a synchronous
|
|
453
|
+
* `isPrerenderPassthrough(component)` on the Promise returns false and the build
|
|
454
|
+
* bakes a corrupt artifact instead of deferring. Resolve thenables first.
|
|
455
|
+
*
|
|
456
|
+
* Rejections are swallowed here: a throwing build handler resurfaces during
|
|
457
|
+
* segment serialization, preserving the prior error-handling behavior.
|
|
458
|
+
*/
|
|
459
|
+
export async function detectPrerenderPassthrough(
|
|
460
|
+
segments: ReadonlyArray<{ component: unknown }>,
|
|
461
|
+
): Promise<boolean> {
|
|
462
|
+
for (const seg of segments) {
|
|
463
|
+
let component: unknown = seg.component;
|
|
464
|
+
if (
|
|
465
|
+
component &&
|
|
466
|
+
typeof (component as { then?: unknown }).then === "function"
|
|
467
|
+
) {
|
|
468
|
+
try {
|
|
469
|
+
component = await component;
|
|
470
|
+
} catch {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (isPrerenderPassthrough(component)) return true;
|
|
475
|
+
}
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
|
|
424
479
|
// -- Type guards ------------------------------------------------------------
|
|
425
480
|
|
|
426
481
|
/**
|
|
@@ -3,26 +3,17 @@
|
|
|
3
3
|
import { Component, useState, type ReactNode } from "react";
|
|
4
4
|
import type { ClientErrorBoundaryFallbackProps } from "./types.js";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Check if an error is a network-related error
|
|
8
|
-
*/
|
|
9
6
|
function isNetworkError(error: Error): boolean {
|
|
10
7
|
return error.name === "NetworkError";
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/**
|
|
14
|
-
* Network error fallback UI with retry functionality
|
|
15
|
-
* Shows a connection-specific message and allows retrying via page refresh
|
|
16
|
-
*/
|
|
17
10
|
function NetworkErrorFallback({
|
|
18
11
|
error,
|
|
19
|
-
reset,
|
|
20
12
|
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
21
13
|
const [isRetrying, setIsRetrying] = useState(false);
|
|
22
14
|
|
|
23
15
|
const handleRetry = (): void => {
|
|
24
16
|
setIsRetrying(true);
|
|
25
|
-
// Refresh the page to retry the request
|
|
26
17
|
window.location.reload();
|
|
27
18
|
};
|
|
28
19
|
|
|
@@ -42,7 +33,6 @@ function NetworkErrorFallback({
|
|
|
42
33
|
marginBottom: "1rem",
|
|
43
34
|
}}
|
|
44
35
|
>
|
|
45
|
-
{/* Simple cloud with x icon using CSS */}
|
|
46
36
|
<span style={{ color: "#9ca3af" }}>☁</span>
|
|
47
37
|
</div>
|
|
48
38
|
<h1
|
|
@@ -101,10 +91,6 @@ function NetworkErrorFallback({
|
|
|
101
91
|
);
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
/**
|
|
105
|
-
* Default fallback UI for root error boundary
|
|
106
|
-
* This is shown when an unhandled error bubbles up to the root
|
|
107
|
-
*/
|
|
108
94
|
function RootErrorFallback({
|
|
109
95
|
error,
|
|
110
96
|
reset,
|
|
@@ -230,7 +216,6 @@ export class RootErrorBoundary extends Component<
|
|
|
230
216
|
}
|
|
231
217
|
|
|
232
218
|
componentDidMount(): void {
|
|
233
|
-
// Listen for popstate (back/forward navigation) to reset error state
|
|
234
219
|
window.addEventListener("popstate", this.handlePopState);
|
|
235
220
|
}
|
|
236
221
|
|
|
@@ -247,15 +232,13 @@ export class RootErrorBoundary extends Component<
|
|
|
247
232
|
}
|
|
248
233
|
|
|
249
234
|
componentDidUpdate(prevProps: { children: ReactNode }): void {
|
|
250
|
-
// Reset error
|
|
251
|
-
// This allows the app to recover after navigation away from an errored route
|
|
235
|
+
// Reset error on children change (navigation).
|
|
252
236
|
if (this.state.hasError && prevProps.children !== this.props.children) {
|
|
253
237
|
this.setState({ hasError: false, error: null });
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
|
|
257
241
|
handlePopState = (): void => {
|
|
258
|
-
// Reset error state on back/forward navigation
|
|
259
242
|
if (this.state.hasError) {
|
|
260
243
|
this.setState({ hasError: false, error: null });
|
|
261
244
|
}
|
|
@@ -276,7 +259,6 @@ export class RootErrorBoundary extends Component<
|
|
|
276
259
|
segmentType: "route" as const,
|
|
277
260
|
};
|
|
278
261
|
|
|
279
|
-
// Use specialized fallback for network errors
|
|
280
262
|
if (isNetworkError(this.state.error)) {
|
|
281
263
|
return <NetworkErrorFallback error={errorInfo} reset={this.reset} />;
|
|
282
264
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { Suspense, use
|
|
3
|
+
import { Suspense, use } from "react";
|
|
4
4
|
import { invariant } from "./errors";
|
|
5
5
|
import { OutletProvider } from "./outlet-provider.js";
|
|
6
6
|
import type { ResolvedSegment } from "./types.js";
|
|
@@ -36,37 +36,6 @@ export function RouteContentWrapper({
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function RouteContentWrapperCallback<T>({
|
|
40
|
-
resolve,
|
|
41
|
-
fallback,
|
|
42
|
-
children,
|
|
43
|
-
}: {
|
|
44
|
-
resolve: Promise<T> | T;
|
|
45
|
-
fallback?: ReactNode;
|
|
46
|
-
children: (data: T) => ReactNode;
|
|
47
|
-
}): ReactNode {
|
|
48
|
-
const id = useId();
|
|
49
|
-
invariant(children, "RouteContentWrapperCallback requires children");
|
|
50
|
-
invariant(
|
|
51
|
-
typeof children === "function",
|
|
52
|
-
"RouteContentWrapperCallback requires children to be a function",
|
|
53
|
-
);
|
|
54
|
-
invariant(
|
|
55
|
-
resolve !== undefined,
|
|
56
|
-
"RouteContentWrapperCallback requires resolve",
|
|
57
|
-
);
|
|
58
|
-
return (
|
|
59
|
-
<Suspense
|
|
60
|
-
fallback={fallback ?? null}
|
|
61
|
-
key={"route-content-suspense-callback-" + id}
|
|
62
|
-
>
|
|
63
|
-
<SuspenderCallback resolve={resolve} key={id}>
|
|
64
|
-
{children}
|
|
65
|
-
</SuspenderCallback>
|
|
66
|
-
</Suspense>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
39
|
const Suspender = ({
|
|
71
40
|
content,
|
|
72
41
|
}: {
|
|
@@ -77,18 +46,6 @@ const Suspender = ({
|
|
|
77
46
|
return use(content);
|
|
78
47
|
};
|
|
79
48
|
|
|
80
|
-
const SuspenderCallback = <T,>({
|
|
81
|
-
resolve,
|
|
82
|
-
children,
|
|
83
|
-
}: {
|
|
84
|
-
resolve: Promise<T> | T;
|
|
85
|
-
children: (data: T) => ReactNode;
|
|
86
|
-
}): ReactNode => {
|
|
87
|
-
return resolve instanceof Promise
|
|
88
|
-
? children(use(resolve))
|
|
89
|
-
: children(resolve);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
49
|
/**
|
|
93
50
|
* LoaderBoundary - Client component that resolves loader promises and renders OutletProvider
|
|
94
51
|
*
|
|
@@ -302,15 +302,15 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
302
302
|
* Supports these call signatures:
|
|
303
303
|
* - cache() - no args, uses app-level defaults (for loader caching)
|
|
304
304
|
* - cache(() => [...]) - wraps children with app-level defaults
|
|
305
|
-
* - cache('profileName') - uses a named cache profile
|
|
306
|
-
* - cache('profileName', () => [...]) - named profile with children
|
|
307
305
|
* - cache({ ttl: 60 }, () => [...]) - with explicit options
|
|
306
|
+
*
|
|
307
|
+
* Named cache profiles are applied via the `"use cache: <profile>"` directive,
|
|
308
|
+
* not a `cache("profileName")` form in the route tree.
|
|
308
309
|
*/
|
|
309
310
|
const cache: RouteHelpers<any, any>["cache"] = (
|
|
310
311
|
optionsOrChildren?:
|
|
311
312
|
| PartialCacheOptions
|
|
312
313
|
| false
|
|
313
|
-
| string
|
|
314
314
|
| (() => UseItems<AllUseItems>),
|
|
315
315
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
316
316
|
) => {
|
|
@@ -326,18 +326,6 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
326
326
|
// cache() - no args, use defaults
|
|
327
327
|
options = {};
|
|
328
328
|
children = undefined;
|
|
329
|
-
} else if (typeof optionsOrChildren === "string") {
|
|
330
|
-
// cache('profileName') or cache('profileName', () => [...])
|
|
331
|
-
// Resolve from context-scoped profiles (set per-router via HelperContext).
|
|
332
|
-
const ctxStore = RangoContext.getStore();
|
|
333
|
-
const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
|
|
334
|
-
invariant(
|
|
335
|
-
profile,
|
|
336
|
-
`cache("${optionsOrChildren}"): unknown cache profile. ` +
|
|
337
|
-
`Define it in createRouter({ cacheProfiles: { "${optionsOrChildren}": { ttl: ... } } }).`,
|
|
338
|
-
);
|
|
339
|
-
options = { ttl: profile.ttl, swr: profile.swr, tags: profile.tags };
|
|
340
|
-
children = maybeChildren;
|
|
341
329
|
} else if (typeof optionsOrChildren === "function") {
|
|
342
330
|
// cache(() => [...]) - use empty options (will use defaults)
|
|
343
331
|
options = {};
|
|
@@ -393,10 +381,10 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
393
381
|
return { name: namespace, type: "cache" } as CacheItem;
|
|
394
382
|
}
|
|
395
383
|
|
|
396
|
-
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
384
|
+
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
385
|
+
// — writes cache config to the loader entry. The wrapper form creates a
|
|
386
|
+
// structural cache boundary with its own children scope, which has no effect
|
|
387
|
+
// on the loader and would silently no-op.
|
|
400
388
|
invariant(
|
|
401
389
|
!(ctx.parent && (ctx.parent as any).type === "loader"),
|
|
402
390
|
"cache() wrapper form is not valid inside loader() use(). Use cache({...}) without children to configure the loader's cache.",
|
|
@@ -441,10 +441,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
441
441
|
cache: {
|
|
442
442
|
(): CacheItem;
|
|
443
443
|
(children: () => UseItems<AllUseItems>): CacheItem;
|
|
444
|
-
(profileName: string): CacheItem;
|
|
445
|
-
(profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
|
|
446
444
|
(
|
|
447
|
-
options: PartialCacheOptions | false,
|
|
445
|
+
options: PartialCacheOptions<TEnv> | false,
|
|
448
446
|
use?: () => UseItems<AllUseItems>,
|
|
449
447
|
): CacheItem;
|
|
450
448
|
};
|
|
@@ -497,6 +495,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
497
495
|
* @param children - Optional callback returning child routes to wrap
|
|
498
496
|
*/
|
|
499
497
|
transition: {
|
|
498
|
+
(): TransitionItem;
|
|
499
|
+
(children: () => UseItems<AllUseItems>): TransitionItem;
|
|
500
500
|
(config: TransitionConfig): TransitionItem;
|
|
501
501
|
(
|
|
502
502
|
config: TransitionConfig,
|
|
@@ -85,9 +85,19 @@ export function redirect(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Auto-prefix root-relative URLs with basename for app-local redirects.
|
|
88
|
+
// Treat the URL as already-prefixed when the basename is followed by a path
|
|
89
|
+
// separator, a query, a fragment, or end-of-string, so "/admin?tab=x" and
|
|
90
|
+
// "/admin#frag" are not double-prefixed into "/admin/admin?tab=x".
|
|
88
91
|
const bn = _getRequestContext()?._basename;
|
|
89
92
|
let resolvedUrl = url;
|
|
90
|
-
if (
|
|
93
|
+
if (
|
|
94
|
+
bn &&
|
|
95
|
+
url.startsWith("/") &&
|
|
96
|
+
url !== bn &&
|
|
97
|
+
!url.startsWith(bn + "/") &&
|
|
98
|
+
!url.startsWith(bn + "?") &&
|
|
99
|
+
!url.startsWith(bn + "#")
|
|
100
|
+
) {
|
|
91
101
|
resolvedUrl = url === "/" ? bn : bn + url;
|
|
92
102
|
}
|
|
93
103
|
|
package/src/route-map-builder.ts
CHANGED
|
@@ -8,15 +8,10 @@
|
|
|
8
8
|
* See docs/manifests.md for the full data flow.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// Singleton route map instance - populated incrementally as routes are encountered
|
|
12
11
|
let globalRouteMap: Record<string, string> = {};
|
|
13
12
|
|
|
14
|
-
// Cached complete manifest - includes all routes (including lazy includes)
|
|
15
|
-
// Set from runtime cache or build-time import
|
|
16
13
|
let cachedManifest: Record<string, string> | null = null;
|
|
17
14
|
|
|
18
|
-
// Pre-computed route entries from build-time prefix tree leaf nodes.
|
|
19
|
-
// Used by evaluateLazyEntry() to skip running the handler for route matching.
|
|
20
15
|
let cachedPrecomputedEntries: Array<{
|
|
21
16
|
staticPrefix: string;
|
|
22
17
|
routes: Record<string, string>;
|
|
@@ -43,7 +38,6 @@ export function registerRouteMap(map: Record<string, string>): void {
|
|
|
43
38
|
* @internal
|
|
44
39
|
*/
|
|
45
40
|
export function getGlobalRouteMap(): Record<string, string> {
|
|
46
|
-
// Cached manifest is complete (includes lazy routes), so prefer it
|
|
47
41
|
if (cachedManifest) {
|
|
48
42
|
return cachedManifest;
|
|
49
43
|
}
|
|
@@ -231,10 +225,6 @@ export function waitForManifestReady(): Promise<void> | null {
|
|
|
231
225
|
return manifestReadyPromise;
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// Route Scope Registry
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
228
|
// Tracks whether each route is at root scope (no named include boundary above).
|
|
239
229
|
// Used by dot-local reverse resolution to decide whether bare-name fallback
|
|
240
230
|
// is allowed after scoped lookups are exhausted.
|
|
@@ -259,14 +249,8 @@ export function isRouteRootScoped(routeName: string): boolean | undefined {
|
|
|
259
249
|
return rootScopeRoutes.get(routeName);
|
|
260
250
|
}
|
|
261
251
|
|
|
262
|
-
// ============================================================================
|
|
263
|
-
// Search Schema Registry
|
|
264
|
-
// ============================================================================
|
|
265
|
-
|
|
266
252
|
import type { SearchSchema } from "./search-params.js";
|
|
267
253
|
|
|
268
|
-
// Global search schema map: route name -> search schema descriptor.
|
|
269
|
-
// Populated by path() when a search option is provided.
|
|
270
254
|
const globalSearchSchemas: Map<string, SearchSchema> = new Map();
|
|
271
255
|
|
|
272
256
|
export function registerSearchSchema(
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a router basename to its canonical form: a single leading slash,
|
|
3
|
+
* no trailing slash, and `undefined` for an empty or bare-"/" value.
|
|
4
|
+
*
|
|
5
|
+
* This is the single source of truth used by both createRouter() (so the RSC
|
|
6
|
+
* handler stores a canonical basename on the request context) and the testing
|
|
7
|
+
* primitives (so a consumer can pass the same un-normalized string their
|
|
8
|
+
* createRouter() accepts and observe the same redirect() prefixing).
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeBasename(basename?: string): string | undefined {
|
|
11
|
+
if (!basename) return undefined;
|
|
12
|
+
const trimmed = basename.replace(/^\/+|\/+$/g, "");
|
|
13
|
+
return trimmed ? "/" + trimmed : undefined;
|
|
14
|
+
}
|
|
@@ -14,7 +14,6 @@ import { traverseBack } from "./pattern-matching.js";
|
|
|
14
14
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
15
15
|
import type { RouteSnapshot } from "./route-snapshot.js";
|
|
16
16
|
|
|
17
|
-
// Response type -> MIME type used for Accept header matching
|
|
18
17
|
export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
19
18
|
json: "application/json",
|
|
20
19
|
text: "text/plain",
|
|
@@ -23,7 +22,6 @@ export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
|
23
22
|
md: "text/markdown",
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
// Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
|
|
27
25
|
export const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
|
|
28
26
|
Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
|
|
29
27
|
);
|
|
@@ -71,12 +69,10 @@ export function parseAcceptTypes(accept: string): AcceptEntry[] {
|
|
|
71
69
|
}
|
|
72
70
|
entries.push({ mime, q, order: i });
|
|
73
71
|
}
|
|
74
|
-
// Sort: highest q first, then lowest client order first (stable)
|
|
75
72
|
entries.sort((a, b) => b.q - a.q || a.order - b.order);
|
|
76
73
|
return entries;
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
// Sentinel response type for RSC routes in negotiation candidates
|
|
80
76
|
export const RSC_RESPONSE_TYPE = "__rsc__";
|
|
81
77
|
|
|
82
78
|
/**
|
|
@@ -89,7 +85,6 @@ export function pickNegotiateVariant(
|
|
|
89
85
|
acceptEntries: AcceptEntry[],
|
|
90
86
|
candidates: Array<{ routeKey: string; responseType: string }>,
|
|
91
87
|
): { routeKey: string; responseType: string } {
|
|
92
|
-
// Build a MIME -> candidate lookup for O(1) matching
|
|
93
88
|
const byCandidateMime = new Map<
|
|
94
89
|
string,
|
|
95
90
|
{ routeKey: string; responseType: string }
|
|
@@ -106,9 +101,7 @@ export function pickNegotiateVariant(
|
|
|
106
101
|
|
|
107
102
|
for (const entry of acceptEntries) {
|
|
108
103
|
if (entry.q === 0) continue;
|
|
109
|
-
// Wildcard matches first candidate
|
|
110
104
|
if (entry.mime === "*/*") return candidates[0]!;
|
|
111
|
-
// Type wildcard (e.g. "text/*") -- match first candidate with that type
|
|
112
105
|
if (entry.mime.endsWith("/*")) {
|
|
113
106
|
const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
|
|
114
107
|
for (const [mime, candidate] of byCandidateMime) {
|
|
@@ -119,7 +112,6 @@ export function pickNegotiateVariant(
|
|
|
119
112
|
const match = byCandidateMime.get(entry.mime);
|
|
120
113
|
if (match) return match;
|
|
121
114
|
}
|
|
122
|
-
// No match -- use first candidate as default
|
|
123
115
|
return candidates[0]!;
|
|
124
116
|
}
|
|
125
117
|
|
|
@@ -173,7 +165,6 @@ export async function negotiateRoute(
|
|
|
173
165
|
|
|
174
166
|
const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
|
|
175
167
|
|
|
176
|
-
// Build candidate list preserving definition order.
|
|
177
168
|
const variants = matched.negotiateVariants;
|
|
178
169
|
let candidates: Array<{ routeKey: string; responseType: string }>;
|
|
179
170
|
if (responseType) {
|
|
@@ -190,12 +181,10 @@ export async function negotiateRoute(
|
|
|
190
181
|
|
|
191
182
|
const variant = pickNegotiateVariant(acceptEntries, candidates);
|
|
192
183
|
|
|
193
|
-
// RSC won negotiation
|
|
194
184
|
if (variant.responseType === RSC_RESPONSE_TYPE) {
|
|
195
185
|
return null;
|
|
196
186
|
}
|
|
197
187
|
|
|
198
|
-
// Primary response-type won — use existing manifest entry and middleware
|
|
199
188
|
if (responseType && variant.routeKey === matched.routeKey) {
|
|
200
189
|
return {
|
|
201
190
|
responseType,
|
|
@@ -205,8 +194,6 @@ export async function negotiateRoute(
|
|
|
205
194
|
negotiated: true,
|
|
206
195
|
};
|
|
207
196
|
}
|
|
208
|
-
|
|
209
|
-
// Different variant won — load its manifest entry
|
|
210
197
|
const negotiateEntry = await loadManifest(
|
|
211
198
|
matched.entry,
|
|
212
199
|
variant.routeKey,
|
|
@@ -117,16 +117,10 @@ export function findNearestErrorBoundary(
|
|
|
117
117
|
let current: EntryData | null = entry;
|
|
118
118
|
|
|
119
119
|
while (current) {
|
|
120
|
-
// Check if this entry has error boundaries defined
|
|
121
120
|
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
122
|
-
// Return the last error boundary (most recently defined takes precedence)
|
|
123
121
|
return current.errorBoundary[current.errorBoundary.length - 1];
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
// Check orphan layouts for error boundaries
|
|
127
|
-
// Orphan layouts are siblings that render alongside the main route chain
|
|
128
|
-
// They can define error boundaries that catch errors from routes in the same route group
|
|
129
|
-
// Check from first to last (first sibling takes precedence as the "outer" wrapper)
|
|
130
124
|
if (current.layout && current.layout.length > 0) {
|
|
131
125
|
for (const orphan of current.layout) {
|
|
132
126
|
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
@@ -153,11 +147,21 @@ export function findNearestNotFoundBoundary(
|
|
|
153
147
|
let current: EntryData | null = entry;
|
|
154
148
|
|
|
155
149
|
while (current) {
|
|
156
|
-
// Check if this entry has notFound boundaries defined
|
|
157
150
|
if (current.notFoundBoundary && current.notFoundBoundary.length > 0) {
|
|
158
|
-
// Return the last notFound boundary (most recently defined takes precedence)
|
|
159
151
|
return current.notFoundBoundary[current.notFoundBoundary.length - 1];
|
|
160
152
|
}
|
|
153
|
+
|
|
154
|
+
// Check orphan layouts mirroring findNearestErrorBoundary: notFoundBoundary
|
|
155
|
+
// attaches identically (onto parent.notFoundBoundary), and an orphan layout
|
|
156
|
+
// (parent=null) is reachable only via this scan. First sibling is "outer".
|
|
157
|
+
if (current.layout && current.layout.length > 0) {
|
|
158
|
+
for (const orphan of current.layout) {
|
|
159
|
+
if (orphan.notFoundBoundary && orphan.notFoundBoundary.length > 0) {
|
|
160
|
+
return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
current = current.parent;
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -207,22 +211,17 @@ export function createErrorSegment(
|
|
|
207
211
|
entry: EntryData,
|
|
208
212
|
params: Record<string, string>,
|
|
209
213
|
): ResolvedSegment {
|
|
210
|
-
// Determine the component to render
|
|
211
214
|
let component: ReactNode;
|
|
212
215
|
|
|
213
216
|
if (typeof fallback === "function") {
|
|
214
|
-
// ErrorBoundaryHandler - call with error info
|
|
215
217
|
const props: ErrorBoundaryFallbackProps = {
|
|
216
218
|
error: errorInfo,
|
|
217
219
|
};
|
|
218
220
|
component = fallback(props);
|
|
219
221
|
} else {
|
|
220
|
-
// Static ReactNode fallback
|
|
221
222
|
component = fallback;
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
// Error segment uses the same ID as the layout that has the error boundary
|
|
225
|
-
// The error boundary content replaces the layout's outlet content
|
|
226
225
|
return {
|
|
227
226
|
id: entry.shortCode,
|
|
228
227
|
namespace: entry.id,
|
|
@@ -261,17 +260,14 @@ export function createNotFoundSegment(
|
|
|
261
260
|
entry: EntryData,
|
|
262
261
|
params: Record<string, string>,
|
|
263
262
|
): ResolvedSegment {
|
|
264
|
-
// Determine the component to render
|
|
265
263
|
let component: ReactNode;
|
|
266
264
|
|
|
267
265
|
if (typeof fallback === "function") {
|
|
268
|
-
// NotFoundBoundaryHandler - call with props
|
|
269
266
|
const props: NotFoundBoundaryFallbackProps = {
|
|
270
267
|
notFound: notFoundInfo,
|
|
271
268
|
};
|
|
272
269
|
component = fallback(props);
|
|
273
270
|
} else {
|
|
274
|
-
// Static ReactNode fallback
|
|
275
271
|
component = fallback;
|
|
276
272
|
}
|
|
277
273
|
|