@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
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 +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +188 -93
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -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
|
|
@@ -208,10 +184,13 @@ export async function executeServerAction<TEnv>(
|
|
|
208
184
|
const payload: RscPayload = {
|
|
209
185
|
metadata: {
|
|
210
186
|
pathname: url.pathname,
|
|
187
|
+
routerId: ctx.router.id,
|
|
211
188
|
segments: errorResult.segments,
|
|
212
189
|
isPartial: true,
|
|
213
190
|
matched: errorResult.matched,
|
|
214
191
|
diff: errorResult.diff,
|
|
192
|
+
resolvedIds: errorResult.resolvedIds,
|
|
193
|
+
params: errorResult.params,
|
|
215
194
|
isError: true,
|
|
216
195
|
handles: handleStore.stream(),
|
|
217
196
|
version: ctx.version,
|
|
@@ -225,6 +204,9 @@ export async function executeServerAction<TEnv>(
|
|
|
225
204
|
|
|
226
205
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
227
206
|
temporaryReferences,
|
|
207
|
+
onError: (error: unknown) => {
|
|
208
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
209
|
+
},
|
|
228
210
|
});
|
|
229
211
|
|
|
230
212
|
return createResponseWithMergedHeaders(rscStream, {
|
|
@@ -314,10 +296,13 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
314
296
|
const payload: RscPayload = {
|
|
315
297
|
metadata: {
|
|
316
298
|
pathname: url.pathname,
|
|
299
|
+
routerId: ctx.router.id,
|
|
317
300
|
segments: matchResult.segments,
|
|
318
301
|
isPartial: true,
|
|
319
302
|
matched: matchResult.matched,
|
|
320
303
|
diff: matchResult.diff,
|
|
304
|
+
resolvedIds: matchResult.resolvedIds,
|
|
305
|
+
params: matchResult.params,
|
|
321
306
|
slots: matchResult.slots,
|
|
322
307
|
handles: handleStore.stream(),
|
|
323
308
|
version: ctx.version,
|
|
@@ -330,6 +315,9 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
330
315
|
const renderStart = performance.now();
|
|
331
316
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
332
317
|
temporaryReferences,
|
|
318
|
+
onError: (error: unknown) => {
|
|
319
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
320
|
+
},
|
|
333
321
|
});
|
|
334
322
|
const rscSerializeDur = performance.now() - renderStart;
|
|
335
323
|
// This measures synchronous stream creation, not end-to-end stream consumption.
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function getSSRSetup<TEnv>(
|
|
|
77
77
|
url: URL,
|
|
78
78
|
metricsStore: MetricsStore | undefined,
|
|
79
79
|
): Promise<SSRSetup> {
|
|
80
|
-
const early = _getRequestContext()?.
|
|
80
|
+
const early = _getRequestContext()?._variables?.[SSR_SETUP_VAR] as
|
|
81
81
|
| Promise<SSRSetup>
|
|
82
82
|
| undefined;
|
|
83
83
|
if (early) return early;
|
|
@@ -98,7 +98,7 @@ export function getSSRSetup<TEnv>(
|
|
|
98
98
|
* the isRscRequest decision in rsc-rendering.ts.
|
|
99
99
|
*
|
|
100
100
|
* Note: response/mime routes are excluded by the caller — this function
|
|
101
|
-
* runs after
|
|
101
|
+
* runs after classifyRequest() determines the request mode.
|
|
102
102
|
*/
|
|
103
103
|
export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
104
104
|
if (
|
|
@@ -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
|
/**
|
|
@@ -19,10 +19,19 @@ export interface RscPayload {
|
|
|
19
19
|
metadata?: {
|
|
20
20
|
pathname: string;
|
|
21
21
|
segments: ResolvedSegment[];
|
|
22
|
+
/** Router instance ID. When this changes between navigations, the client
|
|
23
|
+
* discards cached segments and does a full tree replacement (app switch). */
|
|
24
|
+
routerId?: string;
|
|
22
25
|
isPartial?: boolean;
|
|
23
26
|
isError?: boolean;
|
|
24
27
|
matched?: string[];
|
|
25
28
|
diff?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* All segment ids re-resolved on the server, including null-component
|
|
31
|
+
* ones excluded from `segments`/`diff`. Drives client-side handle-bucket
|
|
32
|
+
* cleanup. Superset of `diff`. See MatchResult.resolvedIds.
|
|
33
|
+
*/
|
|
34
|
+
resolvedIds?: string[];
|
|
26
35
|
/** Merged route params from the matched route */
|
|
27
36
|
params?: Record<string, string>;
|
|
28
37
|
slots?: Record<string, SlotState>;
|
|
@@ -38,6 +47,8 @@ export interface RscPayload {
|
|
|
38
47
|
themeConfig?: ResolvedThemeConfig | null;
|
|
39
48
|
/** Initial theme from cookie (for SSR hydration) */
|
|
40
49
|
initialTheme?: Theme;
|
|
50
|
+
/** URL prefix for all routes (from createRouter({ basename })). */
|
|
51
|
+
basename?: string;
|
|
41
52
|
/** Whether connection warmup is enabled */
|
|
42
53
|
warmupEnabled?: boolean;
|
|
43
54
|
/** Server-side redirect with optional state (for partial requests) */
|
|
@@ -63,7 +74,10 @@ export interface RSCDependencies {
|
|
|
63
74
|
*/
|
|
64
75
|
renderToReadableStream: <T>(
|
|
65
76
|
payload: T,
|
|
66
|
-
options?: {
|
|
77
|
+
options?: {
|
|
78
|
+
temporaryReferences?: unknown;
|
|
79
|
+
onError?: (error: unknown) => string | void;
|
|
80
|
+
},
|
|
67
81
|
) => ReadableStream<Uint8Array>;
|
|
68
82
|
|
|
69
83
|
/**
|
|
@@ -171,7 +185,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
171
185
|
/**
|
|
172
186
|
* The RSC router instance
|
|
173
187
|
*/
|
|
174
|
-
router:
|
|
188
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
175
189
|
|
|
176
190
|
/**
|
|
177
191
|
* RSC dependencies from @vitejs/plugin-rsc/rsc.
|
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.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable Promise wrappers keyed on the component itself. Objects (React
|
|
5
|
+
* elements, functions, lazy payloads) land in a WeakMap so entries GC when
|
|
6
|
+
* the underlying component is released; primitives (string, number, boolean,
|
|
7
|
+
* null) land in a Map so memoization still applies to text-/null-backed
|
|
8
|
+
* segments like those in partial-update flows. Keeping this cache outside
|
|
9
|
+
* the segment eliminates preservation fields on ResolvedSegment — it survives
|
|
10
|
+
* reconciliation naturally because the component ref is what's stable.
|
|
11
|
+
*
|
|
12
|
+
* Browser-only. On the server each SSR render needs a fresh pending promise
|
|
13
|
+
* so Suspense can emit the loading fallback HTML before content streams. A
|
|
14
|
+
* shared already-resolved promise has `.status === "fulfilled"` attached by
|
|
15
|
+
* React on its first observation — subsequent `use()` calls return
|
|
16
|
+
* synchronously without suspending, so the Suspense fallback never makes it
|
|
17
|
+
* into the initial HTML. Route-definition components share refs across
|
|
18
|
+
* requests, so a global cache would leak tracked state between renders.
|
|
19
|
+
*/
|
|
20
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
21
|
+
const objectContentCache = IS_BROWSER
|
|
22
|
+
? new WeakMap<object, Promise<ReactNode>>()
|
|
23
|
+
: null;
|
|
24
|
+
const primitiveContentCache = IS_BROWSER
|
|
25
|
+
? new Map<unknown, Promise<ReactNode>>()
|
|
26
|
+
: null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return a stable Promise wrapping `component`, memoized on the component ref.
|
|
30
|
+
*
|
|
31
|
+
* A fresh `Promise.resolve(component)` each render would suspend for one
|
|
32
|
+
* microtask and briefly commit the loading fallback inside Suspender — the
|
|
33
|
+
* intercept / parallel-slot flicker this indirection prevents. Reusing the
|
|
34
|
+
* same Promise ref keeps React's `use()` in "known fulfilled" state after
|
|
35
|
+
* the first observation.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function getMemoizedContentPromise(
|
|
40
|
+
component: ReactNode,
|
|
41
|
+
): Promise<ReactNode> {
|
|
42
|
+
if (component instanceof Promise) {
|
|
43
|
+
return component as Promise<ReactNode>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!objectContentCache || !primitiveContentCache) {
|
|
47
|
+
return Promise.resolve(component);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (component !== null && typeof component === "object") {
|
|
51
|
+
const cached = objectContentCache.get(component);
|
|
52
|
+
if (cached) {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
const promise = Promise.resolve(component);
|
|
56
|
+
objectContentCache.set(component, promise);
|
|
57
|
+
return promise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cached = primitiveContentCache.get(component);
|
|
61
|
+
if (cached) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
const promise = Promise.resolve(component);
|
|
65
|
+
primitiveContentCache.set(component, promise);
|
|
66
|
+
return promise;
|
|
67
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ResolvedSegment } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache of aggregate Promise.all results keyed on the first loader's
|
|
5
|
+
* `loaderData` reference. Each entry holds the source refs it was built from
|
|
6
|
+
* plus the resulting Promise/array; lookup scans entries for the matching
|
|
7
|
+
* source array (typically a single entry, since distinct loader groups rarely
|
|
8
|
+
* share a first source). Object first-refs live in a WeakMap (auto-GC);
|
|
9
|
+
* primitive first-refs (strings/numbers/booleans/null) live in a Map so
|
|
10
|
+
* loaders that resolve to primitive data are memoized too — bounded in
|
|
11
|
+
* practice by the application's loader set.
|
|
12
|
+
*
|
|
13
|
+
* Keying externally means reconciliation's fresh segment objects no longer
|
|
14
|
+
* drop memoization — the cache survives as long as the underlying loader
|
|
15
|
+
* segments do, and GC collects entries when those loaders are released
|
|
16
|
+
* (object keys only).
|
|
17
|
+
*
|
|
18
|
+
* Browser-only. On the server each SSR render needs a fresh Promise so
|
|
19
|
+
* Suspense can actually suspend and emit the loading fallback HTML before
|
|
20
|
+
* content streams. A shared already-resolved promise has `.status` attached
|
|
21
|
+
* by React on first `use()`; subsequent observations return synchronously
|
|
22
|
+
* and skip the fallback. The zero-loader case is especially prone because
|
|
23
|
+
* every empty-loader site would otherwise share one promise across requests.
|
|
24
|
+
*/
|
|
25
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
26
|
+
|
|
27
|
+
interface LoaderCacheEntry {
|
|
28
|
+
sources: any[];
|
|
29
|
+
promise: Promise<any[]> | any[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const objectLoaderCache = IS_BROWSER
|
|
33
|
+
? new WeakMap<object, LoaderCacheEntry[]>()
|
|
34
|
+
: null;
|
|
35
|
+
const primitiveLoaderCache = IS_BROWSER
|
|
36
|
+
? new Map<unknown, LoaderCacheEntry[]>()
|
|
37
|
+
: null;
|
|
38
|
+
|
|
39
|
+
// In the browser, a single shared empty aggregate is safe (and desirable) —
|
|
40
|
+
// reusing the same resolved promise keeps React's `use()` in a known-fulfilled
|
|
41
|
+
// state across renders. On the server it would leak `.status = "fulfilled"`
|
|
42
|
+
// across requests and skip the Suspense fallback, so we rebuild on each call.
|
|
43
|
+
const SHARED_EMPTY_LOADER_PROMISE: Promise<any[]> | null = IS_BROWSER
|
|
44
|
+
? Promise.resolve([])
|
|
45
|
+
: null;
|
|
46
|
+
|
|
47
|
+
function hasSameReferences(a: any[], b: any[]): boolean {
|
|
48
|
+
if (a.length !== b.length) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < a.length; i++) {
|
|
52
|
+
if (a[i] !== b[i]) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildLoaderPromise(loaders: ResolvedSegment[]): Promise<any[]> {
|
|
60
|
+
if (loaders.length === 0) {
|
|
61
|
+
return Promise.resolve([]);
|
|
62
|
+
}
|
|
63
|
+
return Promise.all(
|
|
64
|
+
loaders.map((loader) =>
|
|
65
|
+
loader.loaderData instanceof Promise
|
|
66
|
+
? loader.loaderData
|
|
67
|
+
: Promise.resolve(loader.loaderData),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isObjectLike(value: unknown): value is object {
|
|
73
|
+
return (
|
|
74
|
+
value !== null && (typeof value === "object" || typeof value === "function")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Memoize an aggregate Promise.all for a set of loader segments. Reusing the
|
|
80
|
+
* same aggregate across renders — invalidated only when any underlying
|
|
81
|
+
* loader.loaderData ref changes — keeps React's `use()` in "known fulfilled"
|
|
82
|
+
* state and prevents a fresh Promise.all from suspending (and briefly
|
|
83
|
+
* committing the Suspense fallback) on every partial update that doesn't
|
|
84
|
+
* actually change loader data.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export function getMemoizedLoaderPromise(
|
|
89
|
+
loaders: ResolvedSegment[],
|
|
90
|
+
): Promise<any[]> | any[] {
|
|
91
|
+
if (loaders.length === 0) {
|
|
92
|
+
return SHARED_EMPTY_LOADER_PROMISE ?? buildLoaderPromise(loaders);
|
|
93
|
+
}
|
|
94
|
+
if (!objectLoaderCache || !primitiveLoaderCache) {
|
|
95
|
+
return buildLoaderPromise(loaders);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sources = loaders.map((loader) => loader.loaderData);
|
|
99
|
+
const first = sources[0];
|
|
100
|
+
const entries = isObjectLike(first)
|
|
101
|
+
? objectLoaderCache.get(first)
|
|
102
|
+
: primitiveLoaderCache.get(first);
|
|
103
|
+
|
|
104
|
+
if (entries) {
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
if (hasSameReferences(entry.sources, sources)) {
|
|
107
|
+
return entry.promise;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const promise = buildLoaderPromise(loaders);
|
|
113
|
+
const newEntry: LoaderCacheEntry = { sources, promise };
|
|
114
|
+
if (entries) {
|
|
115
|
+
entries.push(newEntry);
|
|
116
|
+
} else if (isObjectLike(first)) {
|
|
117
|
+
objectLoaderCache.set(first, [newEntry]);
|
|
118
|
+
} else {
|
|
119
|
+
primitiveLoaderCache.set(first, [newEntry]);
|
|
120
|
+
}
|
|
121
|
+
return promise;
|
|
122
|
+
}
|