@rangojs/router 0.0.0-experimental.d20dd405 → 0.0.0-experimental.d98a8e9d
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 +8 -8
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +917 -500
- package/package.json +55 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +196 -21
- package/skills/host-router/SKILL.md +45 -20
- package/skills/intercept/SKILL.md +1 -4
- package/skills/layout/SKILL.md +4 -7
- package/skills/links/SKILL.md +22 -10
- package/skills/loader/SKILL.md +166 -23
- package/skills/middleware/SKILL.md +13 -9
- package/skills/migrate-nextjs/SKILL.md +1 -1
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +3 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +9 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +53 -41
- package/skills/testing/SKILL.md +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -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 +34 -5
- package/skills/view-transitions/SKILL.md +85 -3
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/event-controller.ts +42 -66
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +9 -67
- package/src/browser/navigation-client.ts +68 -83
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +8 -16
- package/src/browser/prefetch/cache.ts +58 -27
- package/src/browser/prefetch/fetch.ts +92 -33
- package/src/browser/react/NavigationProvider.tsx +55 -65
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-params.ts +3 -4
- package/src/browser/react/use-reverse.ts +19 -12
- package/src/browser/react/use-router.ts +14 -1
- package/src/browser/response-adapter.ts +32 -1
- package/src/browser/rsc-router.tsx +35 -16
- package/src/browser/scroll-restoration.ts +30 -25
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +2 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +43 -0
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +49 -6
- package/src/client.tsx +9 -30
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +136 -20
- package/src/index.rsc.ts +7 -6
- package/src/index.ts +14 -8
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +16 -13
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +238 -263
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +37 -14
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +54 -6
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +35 -16
- package/src/router/loader-resolution.ts +79 -36
- package/src/router/manifest.ts +19 -6
- package/src/router/match-handlers.ts +62 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +32 -30
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +1 -1
- package/src/router/middleware.ts +46 -78
- package/src/router/pattern-matching.ts +15 -2
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +43 -1
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +19 -6
- package/src/router/segment-resolution/revalidation.ts +19 -6
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +22 -3
- package/src/router/types.ts +8 -0
- package/src/router.ts +51 -28
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +20 -65
- package/src/rsc/helpers.ts +22 -2
- package/src/rsc/index.ts +1 -1
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +43 -60
- package/src/rsc/rsc-rendering.ts +27 -53
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +13 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +2 -2
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +64 -49
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +57 -9
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -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 +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -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 +285 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +56 -11
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +6 -3
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +75 -72
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +19 -25
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +3 -7
- package/src/vite/plugins/client-ref-hashing.ts +12 -1
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
- package/src/vite/plugins/expose-action-id.ts +2 -2
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-internal-ids.ts +47 -67
- package/src/vite/plugins/performance-tracks.ts +12 -16
- package/src/vite/plugins/use-cache-transform.ts +13 -11
- package/src/vite/plugins/version-injector.ts +2 -12
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +67 -15
- package/src/vite/router-discovery.ts +208 -63
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +8 -59
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
} from "../server/request-context.js";
|
|
14
14
|
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
15
15
|
import { appendMetric } from "../router/metrics.js";
|
|
16
|
-
import { getSSRSetup } from "./ssr-setup.js";
|
|
16
|
+
import { getSSRSetup, isRscRequest } from "./ssr-setup.js";
|
|
17
17
|
import type { RscPayload } from "./types.js";
|
|
18
|
+
import type { MatchResult } from "../types.js";
|
|
18
19
|
import {
|
|
19
20
|
createResponseWithMergedHeaders,
|
|
20
21
|
createSimpleRedirectResponse,
|
|
@@ -35,6 +36,28 @@ export async function handleRscRendering<TEnv>(
|
|
|
35
36
|
let payload: RscPayload;
|
|
36
37
|
let hasInterceptSlots = false;
|
|
37
38
|
|
|
39
|
+
// Shared by the partial-fallback and full-render paths. The partial-success
|
|
40
|
+
// payload below is intentionally different (omits rootLayout/theme, adds slots).
|
|
41
|
+
const buildFullPayload = (m: MatchResult): RscPayload => ({
|
|
42
|
+
metadata: {
|
|
43
|
+
pathname: url.pathname,
|
|
44
|
+
routerId: ctx.router.id,
|
|
45
|
+
basename: ctx.router.basename,
|
|
46
|
+
segments: m.segments,
|
|
47
|
+
matched: m.matched,
|
|
48
|
+
diff: m.diff,
|
|
49
|
+
resolvedIds: m.resolvedIds,
|
|
50
|
+
params: m.params,
|
|
51
|
+
isPartial: false,
|
|
52
|
+
rootLayout: ctx.router.rootLayout,
|
|
53
|
+
handles: handleStore.stream(),
|
|
54
|
+
version: ctx.version,
|
|
55
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
56
|
+
themeConfig: ctx.router.themeConfig,
|
|
57
|
+
initialTheme: reqCtx.theme,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
38
61
|
if (isPartial) {
|
|
39
62
|
// Partial render (navigation)
|
|
40
63
|
const result = await ctx.router.matchPartial(request, { env });
|
|
@@ -51,25 +74,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
51
74
|
return createSimpleRedirectResponse(match.redirect);
|
|
52
75
|
}
|
|
53
76
|
|
|
54
|
-
payload =
|
|
55
|
-
metadata: {
|
|
56
|
-
pathname: url.pathname,
|
|
57
|
-
routerId: ctx.router.id,
|
|
58
|
-
basename: ctx.router.basename,
|
|
59
|
-
segments: match.segments,
|
|
60
|
-
matched: match.matched,
|
|
61
|
-
diff: match.diff,
|
|
62
|
-
resolvedIds: match.resolvedIds,
|
|
63
|
-
params: match.params,
|
|
64
|
-
isPartial: false,
|
|
65
|
-
rootLayout: ctx.router.rootLayout,
|
|
66
|
-
handles: handleStore.stream(),
|
|
67
|
-
version: ctx.version,
|
|
68
|
-
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
69
|
-
themeConfig: ctx.router.themeConfig,
|
|
70
|
-
initialTheme: reqCtx.theme,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
77
|
+
payload = buildFullPayload(match);
|
|
73
78
|
} else {
|
|
74
79
|
setRequestContextParams(result.params, result.routeName);
|
|
75
80
|
|
|
@@ -135,28 +140,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
135
140
|
{ headers: { "Content-Type": "application/json" } },
|
|
136
141
|
);
|
|
137
142
|
} else {
|
|
138
|
-
payload =
|
|
139
|
-
// Initial SSR can reconstruct the tree from segments + rootLayout,
|
|
140
|
-
// so we omit root to avoid sending the same structure twice.
|
|
141
|
-
|
|
142
|
-
metadata: {
|
|
143
|
-
pathname: url.pathname,
|
|
144
|
-
routerId: ctx.router.id,
|
|
145
|
-
basename: ctx.router.basename,
|
|
146
|
-
segments: match.segments,
|
|
147
|
-
matched: match.matched,
|
|
148
|
-
diff: match.diff,
|
|
149
|
-
resolvedIds: match.resolvedIds,
|
|
150
|
-
params: match.params,
|
|
151
|
-
isPartial: false,
|
|
152
|
-
rootLayout: ctx.router.rootLayout,
|
|
153
|
-
handles: handleStore.stream(),
|
|
154
|
-
version: ctx.version,
|
|
155
|
-
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
156
|
-
themeConfig: ctx.router.themeConfig,
|
|
157
|
-
initialTheme: reqCtx.theme,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
143
|
+
payload = buildFullPayload(match);
|
|
160
144
|
}
|
|
161
145
|
}
|
|
162
146
|
|
|
@@ -190,17 +174,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
190
174
|
rscSerializeDur,
|
|
191
175
|
);
|
|
192
176
|
|
|
193
|
-
|
|
194
|
-
// Partial requests (_rsc_partial) are always RSC -- they come from client-side
|
|
195
|
-
// navigation or prefetch fetch(). We cannot rely on Accept alone since some
|
|
196
|
-
// browsers may send Accept: text/html for non-HTML requests.
|
|
197
|
-
const isRscRequest =
|
|
198
|
-
isPartial ||
|
|
199
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
200
|
-
!url.searchParams.has("__html")) ||
|
|
201
|
-
url.searchParams.has("__rsc");
|
|
202
|
-
|
|
203
|
-
if (isRscRequest) {
|
|
177
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
204
178
|
const renderDur = performance.now() - renderStart;
|
|
205
179
|
appendMetric(metricsStore, "render:total", renderStart, renderDur);
|
|
206
180
|
const rscHeaders: Record<string, string> = {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createResponseWithMergedHeaders,
|
|
9
9
|
carryOverRedirectHeaders,
|
|
10
10
|
} from "./helpers.js";
|
|
11
|
+
import { isRedirectResponse } from "../response-utils.js";
|
|
11
12
|
|
|
12
13
|
// W3 -----------------------------------------------------------------------
|
|
13
14
|
|
|
@@ -18,16 +19,14 @@ import {
|
|
|
18
19
|
*/
|
|
19
20
|
export function extractRedirectResponse(value: unknown): Response | null {
|
|
20
21
|
if (!(value instanceof Response)) return null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
22
|
+
if (!isRedirectResponse(value)) return null;
|
|
23
|
+
const location = value.headers.get("Location")!;
|
|
24
|
+
const redirect = createResponseWithMergedHeaders(null, {
|
|
25
|
+
status: value.status,
|
|
26
|
+
headers: { Location: location },
|
|
27
|
+
});
|
|
28
|
+
carryOverRedirectHeaders(value, redirect);
|
|
29
|
+
return redirect;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
package/src/rsc/server-action.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
hasBodyContent,
|
|
28
28
|
createResponseWithMergedHeaders,
|
|
29
29
|
createSimpleRedirectResponse,
|
|
30
|
-
|
|
30
|
+
interceptRedirectForPartial,
|
|
31
31
|
} from "./helpers.js";
|
|
32
32
|
import type { HandlerContext } from "./handler-context.js";
|
|
33
33
|
|
|
@@ -111,49 +111,25 @@ export async function executeServerAction<TEnv>(
|
|
|
111
111
|
loadedAction = await ctx.loadServerAction(actionId);
|
|
112
112
|
const data = await loadedAction!.apply(null, args);
|
|
113
113
|
|
|
114
|
-
// Intercept redirect
|
|
115
|
-
//
|
|
116
|
-
// and the revalidation step would run unnecessarily.
|
|
114
|
+
// Intercept redirect Responses: serializing one as the action returnValue
|
|
115
|
+
// would fail, and revalidation would run needlessly.
|
|
117
116
|
if (data instanceof Response) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (locationState) {
|
|
124
|
-
redirect = ctx.createRedirectFlightResponse(
|
|
125
|
-
redirectUrl,
|
|
126
|
-
resolveLocationStateEntries(locationState),
|
|
127
|
-
);
|
|
128
|
-
} else {
|
|
129
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
130
|
-
}
|
|
131
|
-
carryOverRedirectHeaders(data, redirect);
|
|
132
|
-
return redirect;
|
|
133
|
-
}
|
|
117
|
+
const intercepted = interceptRedirectForPartial(
|
|
118
|
+
data,
|
|
119
|
+
ctx.createRedirectFlightResponse,
|
|
120
|
+
);
|
|
121
|
+
if (intercepted) return intercepted;
|
|
134
122
|
}
|
|
135
123
|
|
|
136
124
|
returnValue = { ok: true, data };
|
|
137
125
|
} catch (error) {
|
|
138
126
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
139
127
|
if (error instanceof Response) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let redirect: Response;
|
|
146
|
-
if (locationState) {
|
|
147
|
-
redirect = ctx.createRedirectFlightResponse(
|
|
148
|
-
redirectUrl,
|
|
149
|
-
resolveLocationStateEntries(locationState),
|
|
150
|
-
);
|
|
151
|
-
} else {
|
|
152
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
153
|
-
}
|
|
154
|
-
carryOverRedirectHeaders(error, redirect);
|
|
155
|
-
return redirect;
|
|
156
|
-
}
|
|
128
|
+
const intercepted = interceptRedirectForPartial(
|
|
129
|
+
error,
|
|
130
|
+
ctx.createRedirectFlightResponse,
|
|
131
|
+
);
|
|
132
|
+
if (intercepted) return intercepted;
|
|
157
133
|
|
|
158
134
|
// Non-redirect Response thrown from action — this will be treated
|
|
159
135
|
// as a regular error and routed to the error boundary. Warn in dev
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -126,3 +126,19 @@ export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
|
126
126
|
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
// Final render-time decision: is the response an RSC stream (vs HTML)? Distinct
|
|
131
|
+
// from mayNeedSSR, which is a conservative pre-classifier (it treats a missing
|
|
132
|
+
// Accept header as needing SSR; this treats it as RSC).
|
|
133
|
+
export function isRscRequest(
|
|
134
|
+
request: Request,
|
|
135
|
+
url: URL,
|
|
136
|
+
isPartial: boolean,
|
|
137
|
+
): boolean {
|
|
138
|
+
return (
|
|
139
|
+
isPartial ||
|
|
140
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
141
|
+
!url.searchParams.has("__html")) ||
|
|
142
|
+
url.searchParams.has("__rsc")
|
|
143
|
+
);
|
|
144
|
+
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ResolvedSegment, SlotState } from "../types.js";
|
|
9
9
|
import type { HandleData } from "../server/handle-store.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
11
11
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -185,7 +185,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
185
185
|
/**
|
|
186
186
|
* The RSC router instance
|
|
187
187
|
*/
|
|
188
|
-
router:
|
|
188
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
191
|
* RSC dependencies from @vitejs/plugin-rsc/rsc.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Runtime-safe detection of a test runner (Vitest), used to decide whether a
|
|
2
|
+
// create*() call with no plugin-injected $$id may fall back to a synthetic id (a
|
|
3
|
+
// bare test) or must fail loud (dev / a real build).
|
|
4
|
+
//
|
|
5
|
+
// `process` is absent in some target runtimes (the browser, certain edge/worker
|
|
6
|
+
// RSC environments), so probe it through `globalThis` with optional chaining —
|
|
7
|
+
// NEVER a bare `process.env.VITEST`, which would ReferenceError before the
|
|
8
|
+
// intended error is thrown. Unlike `process.env.NODE_ENV` (folded by the app's
|
|
9
|
+
// build `define`), `VITEST` is not folded, so this stays a small runtime check;
|
|
10
|
+
// it lives only on the create*() error path (id missing), which never runs in a
|
|
11
|
+
// correct production build.
|
|
12
|
+
//
|
|
13
|
+
// Vitest sets `VITEST` in every test process — the node project and the
|
|
14
|
+
// react-server forks alike (the RSC project forces NODE_ENV=production, so NODE_ENV
|
|
15
|
+
// cannot distinguish it from a real build; `VITEST` can). A real build never sets it.
|
|
16
|
+
export function isUnderTestRunner(): boolean {
|
|
17
|
+
return !!globalThis.process?.env?.VITEST;
|
|
18
|
+
}
|
package/src/search-params.ts
CHANGED
|
@@ -81,11 +81,11 @@ export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
|
81
81
|
// ============================================================================
|
|
82
82
|
|
|
83
83
|
/** Resolve the global route map from RegisteredRoutes or GeneratedRouteMap. */
|
|
84
|
-
type GlobalRouteMap = keyof
|
|
85
|
-
? keyof
|
|
84
|
+
type GlobalRouteMap = keyof Rango.RegisteredRoutes extends never
|
|
85
|
+
? keyof Rango.GeneratedRouteMap extends never
|
|
86
86
|
? Record<string, string>
|
|
87
|
-
:
|
|
88
|
-
:
|
|
87
|
+
: Rango.GeneratedRouteMap
|
|
88
|
+
: Rango.RegisteredRoutes;
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Extract the resolved search params type for a named route.
|
package/src/segment-system.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { createElement, type ReactNode, type ComponentType } from "react";
|
|
|
3
3
|
import { OutletProvider } from "./client.js";
|
|
4
4
|
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
5
5
|
import type { ResolvedSegment, RootLayoutProps } from "./types.js";
|
|
6
|
-
import {
|
|
6
|
+
import { decodeLoaderResults } from "./decode-loader-results.js";
|
|
7
7
|
import { invariant } from "./errors.js";
|
|
8
8
|
import {
|
|
9
9
|
RouteContentWrapper,
|
|
@@ -59,42 +59,6 @@ function restoreParallelLoaderMarkers(
|
|
|
59
59
|
return nextSegments ?? segments;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/**
|
|
63
|
-
* Resolve loader data from raw results, unwrapping LoaderDataResult wrappers
|
|
64
|
-
*/
|
|
65
|
-
function resolveLoaderData(
|
|
66
|
-
resolvedData: any[],
|
|
67
|
-
loaderIds: string[],
|
|
68
|
-
): { loaderData: Record<string, any>; errorFallback: ReactNode } {
|
|
69
|
-
const loaderData: Record<string, any> = {};
|
|
70
|
-
let errorFallback: ReactNode = null;
|
|
71
|
-
|
|
72
|
-
for (let i = 0; i < loaderIds.length; i++) {
|
|
73
|
-
const id = loaderIds[i];
|
|
74
|
-
const result = resolvedData[i];
|
|
75
|
-
|
|
76
|
-
if (!isLoaderDataResult(result)) {
|
|
77
|
-
// Legacy format - direct data
|
|
78
|
-
loaderData[id] = result;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (result.ok) {
|
|
83
|
-
loaderData[id] = result.data;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Error case
|
|
88
|
-
if (result.fallback) {
|
|
89
|
-
errorFallback = result.fallback;
|
|
90
|
-
} else {
|
|
91
|
-
throw new Error(result.error.message);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { loaderData, errorFallback };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
62
|
/**
|
|
99
63
|
* Options for renderSegments
|
|
100
64
|
*/
|
|
@@ -135,8 +99,11 @@ function createViewTransitionBoundary(
|
|
|
135
99
|
transition: NonNullable<ResolvedSegment["transition"]>,
|
|
136
100
|
children: ReactNode,
|
|
137
101
|
): ReactNode {
|
|
102
|
+
// `viewTransition` is a router-specific flag (boundary opt-out), not a React
|
|
103
|
+
// <ViewTransition> prop — strip it so it never reaches React.
|
|
104
|
+
const { viewTransition: _viewTransition, ...vtProps } = transition;
|
|
138
105
|
return createElement(ReactViewTransition, {
|
|
139
|
-
...
|
|
106
|
+
...vtProps,
|
|
140
107
|
children,
|
|
141
108
|
});
|
|
142
109
|
}
|
|
@@ -252,6 +219,25 @@ export async function renderSegments(
|
|
|
252
219
|
}
|
|
253
220
|
// Separate segments by type, passing intercept segments for explicit injection
|
|
254
221
|
const tree = segmentTreeWalk(normalizedSegments, normalizedInterceptSegments);
|
|
222
|
+
|
|
223
|
+
// A route is "in a transition scope" when its own segment OR any layout in
|
|
224
|
+
// its matched chain declares transition(). Both transition() forms land here:
|
|
225
|
+
// the per-route item form sets transition on the route entry, and the block
|
|
226
|
+
// wrapper form sets it on a transparent ancestor layout (dsl-helpers.ts). When
|
|
227
|
+
// in scope, the route and its route-owned layouts use param-agnostic keys so a
|
|
228
|
+
// same-route navigation reconciles (holds content) instead of remounting. The
|
|
229
|
+
// value is a static property of the route's position in the tree, so it is the
|
|
230
|
+
// same on every render of that route (SSR, navigation, action) — the keys
|
|
231
|
+
// never drift. Cross-route navigation still remounts: different routes have
|
|
232
|
+
// different segment ids regardless of transition scope.
|
|
233
|
+
const inTransitionScope = normalizedSegments.some(
|
|
234
|
+
(s) =>
|
|
235
|
+
s.transition != null &&
|
|
236
|
+
(s.type === "layout" ||
|
|
237
|
+
s.type === "route" ||
|
|
238
|
+
s.type === "error" ||
|
|
239
|
+
s.type === "notFound"),
|
|
240
|
+
);
|
|
255
241
|
// Render content segments as siblings
|
|
256
242
|
let content: ReactNode = null;
|
|
257
243
|
for (const node of tree) {
|
|
@@ -264,17 +250,31 @@ export async function renderSegments(
|
|
|
264
250
|
);
|
|
265
251
|
const { component, id, params, loading } = node.segment;
|
|
266
252
|
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
253
|
+
// Param-agnostic keys are opt-in via the transition() DSL (see
|
|
254
|
+
// inTransitionScope above). A route (and its route-owned layouts) inside a
|
|
255
|
+
// transition scope drops the param from its key, so navigating between two
|
|
256
|
+
// param values of the SAME route (e.g. /product/1 -> /product/2) reconciles
|
|
257
|
+
// the route subtree instead of remounting it. Combined with the
|
|
258
|
+
// startTransition wrap that shouldStartViewTransition already applies to
|
|
259
|
+
// transition routes (browser/partial-update.ts), the previous content stays
|
|
260
|
+
// on screen while the new loaders resolve (stale-while-revalidate) instead
|
|
261
|
+
// of flashing the loading skeleton. This works on stable React; experimental
|
|
262
|
+
// React adds the animated <ViewTransition> cross-fade on top.
|
|
263
|
+
//
|
|
264
|
+
// Outside a transition scope the key stays param-bearing and the route
|
|
265
|
+
// remounts on param change (the default: a fresh skeleton and fresh
|
|
266
|
+
// component state).
|
|
267
|
+
//
|
|
268
|
+
// error/notFound always keep param-bearing keys: createErrorSegment reuses
|
|
269
|
+
// the boundary layout's shortCode as the error segment id (router/
|
|
270
|
+
// error-handling.ts), so a param-agnostic error key could collide with that
|
|
271
|
+
// layout's key within the same render.
|
|
273
272
|
const includeParams =
|
|
274
|
-
node.segment.type === "route" ||
|
|
275
273
|
node.segment.type === "error" ||
|
|
276
274
|
node.segment.type === "notFound" ||
|
|
277
|
-
(node.segment.type === "
|
|
275
|
+
((node.segment.type === "route" ||
|
|
276
|
+
(node.segment.type === "layout" && node.segment.belongsToRoute)) &&
|
|
277
|
+
!inTransitionScope);
|
|
278
278
|
|
|
279
279
|
const paramStr =
|
|
280
280
|
includeParams && params && Object.keys(params).length > 0
|
|
@@ -322,12 +322,25 @@ export async function renderSegments(
|
|
|
322
322
|
// subtree update on the layout-level VT — which would otherwise make
|
|
323
323
|
// React's commit walker fire `document.startViewTransition` and apply
|
|
324
324
|
// view-transition-names to the underlying main subtree (cover/title/etc.).
|
|
325
|
+
//
|
|
326
|
+
// `transition.viewTransition === false` opts out of the router-owned
|
|
327
|
+
// boundary only. Driving (the startTransition wrap in browser/partial-update.ts
|
|
328
|
+
// and the param-agnostic key/hold below) keys off transition *presence*, not
|
|
329
|
+
// this flag, so a boundary-less transition still holds content and lets
|
|
330
|
+
// consumer-placed <ViewTransition> elements animate. The global
|
|
331
|
+
// createRouter({ viewTransition }) default is resolved into this field
|
|
332
|
+
// during segment resolution (only `false` is stamped; unset/"auto" is left
|
|
333
|
+
// as-is and means "wrap"), so this gate needs no router-option threading.
|
|
325
334
|
let outletContent: ReactNode =
|
|
326
335
|
node.segment.type === "layout" ? content : null;
|
|
327
336
|
|
|
328
337
|
const transition = node.segment.transition;
|
|
329
338
|
|
|
330
|
-
if (
|
|
339
|
+
if (
|
|
340
|
+
ReactViewTransition &&
|
|
341
|
+
transition &&
|
|
342
|
+
transition.viewTransition !== false
|
|
343
|
+
) {
|
|
331
344
|
if (node.segment.type === "layout") {
|
|
332
345
|
outletContent = wrapDefaultOutletContent(outletContent, transition);
|
|
333
346
|
} else {
|
|
@@ -337,13 +350,15 @@ export async function renderSegments(
|
|
|
337
350
|
|
|
338
351
|
// Prepare loader data if there are loaders
|
|
339
352
|
const loaderIds = loaderEntries.map((loader) => loader.loaderId!);
|
|
340
|
-
const loaderDataPromise = getMemoizedLoaderPromise(loaderEntries);
|
|
341
353
|
|
|
342
354
|
// Use LoaderBoundary when loading is defined to maintain consistent tree structure
|
|
343
355
|
// This ensures cached segments (which may not have loader segments) have the same
|
|
344
356
|
// tree structure as fresh segments, preventing React remounts
|
|
345
357
|
// If forceAwait or isAction is set, pre-resolve promises so LoaderBoundary won't suspend
|
|
346
358
|
if (loading !== undefined && loading !== null) {
|
|
359
|
+
// Aggregate built here only — the loaderless and no-loading branches don't
|
|
360
|
+
// read it (the latter builds its own per-parallel promises).
|
|
361
|
+
const loaderDataPromise = getMemoizedLoaderPromise(loaderEntries);
|
|
347
362
|
content = createElement(LoaderBoundary, {
|
|
348
363
|
key: `loader-boundary-${key}`,
|
|
349
364
|
loaderDataPromise:
|
|
@@ -387,7 +402,7 @@ export async function renderSegments(
|
|
|
387
402
|
)
|
|
388
403
|
: Promise.resolve([]);
|
|
389
404
|
const resolvedData = await layoutLoaderDataPromise;
|
|
390
|
-
const { loaderData, errorFallback } =
|
|
405
|
+
const { loaderData, errorFallback } = decodeLoaderResults(
|
|
391
406
|
resolvedData,
|
|
392
407
|
layoutLoaderIds,
|
|
393
408
|
);
|