@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +60 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +22 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -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 +121 -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/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- 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 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- 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 +3 -6
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -11
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- 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-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- 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 +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- 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 +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- 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 +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +20 -58
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -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 +87 -47
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -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 +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -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 +330 -0
- package/src/testing/render-route.tsx +566 -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/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- 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 +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- 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 +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route-param comparison helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shallow equality for two route-param records. Same-reference is a fast path;
|
|
7
|
+
* otherwise compares key count then each value.
|
|
8
|
+
*/
|
|
9
|
+
export function paramsEqual(
|
|
10
|
+
a: Record<string, string>,
|
|
11
|
+
b: Record<string, string>,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (a === b) return true;
|
|
14
|
+
|
|
15
|
+
const keysA = Object.keys(a);
|
|
16
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
17
|
+
|
|
18
|
+
for (const key of keysA) {
|
|
19
|
+
if (a[key] !== b[key]) return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
@@ -33,13 +33,6 @@ export interface ParsedSegment {
|
|
|
33
33
|
*/
|
|
34
34
|
export function parsePattern(pattern: string): ParsedSegment[] {
|
|
35
35
|
const segments: ParsedSegment[] = [];
|
|
36
|
-
// Match: /segment where segment can be:
|
|
37
|
-
// - static text
|
|
38
|
-
// - :param
|
|
39
|
-
// - :param?
|
|
40
|
-
// - :param(a|b)
|
|
41
|
-
// - :param(a|b)?
|
|
42
|
-
// - *
|
|
43
36
|
const segmentRegex =
|
|
44
37
|
/\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
|
|
45
38
|
|
|
@@ -81,7 +74,6 @@ export function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
81
74
|
export interface CompiledPattern {
|
|
82
75
|
regex: RegExp;
|
|
83
76
|
paramNames: string[];
|
|
84
|
-
optionalParams: Set<string>;
|
|
85
77
|
hasTrailingSlash: boolean;
|
|
86
78
|
/**
|
|
87
79
|
* Param-name → allowed values for constrained params (e.g. `:lang(en|gb)`).
|
|
@@ -149,7 +141,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
149
141
|
|
|
150
142
|
const segments = parsePattern(normalizedPattern);
|
|
151
143
|
const paramNames: string[] = [];
|
|
152
|
-
const optionalParams = new Set<string>();
|
|
153
144
|
let constraints: Record<string, string[]> | undefined;
|
|
154
145
|
|
|
155
146
|
let regexPattern = "";
|
|
@@ -171,7 +162,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
171
162
|
}
|
|
172
163
|
|
|
173
164
|
if (segment.optional) {
|
|
174
|
-
optionalParams.add(segment.value);
|
|
175
165
|
// Optional: make the whole /segment optional
|
|
176
166
|
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
177
167
|
} else {
|
|
@@ -183,7 +173,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
183
173
|
}
|
|
184
174
|
}
|
|
185
175
|
|
|
186
|
-
// Handle root path
|
|
187
176
|
if (regexPattern === "") {
|
|
188
177
|
regexPattern = "/";
|
|
189
178
|
}
|
|
@@ -210,7 +199,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
210
199
|
return {
|
|
211
200
|
regex: new RegExp(`^${regexPattern}$`),
|
|
212
201
|
paramNames,
|
|
213
|
-
optionalParams,
|
|
214
202
|
hasTrailingSlash,
|
|
215
203
|
...(constraints ? { constraints } : {}),
|
|
216
204
|
};
|
|
@@ -285,7 +273,6 @@ function buildParamsFromMatch(
|
|
|
285
273
|
export function extractStaticPrefix(pattern: string): string {
|
|
286
274
|
if (!pattern || pattern === "/") return "";
|
|
287
275
|
|
|
288
|
-
// Find the first occurrence of : or *
|
|
289
276
|
const paramIndex = pattern.indexOf(":");
|
|
290
277
|
const wildcardIndex = pattern.indexOf("*");
|
|
291
278
|
|
|
@@ -299,16 +286,13 @@ export function extractStaticPrefix(pattern: string): string {
|
|
|
299
286
|
}
|
|
300
287
|
|
|
301
288
|
if (cutIndex === -1) {
|
|
302
|
-
// No params or wildcards - entire pattern is static
|
|
303
289
|
return pattern;
|
|
304
290
|
}
|
|
305
291
|
|
|
306
292
|
if (cutIndex === 0) {
|
|
307
|
-
// Pattern starts with : or * - no static prefix
|
|
308
293
|
return "";
|
|
309
294
|
}
|
|
310
295
|
|
|
311
|
-
// Find the last / before the param
|
|
312
296
|
const lastSlash = pattern.lastIndexOf("/", cutIndex - 1);
|
|
313
297
|
if (lastSlash === -1 || lastSlash === 0) {
|
|
314
298
|
return "";
|
|
@@ -317,13 +301,28 @@ export function extractStaticPrefix(pattern: string): string {
|
|
|
317
301
|
return pattern.slice(0, lastSlash);
|
|
318
302
|
}
|
|
319
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Join a URL prefix to a sub-prefix, collapsing the duplicate slash when the
|
|
306
|
+
* base ends with "/" and the sub-prefix starts with "/". This mirrors the
|
|
307
|
+
* canonical join in `include()` (urls/include-helper.ts) and `runWithPrefixes`
|
|
308
|
+
* (server/context.ts) so a nested lazy include's runtime staticPrefix matches
|
|
309
|
+
* the build-time trie's `sp` (e.g. `include("/parent/", …)` containing
|
|
310
|
+
* `include("/child", …)` resolves to `/parent/child`, not `/parent//child`).
|
|
311
|
+
*/
|
|
312
|
+
export function joinPrefix(base: string | undefined, prefix: string): string {
|
|
313
|
+
if (!base) return prefix;
|
|
314
|
+
return base.endsWith("/") && prefix.startsWith("/")
|
|
315
|
+
? base + prefix.slice(1)
|
|
316
|
+
: base + prefix;
|
|
317
|
+
}
|
|
318
|
+
|
|
320
319
|
/**
|
|
321
320
|
* Match a pathname against registered routes
|
|
322
321
|
*
|
|
323
322
|
* Note: Optional params that are absent in the path are omitted from the
|
|
324
323
|
* returned `params` (read as `undefined`), matching the trie matcher and
|
|
325
|
-
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition
|
|
326
|
-
*
|
|
324
|
+
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition to
|
|
325
|
+
* determine which keys are optional.
|
|
327
326
|
*
|
|
328
327
|
* Trailing slash handling (priority order):
|
|
329
328
|
* 1. Per-route `trailingSlash` config from route()
|
|
@@ -341,10 +340,7 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
341
340
|
entry: RouteEntry<TEnv>;
|
|
342
341
|
routeKey: string;
|
|
343
342
|
params: Record<string, string>;
|
|
344
|
-
optionalParams: Set<string>;
|
|
345
343
|
redirectTo?: string;
|
|
346
|
-
/** Ancestry shortCodes for layout pruning (from trie match) */
|
|
347
|
-
ancestry?: string[];
|
|
348
344
|
/** Route has pre-rendered data available (from trie) */
|
|
349
345
|
pr?: true;
|
|
350
346
|
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
@@ -422,8 +418,6 @@ export function findMatch<TEnv>(
|
|
|
422
418
|
: pathname + "/";
|
|
423
419
|
|
|
424
420
|
for (const entry of routesEntries) {
|
|
425
|
-
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
426
|
-
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
427
421
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
428
422
|
if (effectiveDebug) {
|
|
429
423
|
debugStats.entriesSkipped++;
|
|
@@ -435,8 +429,6 @@ export function findMatch<TEnv>(
|
|
|
435
429
|
continue;
|
|
436
430
|
}
|
|
437
431
|
|
|
438
|
-
// Check if this is a lazy entry that needs evaluation
|
|
439
|
-
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
440
432
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
441
433
|
if (effectiveDebug) {
|
|
442
434
|
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
@@ -457,7 +449,6 @@ export function findMatch<TEnv>(
|
|
|
457
449
|
debugStats.routesChecked++;
|
|
458
450
|
}
|
|
459
451
|
|
|
460
|
-
// Join prefix and pattern, handling edge cases
|
|
461
452
|
let fullPattern: string;
|
|
462
453
|
if (entry.prefix === "" || entry.prefix === "/") {
|
|
463
454
|
fullPattern = pattern;
|
|
@@ -467,19 +458,12 @@ export function findMatch<TEnv>(
|
|
|
467
458
|
fullPattern = entry.prefix + pattern;
|
|
468
459
|
}
|
|
469
460
|
|
|
470
|
-
const {
|
|
471
|
-
|
|
472
|
-
paramNames,
|
|
473
|
-
optionalParams,
|
|
474
|
-
hasTrailingSlash,
|
|
475
|
-
constraints,
|
|
476
|
-
} = getCompiledPattern(fullPattern);
|
|
461
|
+
const { regex, paramNames, hasTrailingSlash, constraints } =
|
|
462
|
+
getCompiledPattern(fullPattern);
|
|
477
463
|
|
|
478
|
-
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
479
464
|
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
480
465
|
entry.trailingSlash?.[routeKey];
|
|
481
466
|
|
|
482
|
-
// Prerender flag from entry metadata (set by urls() for prerender handlers)
|
|
483
467
|
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
484
468
|
? { pr: true as const }
|
|
485
469
|
: {};
|
|
@@ -487,13 +471,10 @@ export function findMatch<TEnv>(
|
|
|
487
471
|
? { pt: true as const }
|
|
488
472
|
: {};
|
|
489
473
|
|
|
490
|
-
// Try exact match first
|
|
491
474
|
const match = regex.exec(pathname);
|
|
492
475
|
if (match) {
|
|
493
476
|
const params = buildParamsFromMatch(match, paramNames);
|
|
494
477
|
|
|
495
|
-
// Validate constraints against decoded values; a failure falls
|
|
496
|
-
// through to the next route so other patterns can still match.
|
|
497
478
|
if (!satisfiesConstraints(params, constraints)) {
|
|
498
479
|
continue;
|
|
499
480
|
}
|
|
@@ -506,29 +487,24 @@ export function findMatch<TEnv>(
|
|
|
506
487
|
});
|
|
507
488
|
}
|
|
508
489
|
|
|
509
|
-
// Check if trailing slash mode requires redirect even on exact match
|
|
510
490
|
if (
|
|
511
491
|
trailingSlashMode === "always" &&
|
|
512
492
|
!pathnameHasTrailingSlash &&
|
|
513
493
|
pathname !== "/"
|
|
514
494
|
) {
|
|
515
|
-
// Mode says always have trailing slash, but pathname doesn't have it
|
|
516
495
|
return {
|
|
517
496
|
entry,
|
|
518
497
|
routeKey,
|
|
519
498
|
params,
|
|
520
|
-
optionalParams,
|
|
521
499
|
redirectTo: pathname + "/",
|
|
522
500
|
...prFlag,
|
|
523
501
|
...ptFlag,
|
|
524
502
|
};
|
|
525
503
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
526
|
-
// Mode says never have trailing slash, but pathname has it
|
|
527
504
|
return {
|
|
528
505
|
entry,
|
|
529
506
|
routeKey,
|
|
530
507
|
params,
|
|
531
|
-
optionalParams,
|
|
532
508
|
redirectTo: pathname.slice(0, -1),
|
|
533
509
|
...prFlag,
|
|
534
510
|
...ptFlag,
|
|
@@ -539,13 +515,11 @@ export function findMatch<TEnv>(
|
|
|
539
515
|
entry,
|
|
540
516
|
routeKey,
|
|
541
517
|
params,
|
|
542
|
-
optionalParams,
|
|
543
518
|
...prFlag,
|
|
544
519
|
...ptFlag,
|
|
545
520
|
};
|
|
546
521
|
}
|
|
547
522
|
|
|
548
|
-
// Try alternate pathname (opposite trailing slash)
|
|
549
523
|
const altMatch = regex.exec(alternatePathname);
|
|
550
524
|
if (altMatch) {
|
|
551
525
|
const params = buildParamsFromMatch(altMatch, paramNames);
|
|
@@ -554,25 +528,20 @@ export function findMatch<TEnv>(
|
|
|
554
528
|
continue;
|
|
555
529
|
}
|
|
556
530
|
|
|
557
|
-
// Determine redirect behavior based on mode
|
|
558
531
|
if (trailingSlashMode === "ignore") {
|
|
559
|
-
// Match without redirect
|
|
560
532
|
return {
|
|
561
533
|
entry,
|
|
562
534
|
routeKey,
|
|
563
535
|
params,
|
|
564
|
-
optionalParams,
|
|
565
536
|
...prFlag,
|
|
566
537
|
...ptFlag,
|
|
567
538
|
};
|
|
568
539
|
} else if (trailingSlashMode === "never") {
|
|
569
|
-
// Redirect to no trailing slash
|
|
570
540
|
if (pathnameHasTrailingSlash) {
|
|
571
541
|
return {
|
|
572
542
|
entry,
|
|
573
543
|
routeKey,
|
|
574
544
|
params,
|
|
575
|
-
optionalParams,
|
|
576
545
|
redirectTo: alternatePathname,
|
|
577
546
|
...prFlag,
|
|
578
547
|
...ptFlag,
|
|
@@ -582,18 +551,15 @@ export function findMatch<TEnv>(
|
|
|
582
551
|
entry,
|
|
583
552
|
routeKey,
|
|
584
553
|
params,
|
|
585
|
-
optionalParams,
|
|
586
554
|
...prFlag,
|
|
587
555
|
...ptFlag,
|
|
588
556
|
};
|
|
589
557
|
} else if (trailingSlashMode === "always") {
|
|
590
|
-
// Redirect to with trailing slash
|
|
591
558
|
if (!pathnameHasTrailingSlash) {
|
|
592
559
|
return {
|
|
593
560
|
entry,
|
|
594
561
|
routeKey,
|
|
595
562
|
params,
|
|
596
|
-
optionalParams,
|
|
597
563
|
redirectTo: alternatePathname,
|
|
598
564
|
...prFlag,
|
|
599
565
|
...ptFlag,
|
|
@@ -603,13 +569,10 @@ export function findMatch<TEnv>(
|
|
|
603
569
|
entry,
|
|
604
570
|
routeKey,
|
|
605
571
|
params,
|
|
606
|
-
optionalParams,
|
|
607
572
|
...prFlag,
|
|
608
573
|
...ptFlag,
|
|
609
574
|
};
|
|
610
575
|
} else {
|
|
611
|
-
// No explicit mode - use pattern-based detection
|
|
612
|
-
// Redirect to canonical form (what the pattern defines)
|
|
613
576
|
const canonicalPath = hasTrailingSlash
|
|
614
577
|
? alternatePathname
|
|
615
578
|
: pathname.slice(0, -1);
|
|
@@ -617,7 +580,6 @@ export function findMatch<TEnv>(
|
|
|
617
580
|
entry,
|
|
618
581
|
routeKey,
|
|
619
582
|
params,
|
|
620
|
-
optionalParams,
|
|
621
583
|
redirectTo: canonicalPath,
|
|
622
584
|
...prFlag,
|
|
623
585
|
...ptFlag,
|
|
@@ -638,7 +600,7 @@ export function* traverseBack(entry: EntryData): Generator<EntryData> {
|
|
|
638
600
|
let current: EntryData | null = entry;
|
|
639
601
|
const items = [] as EntryData[];
|
|
640
602
|
while (current !== null) {
|
|
641
|
-
items.push(current);
|
|
603
|
+
items.push(current);
|
|
642
604
|
current = current.parent;
|
|
643
605
|
}
|
|
644
606
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
runWithRequestContext,
|
|
6
6
|
type RequestContext,
|
|
7
7
|
} from "../server/request-context.js";
|
|
8
|
-
import { contextGet, contextSet } from "../context-var.js";
|
|
8
|
+
import { contextGet, contextSet, hasContextVars } from "../context-var.js";
|
|
9
9
|
import {
|
|
10
10
|
createPrerenderContext,
|
|
11
11
|
createStaticContext,
|
|
12
12
|
createReverseFunction,
|
|
13
13
|
} from "./handler-context.js";
|
|
14
|
-
import {
|
|
14
|
+
import { detectPrerenderPassthrough } from "../prerender.js";
|
|
15
15
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
16
16
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
17
17
|
import { loadManifest } from "./manifest.js";
|
|
@@ -59,11 +59,16 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
59
59
|
devMode?: boolean,
|
|
60
60
|
): Promise<{
|
|
61
61
|
segments: SerializedSegmentData[];
|
|
62
|
-
|
|
62
|
+
/** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
|
|
63
|
+
* the producer (where the Flight codec resolves) so the node-side build/dev
|
|
64
|
+
* sinks can persist it without touching the codec. */
|
|
65
|
+
handles: string;
|
|
63
66
|
routeName: string;
|
|
64
67
|
params: Record<string, string>;
|
|
65
68
|
interceptSegments?: SerializedSegmentData[];
|
|
66
|
-
|
|
69
|
+
/** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact;
|
|
70
|
+
* the sinks store it as-is (no longer merge raw records). */
|
|
71
|
+
interceptHandles?: string;
|
|
67
72
|
passthrough?: true;
|
|
68
73
|
} | null> {
|
|
69
74
|
// 1. Find the matching route entry
|
|
@@ -77,6 +82,17 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
77
82
|
// Build RouterContext for loadManifest/traverseBack
|
|
78
83
|
const routerCtx = deps.buildRouterContext();
|
|
79
84
|
|
|
85
|
+
// Passthrough sentinel result: an unknown-param Passthrough route falls
|
|
86
|
+
// through to the live handler at runtime, so no artifact is baked. A fresh
|
|
87
|
+
// object is returned per call (no site mutates or identity-compares it).
|
|
88
|
+
const passthroughResult = () => ({
|
|
89
|
+
segments: [],
|
|
90
|
+
handles: "",
|
|
91
|
+
routeName: matched.routeKey,
|
|
92
|
+
params: matchedParams,
|
|
93
|
+
passthrough: true as const,
|
|
94
|
+
});
|
|
95
|
+
|
|
80
96
|
return runWithRouterContext(routerCtx, async () => {
|
|
81
97
|
// 2. Load the manifest entry tree
|
|
82
98
|
const manifestEntry = await loadManifest(
|
|
@@ -126,7 +142,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
126
142
|
get env() {
|
|
127
143
|
if (buildEnv !== undefined) return buildEnv;
|
|
128
144
|
throw new Error(
|
|
129
|
-
"[
|
|
145
|
+
"[rango] ctx.env is not available during dev-mode getParams(). " +
|
|
130
146
|
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
131
147
|
);
|
|
132
148
|
},
|
|
@@ -140,19 +156,10 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
140
156
|
);
|
|
141
157
|
});
|
|
142
158
|
if (!isKnown) {
|
|
143
|
-
return
|
|
144
|
-
segments: [],
|
|
145
|
-
handles: {},
|
|
146
|
-
routeName: matched.routeKey,
|
|
147
|
-
params: matchedParams,
|
|
148
|
-
passthrough: true as const,
|
|
149
|
-
};
|
|
159
|
+
return passthroughResult();
|
|
150
160
|
}
|
|
151
161
|
// Preserve vars set by getParams() for the render context
|
|
152
|
-
if (
|
|
153
|
-
Object.keys(probeBuildVars).length > 0 ||
|
|
154
|
-
Object.getOwnPropertySymbols(probeBuildVars).length > 0
|
|
155
|
-
) {
|
|
162
|
+
if (hasContextVars(probeBuildVars)) {
|
|
156
163
|
devProbeBuildVars = probeBuildVars;
|
|
157
164
|
}
|
|
158
165
|
} catch (err: any) {
|
|
@@ -160,13 +167,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
160
167
|
// Skip errors are intentional — treat as passthrough.
|
|
161
168
|
// All other errors propagate so dev surfaces them.
|
|
162
169
|
if (err?.name === "Skip") {
|
|
163
|
-
return
|
|
164
|
-
segments: [],
|
|
165
|
-
handles: {},
|
|
166
|
-
routeName: matched.routeKey,
|
|
167
|
-
params: matchedParams,
|
|
168
|
-
passthrough: true as const,
|
|
169
|
-
};
|
|
170
|
+
return passthroughResult();
|
|
170
171
|
}
|
|
171
172
|
throw err;
|
|
172
173
|
}
|
|
@@ -206,11 +207,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
206
207
|
header: () => {},
|
|
207
208
|
setStatus: () => {},
|
|
208
209
|
_setStatus: () => {},
|
|
210
|
+
_rotateStateCookie: () => {},
|
|
211
|
+
_setKeepCacheDirective: () => {},
|
|
209
212
|
use: (() => {
|
|
210
213
|
throw new Error("use() not available during pre-rendering");
|
|
211
214
|
}) as any,
|
|
212
215
|
method: "GET",
|
|
213
216
|
_handleStore: handleStore,
|
|
217
|
+
_requestTags: new Set<string>(),
|
|
214
218
|
waitUntil: () => {},
|
|
215
219
|
onResponse: () => {},
|
|
216
220
|
_onResponseCallbacks: [],
|
|
@@ -256,17 +260,13 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
256
260
|
{ skipLoaders: true },
|
|
257
261
|
);
|
|
258
262
|
|
|
259
|
-
// 9. Detect passthrough sentinel: handler returned ctx.passthrough()
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
params: matchedParams,
|
|
267
|
-
passthrough: true as const,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
263
|
+
// 9. Detect passthrough sentinel: handler returned ctx.passthrough().
|
|
264
|
+
// When the route declares loading(), the handler result is deferred so the
|
|
265
|
+
// component is a thenable resolving to the sentinel — detectPrerenderPassthrough
|
|
266
|
+
// resolves thenables before testing (a sync check would miss it and bake a
|
|
267
|
+
// corrupt artifact).
|
|
268
|
+
if (await detectPrerenderPassthrough(allSegments)) {
|
|
269
|
+
return passthroughResult();
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// 10. Filter out any loader segments (belt-and-suspenders)
|
|
@@ -278,16 +278,22 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
278
278
|
|
|
279
279
|
// 12. Serialize segments using the cache serializer
|
|
280
280
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
281
|
+
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
281
282
|
const serializedSegments = await serializeSegments(nonLoaderSegments);
|
|
282
283
|
|
|
283
|
-
// 13. Collect handle data per segment (skip segments with no handle data)
|
|
284
|
-
|
|
284
|
+
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
285
|
+
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
286
|
+
// handle values survive the JSON-serialized build artifact / dev wire —
|
|
287
|
+
// the same fix the runtime cache uses. Encode happens here, in the RSC
|
|
288
|
+
// environment where the codec resolves; the node-side sinks only persist.
|
|
289
|
+
const handlesRecord: Record<string, SegmentHandleData> = {};
|
|
285
290
|
for (const seg of nonLoaderSegments) {
|
|
286
291
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
287
292
|
if (Object.keys(segHandles).length > 0) {
|
|
288
|
-
|
|
293
|
+
handlesRecord[seg.id] = segHandles;
|
|
289
294
|
}
|
|
290
295
|
}
|
|
296
|
+
const handles = await encodeHandles(handlesRecord);
|
|
291
297
|
|
|
292
298
|
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
293
299
|
const routeName = matched.routeKey;
|
|
@@ -297,7 +303,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
297
303
|
// evaluation -- we pre-render all intercepts unconditionally and let
|
|
298
304
|
// runtime matching decide which to serve.
|
|
299
305
|
let interceptSegments: SerializedSegmentData[] | undefined;
|
|
300
|
-
let interceptHandles:
|
|
306
|
+
let interceptHandles: string | undefined;
|
|
301
307
|
|
|
302
308
|
const foundIntercepts: {
|
|
303
309
|
intercept: InterceptEntry;
|
|
@@ -305,24 +311,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
305
311
|
}[] = [];
|
|
306
312
|
let current: EntryData | null = manifestEntry;
|
|
307
313
|
while (current) {
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
// Flatten the entry and its sibling layouts into one source list, the
|
|
315
|
+
// same traversal findInterceptForRoute uses; the build keeps ALL matches
|
|
316
|
+
// (not just the innermost) and skips when(). intercept/layout are
|
|
317
|
+
// non-optional arrays, so empty ones are a no-op here.
|
|
318
|
+
for (const source of [current, ...current.layout]) {
|
|
319
|
+
for (const ic of source.intercept) {
|
|
310
320
|
if (ic.routeName === matched.routeKey) {
|
|
311
|
-
foundIntercepts.push({ intercept: ic, entry:
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (current.layout && current.layout.length > 0) {
|
|
316
|
-
for (const siblingLayout of current.layout) {
|
|
317
|
-
if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
|
|
318
|
-
for (const ic of siblingLayout.intercept) {
|
|
319
|
-
if (ic.routeName === matched.routeKey) {
|
|
320
|
-
foundIntercepts.push({
|
|
321
|
-
intercept: ic,
|
|
322
|
-
entry: siblingLayout,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
}
|
|
321
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
326
322
|
}
|
|
327
323
|
}
|
|
328
324
|
}
|
|
@@ -333,6 +329,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
333
329
|
const interceptResolvedSegments: typeof nonLoaderSegments = [];
|
|
334
330
|
|
|
335
331
|
for (const { intercept, entry: parentEntry } of foundIntercepts) {
|
|
332
|
+
// setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
|
|
333
|
+
// resolveAllSegments pass left it on the route's segment id, so pin it
|
|
334
|
+
// to THIS intercept's slot id before resolving the handler/layout --
|
|
335
|
+
// otherwise the intercept's ctx.use() handle pushes land in the wrong
|
|
336
|
+
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
337
|
+
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
338
|
+
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
339
|
+
// pinning).
|
|
340
|
+
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
341
|
+
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
342
|
+
interceptSegmentId;
|
|
343
|
+
|
|
336
344
|
// Resolve handler
|
|
337
345
|
const handlerRaw =
|
|
338
346
|
typeof intercept.handler === "function"
|
|
@@ -359,7 +367,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
359
367
|
}
|
|
360
368
|
|
|
361
369
|
interceptResolvedSegments.push({
|
|
362
|
-
id:
|
|
370
|
+
id: interceptSegmentId,
|
|
363
371
|
namespace: `intercept:${intercept.routeName}`,
|
|
364
372
|
type: "parallel" as const,
|
|
365
373
|
index: 0,
|
|
@@ -379,13 +387,20 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
379
387
|
interceptSegments = await serializeSegments(
|
|
380
388
|
interceptResolvedSegments,
|
|
381
389
|
);
|
|
382
|
-
|
|
390
|
+
const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
|
|
383
391
|
for (const seg of interceptResolvedSegments) {
|
|
384
392
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
385
393
|
if (Object.keys(segHandles).length > 0) {
|
|
386
|
-
|
|
394
|
+
interceptHandlesRecord[seg.id] = segHandles;
|
|
387
395
|
}
|
|
388
396
|
}
|
|
397
|
+
// The intercept artifact serves main + intercept segments together, so
|
|
398
|
+
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
399
|
+
// records — they store this pre-encoded string as-is).
|
|
400
|
+
interceptHandles = await encodeHandles({
|
|
401
|
+
...handlesRecord,
|
|
402
|
+
...interceptHandlesRecord,
|
|
403
|
+
});
|
|
389
404
|
}
|
|
390
405
|
}
|
|
391
406
|
|
|
@@ -414,7 +429,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
414
429
|
routeName?: string,
|
|
415
430
|
buildEnv?: TEnv,
|
|
416
431
|
devMode?: boolean,
|
|
417
|
-
): Promise<{ encoded: string; handles:
|
|
432
|
+
): Promise<{ encoded: string; handles: string } | null> {
|
|
418
433
|
const syntheticUrl = new URL("http://prerender/");
|
|
419
434
|
const syntheticRequest = new Request(syntheticUrl);
|
|
420
435
|
|
|
@@ -442,11 +457,14 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
442
457
|
header: () => {},
|
|
443
458
|
setStatus: () => {},
|
|
444
459
|
_setStatus: () => {},
|
|
460
|
+
_rotateStateCookie: () => {},
|
|
461
|
+
_setKeepCacheDirective: () => {},
|
|
445
462
|
use: (() => {
|
|
446
463
|
throw new Error("use() not available during static pre-rendering");
|
|
447
464
|
}) as any,
|
|
448
465
|
method: "GET",
|
|
449
466
|
_handleStore: handleStore,
|
|
467
|
+
_requestTags: new Set<string>(),
|
|
450
468
|
waitUntil: () => {},
|
|
451
469
|
onResponse: () => {},
|
|
452
470
|
_onResponseCallbacks: [],
|
|
@@ -479,23 +497,41 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
479
497
|
setupBuildUse(buildCtx);
|
|
480
498
|
|
|
481
499
|
const raw = await handler(buildCtx);
|
|
482
|
-
|
|
500
|
+
|
|
501
|
+
// Static handlers must return a ReactNode. A returned Response (e.g. an
|
|
502
|
+
// accidental redirect()) would otherwise be serialized as a corrupt build
|
|
503
|
+
// artifact; fail loudly instead. The fresh/revalidation paths route handler
|
|
504
|
+
// results through handleHandlerResult, which throws Responses; the static
|
|
505
|
+
// build path bypasses that, so guard here.
|
|
506
|
+
if (raw instanceof Response) {
|
|
507
|
+
throw new TypeError(
|
|
508
|
+
`Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
|
|
509
|
+
`Responses (redirects, file responses) are not supported during static pre-rendering.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
483
512
|
|
|
484
513
|
const segment: ResolvedSegment = {
|
|
485
514
|
id: handlerId,
|
|
486
515
|
namespace: handlerId,
|
|
487
516
|
type: "layout",
|
|
488
517
|
index: 0,
|
|
489
|
-
component,
|
|
518
|
+
component: raw,
|
|
490
519
|
params: {},
|
|
491
520
|
belongsToRoute: false,
|
|
492
521
|
};
|
|
493
522
|
|
|
494
523
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
524
|
+
const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
|
|
495
525
|
const [serialized] = await serializeSegments([segment]);
|
|
496
526
|
|
|
497
|
-
// Collect handle data pushed during rendering
|
|
498
|
-
|
|
527
|
+
// Collect handle data pushed during rendering and Flight-encode it (so
|
|
528
|
+
// Promise/ReactNode handle values survive the JSON build artifact). "" when
|
|
529
|
+
// nothing was pushed.
|
|
530
|
+
const segHandles = handleStore.getDataForSegment(handlerId);
|
|
531
|
+
const handles =
|
|
532
|
+
Object.keys(segHandles).length > 0
|
|
533
|
+
? await encodeHandleValue(segHandles)
|
|
534
|
+
: "";
|
|
499
535
|
|
|
500
536
|
return { encoded: serialized.encoded, handles };
|
|
501
537
|
});
|
|
@@ -67,9 +67,11 @@ export async function previewMatch<TEnv = any>(
|
|
|
67
67
|
responseType: negotiation.responseType,
|
|
68
68
|
handler: negotiation.handler,
|
|
69
69
|
params: matched.params,
|
|
70
|
-
negotiated: true,
|
|
71
70
|
manifestEntry: negotiation.manifestEntry,
|
|
72
71
|
routeKey: matched.routeKey,
|
|
72
|
+
// omitted unless a variant negotiated, preserving the prior public
|
|
73
|
+
// shape (absent for plain response routes, not negotiated:false)
|
|
74
|
+
...(negotiation.negotiated ? { negotiated: true } : {}),
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
|