@rangojs/router 0.26.3 → 0.26.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rango.js +1 -1
- package/dist/types/browser/document-flight-stream.d.ts +15 -0
- package/dist/types/browser/rsc-router.d.ts +3 -2
- package/dist/types/deps/html-stream-client.d.ts +1 -1
- package/dist/types/router/dev-reload-handoff.d.ts +12 -0
- package/dist/types/ssr/index.d.ts +9 -7
- package/dist/types/ssr/ssr-root.d.ts +5 -5
- package/dist/types/vite/discovery/state.d.ts +4 -3
- package/dist/types/vite/plugin-types.d.ts +6 -6
- package/dist/vite/index.js +16 -9
- package/package.json +1 -1
- package/skills/loader/SKILL.md +4 -5
- package/skills/react-compiler/SKILL.md +4 -3
- package/src/browser/document-flight-stream.ts +53 -0
- package/src/browser/rsc-router.tsx +3 -2
- package/src/deps/html-stream-client.ts +11 -2
- package/src/router/dev-reload-handoff.ts +37 -0
- package/src/router.ts +23 -11
- package/src/ssr/index.tsx +51 -24
- package/src/ssr/ssr-root.tsx +5 -5
- package/src/vite/discovery/shell-prerender-phase.ts +2 -1
- package/src/vite/discovery/state.ts +4 -3
- package/src/vite/plugin-types.ts +6 -6
- package/src/vite/rango.ts +0 -2
- package/src/vite/router-discovery.ts +5 -3
- package/src/vite/utils/forward-user-plugins.ts +31 -8
package/dist/bin/rango.js
CHANGED
|
@@ -1644,7 +1644,7 @@ var init_package = __esm({
|
|
|
1644
1644
|
"package.json"() {
|
|
1645
1645
|
package_default = {
|
|
1646
1646
|
name: "@rangojs/router",
|
|
1647
|
-
version: "0.26.
|
|
1647
|
+
version: "0.26.5",
|
|
1648
1648
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1649
1649
|
keywords: [
|
|
1650
1650
|
"react",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type FlightChunk = string | Uint8Array;
|
|
2
|
+
/** Where the document's inline scripts push Flight chunks (`self`). */
|
|
3
|
+
export interface FlightDataHost {
|
|
4
|
+
__FLIGHT_DATA?: FlightChunk[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The document's Flight payload as a stream: the server's inline scripts push
|
|
8
|
+
* chunks onto `self.__FLIGHT_DATA` (rsc-html-stream/server, or
|
|
9
|
+
* ssr/inject-rsc-eager.ts), and the browser's own DOMContentLoaded
|
|
10
|
+
* (`isTrusted`) closes it after the last of them ran. rsc-html-stream/client
|
|
11
|
+
* closed on any DOMContentLoaded, so a script re-dispatching the event cut a
|
|
12
|
+
* streaming page short and threw after the load (#1205).
|
|
13
|
+
*/
|
|
14
|
+
export declare function createDocumentFlightStream(doc: Pick<Document, "readyState" | "addEventListener" | "removeEventListener">, host: FlightDataHost): ReadableStream<Uint8Array>;
|
|
15
|
+
export {};
|
|
@@ -7,7 +7,8 @@ import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export interface InitBrowserAppOptions {
|
|
9
9
|
/**
|
|
10
|
-
* RSC stream containing the initial payload
|
|
10
|
+
* RSC stream containing the initial payload
|
|
11
|
+
* (`@rangojs/router/internal/deps/html-stream-client`)
|
|
11
12
|
*/
|
|
12
13
|
rscStream: ReadableStream<Uint8Array>;
|
|
13
14
|
/**
|
|
@@ -133,7 +134,7 @@ export interface RangoProps {
|
|
|
133
134
|
* @example
|
|
134
135
|
* ```tsx
|
|
135
136
|
* import { initBrowserApp, Rango } from "@rangojs/router/browser";
|
|
136
|
-
* import { rscStream } from "
|
|
137
|
+
* import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
|
|
137
138
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
138
139
|
*
|
|
139
140
|
* async function main() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const rscStream: ReadableStream<Uint8Array>;
|
|
@@ -13,6 +13,18 @@ export declare function watchRunnerReloads(events: RunnerReloadEvents | undefine
|
|
|
13
13
|
* @internal
|
|
14
14
|
*/
|
|
15
15
|
export declare function droppedByRunnerReload(): boolean;
|
|
16
|
+
type Serve = () => Promise<Response>;
|
|
17
|
+
/**
|
|
18
|
+
* Serves a request on this router copy, or on the router a runner full reload
|
|
19
|
+
* re-created when the reload dropped this copy before the request or while it
|
|
20
|
+
* ran. What a request imports after the clear is the fresh copy, so one the
|
|
21
|
+
* reload lands inside fails or answers the wrong route: its outcome is
|
|
22
|
+
* discarded. Only GET and HEAD re-run, since a re-run repeats side effects and
|
|
23
|
+
* cannot re-read a body. A reload after the response is returned still splits
|
|
24
|
+
* its stream.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare function serveAcrossRunnerReload(request: Request, serve: Serve, recreated: () => Promise<Serve | undefined>): Promise<Response>;
|
|
16
28
|
/**
|
|
17
29
|
* The router the reload re-created for `routerId`, or undefined when none
|
|
18
30
|
* registered within DEV_RECREATED_ROUTER_WAIT_MS. Importing `sourceFile`
|
|
@@ -169,13 +169,15 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
169
169
|
* the `rango({ progressiveChunkSize })` plugin option here (canonical docs
|
|
170
170
|
* on `RangoBaseOptions.progressiveChunkSize` in vite/plugin-types.ts).
|
|
171
171
|
*
|
|
172
|
-
* When UNSET, createSSRHandler auto-
|
|
173
|
-
* document renders
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
172
|
+
* When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
|
|
173
|
+
* Number.MAX_SAFE_INTEGER for document renders and shell captures whose
|
|
174
|
+
* matched chain has a `loader(Def, { ssr: false })` entry (payload segments
|
|
175
|
+
* carry `awaitBeforeFlush`): the loader was awaited before first flush
|
|
176
|
+
* precisely so its content ships in-place, and React 19.2's Fizz would
|
|
177
|
+
* otherwise move any completed boundary over ~500 bytes to an end-of-stream
|
|
178
|
+
* `<div hidden>` + $RC reveal once the shell saturates the default 12800
|
|
179
|
+
* budget. A shell HIT inherits the raise (prelude and resumed holes), so it
|
|
180
|
+
* matches the live render. An explicit value disables the auto-raise.
|
|
179
181
|
*/
|
|
180
182
|
progressiveChunkSize?: number;
|
|
181
183
|
/**
|
|
@@ -28,11 +28,11 @@ export interface RscPayload {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Deserialize the Flight stream branch into the payload SsrRoot renders,
|
|
31
|
-
* fragment expansion included. Factored out of SsrRoot so createSSRHandler
|
|
32
|
-
* create the promise BEFORE fizz starts and read
|
|
33
|
-
* the fizz call itself (the ssr:false
|
|
34
|
-
* awaiting it there is latency-neutral
|
|
35
|
-
* until this same promise settles.
|
|
31
|
+
* fragment expansion included. Factored out of SsrRoot so createSSRHandler and
|
|
32
|
+
* createShellCaptureHandler can create the promise BEFORE fizz starts and read
|
|
33
|
+
* metadata that must influence the fizz call itself (the ssr:false
|
|
34
|
+
* progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
|
|
35
|
+
* because fizz cannot emit even <html> until this same promise settles.
|
|
36
36
|
*/
|
|
37
37
|
export declare function deserializeSsrPayload(createFromReadableStream: CreateFromReadableStream, rscStream: ReadableStream<Uint8Array>): Promise<RscPayload>;
|
|
38
38
|
/**
|
|
@@ -126,9 +126,10 @@ export interface DiscoveryState {
|
|
|
126
126
|
*/
|
|
127
127
|
userRunnerConfig: import("../utils/forward-user-plugins.js").ForwardedRunnerConfig | undefined;
|
|
128
128
|
/**
|
|
129
|
-
* User resolution plugins (resolveId/load
|
|
130
|
-
* surface, forwarded into the discovery temp
|
|
131
|
-
* resolvers such as vite-tsconfig-paths participate
|
|
129
|
+
* User resolution plugins (resolveId/load, plus import rewriters' transform),
|
|
130
|
+
* stripped to their resolution surface, forwarded into the discovery temp
|
|
131
|
+
* server. Lets third-party resolvers such as vite-tsconfig-paths participate
|
|
132
|
+
* in discovery.
|
|
132
133
|
*/
|
|
133
134
|
userResolvePlugins: import("vite").Plugin[];
|
|
134
135
|
scanFilter: ScanFilter | undefined;
|
|
@@ -165,12 +165,12 @@ interface RangoBaseOptions {
|
|
|
165
165
|
* the CSS. Trade-off: inline content delays later shell bytes behind it,
|
|
166
166
|
* which is why React outlines by default.
|
|
167
167
|
*
|
|
168
|
-
* When UNSET, document renders whose matched chain
|
|
169
|
-
* `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
170
|
-
* loader was awaited before first flush precisely so its content ships
|
|
171
|
-
* in-place, and outlining the boundary it feeds would defeat that.
|
|
172
|
-
*
|
|
173
|
-
*
|
|
168
|
+
* When UNSET, document renders and PPR shell captures whose matched chain
|
|
169
|
+
* has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
170
|
+
* the loader was awaited before first flush precisely so its content ships
|
|
171
|
+
* in-place, and outlining the boundary it feeds would defeat that. A shell
|
|
172
|
+
* HIT inherits the captured value, so it matches the live render. Setting
|
|
173
|
+
* an explicit value disables the auto-raise.
|
|
174
174
|
*
|
|
175
175
|
* Apps with a custom SSR entry set this per-handler via
|
|
176
176
|
* `SSRDependencies.progressiveChunkSize`.
|
package/dist/vite/index.js
CHANGED
|
@@ -7078,7 +7078,7 @@ function resolveVersionsFrom(table, routerId2) {
|
|
|
7078
7078
|
// package.json
|
|
7079
7079
|
var package_default = {
|
|
7080
7080
|
name: "@rangojs/router",
|
|
7081
|
-
version: "0.26.
|
|
7081
|
+
version: "0.26.5",
|
|
7082
7082
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7083
7083
|
keywords: [
|
|
7084
7084
|
"react",
|
|
@@ -12227,7 +12227,8 @@ async function runShellPrerenderPhase(s, builder, versionsTable) {
|
|
|
12227
12227
|
prerender: reactDomStatic.prerender ?? reactDomStatic.default?.prerender,
|
|
12228
12228
|
injectRSCPayload: htmlStream.injectRSCPayload,
|
|
12229
12229
|
headScripts: "preinit",
|
|
12230
|
-
// Build-captured shells
|
|
12230
|
+
// Build-captured shells honor the explicit outlining budget, or the
|
|
12231
|
+
// ssr:false auto-raise without one (createShellCaptureHandler): the
|
|
12231
12232
|
// capture value rides the postponed state into every runtime resume().
|
|
12232
12233
|
...s.opts?.progressiveChunkSize !== void 0 && {
|
|
12233
12234
|
progressiveChunkSize: s.opts.progressiveChunkSize
|
|
@@ -12942,8 +12943,13 @@ function createDiscoveryGate(s, debug14) {
|
|
|
12942
12943
|
function isDenied(name) {
|
|
12943
12944
|
return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
|
|
12944
12945
|
}
|
|
12945
|
-
|
|
12946
|
-
|
|
12946
|
+
var IMPORT_REWRITE_PLUGINS = /* @__PURE__ */ new Set([
|
|
12947
|
+
"rolldown-plugin-transform-imports"
|
|
12948
|
+
]);
|
|
12949
|
+
function isForwardable(p) {
|
|
12950
|
+
return Boolean(
|
|
12951
|
+
p.resolveId || p.load || IMPORT_REWRITE_PLUGINS.has(p.name)
|
|
12952
|
+
);
|
|
12947
12953
|
}
|
|
12948
12954
|
function stripToResolutionHooks(p) {
|
|
12949
12955
|
const stripped = { name: p.name };
|
|
@@ -12952,6 +12958,8 @@ function stripToResolutionHooks(p) {
|
|
|
12952
12958
|
stripped.applyToEnvironment = p.applyToEnvironment;
|
|
12953
12959
|
if (p.resolveId) stripped.resolveId = p.resolveId;
|
|
12954
12960
|
if (p.load) stripped.load = p.load;
|
|
12961
|
+
if (IMPORT_REWRITE_PLUGINS.has(p.name))
|
|
12962
|
+
stripped.transform = p.transform;
|
|
12955
12963
|
return stripped;
|
|
12956
12964
|
}
|
|
12957
12965
|
function selectForwardableResolvePlugins(plugins) {
|
|
@@ -12960,7 +12968,7 @@ function selectForwardableResolvePlugins(plugins) {
|
|
|
12960
12968
|
for (const p of plugins) {
|
|
12961
12969
|
const name = p?.name;
|
|
12962
12970
|
if (!name || isDenied(name)) continue;
|
|
12963
|
-
if (!
|
|
12971
|
+
if (!isForwardable(p)) continue;
|
|
12964
12972
|
forwarded.push(stripToResolutionHooks(p));
|
|
12965
12973
|
}
|
|
12966
12974
|
return forwarded;
|
|
@@ -13110,7 +13118,8 @@ async function createTempRscServer(state, options = {}) {
|
|
|
13110
13118
|
}
|
|
13111
13119
|
},
|
|
13112
13120
|
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
13113
|
-
// to resolveId/load
|
|
13121
|
+
// to resolveId/load (plus transform for import rewriters, see
|
|
13122
|
+
// IMPORT_REWRITE_PLUGINS) and placed last so framework resolution runs first;
|
|
13114
13123
|
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
13115
13124
|
...state.userResolvePlugins
|
|
13116
13125
|
]
|
|
@@ -14755,7 +14764,6 @@ async function rango(options) {
|
|
|
14755
14764
|
}
|
|
14756
14765
|
},
|
|
14757
14766
|
optimizeDeps: {
|
|
14758
|
-
include: [nested("rsc-html-stream/client")],
|
|
14759
14767
|
exclude: excludeDeps,
|
|
14760
14768
|
rolldownOptions: sharedRolldownOptions
|
|
14761
14769
|
}
|
|
@@ -14923,8 +14931,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
|
|
|
14923
14931
|
"react",
|
|
14924
14932
|
"react-dom",
|
|
14925
14933
|
"react/jsx-runtime",
|
|
14926
|
-
"react/jsx-dev-runtime"
|
|
14927
|
-
nested("rsc-html-stream/client")
|
|
14934
|
+
"react/jsx-dev-runtime"
|
|
14928
14935
|
],
|
|
14929
14936
|
exclude: excludeDeps,
|
|
14930
14937
|
rolldownOptions: sharedRolldownOptions,
|
package/package.json
CHANGED
package/skills/loader/SKILL.md
CHANGED
|
@@ -1136,8 +1136,9 @@ COMPLETED Suspense boundary over ~500 bytes to an end-of-stream
|
|
|
1136
1136
|
`progressiveChunkSize` budget — the bytes are in the HTML, but not at their
|
|
1137
1137
|
document position, and a consumer that doesn't execute scripts never sees
|
|
1138
1138
|
them revealed. When the matched chain has a flagged loader, the document
|
|
1139
|
-
render
|
|
1140
|
-
content renders where it belongs
|
|
1139
|
+
render and the `ppr` shell capture raise the budget to `MAX_SAFE_INTEGER`
|
|
1140
|
+
automatically, so the awaited content renders where it belongs, on a shell
|
|
1141
|
+
HIT too. Pin the budget yourself (in either
|
|
1141
1142
|
direction) with `rango({ progressiveChunkSize })` in the Vite config — an
|
|
1142
1143
|
explicit value disables the auto-raise. Boundaries hoisting stylesheets
|
|
1143
1144
|
still outline; their reveal must wait for the CSS.
|
|
@@ -1160,9 +1161,7 @@ The costs and constraints:
|
|
|
1160
1161
|
served from the shell and does not run (unless the capture marked it to run:
|
|
1161
1162
|
see `/ppr` → On a shell HIT); one whose return carries promises
|
|
1162
1163
|
runs on every HIT, with its baked parts overlaid. A `cookies()`, `headers()`,
|
|
1163
|
-
`ctx.request.headers` or `{ cache: false }` read in it refuses the capture.
|
|
1164
|
-
`progressiveChunkSize` auto-raise is live-document only; captured shells
|
|
1165
|
-
outline per the explicit option or React's default.
|
|
1164
|
+
`ctx.request.headers` or `{ cache: false }` read in it refuses the capture.
|
|
1166
1165
|
|
|
1167
1166
|
Also available in `clientUrls()` route groups (`/client-urls`), where the
|
|
1168
1167
|
loader-heavy shape makes it most useful.
|
|
@@ -122,9 +122,10 @@ options (`logger`, function-valued `sources`) do not exist on the native path;
|
|
|
122
122
|
|
|
123
123
|
Nothing to configure. Rango's discovery/prerender step runs a throwaway temp Vite
|
|
124
124
|
server (`createTempRscServer`) that forwards only your **resolution** plugins
|
|
125
|
-
(`resolveId` / `load
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
(`resolveId` / `load`, plus the import rewrite of
|
|
126
|
+
`@rolldown/plugin-transform-imports`) and denies every `vite:*` plugin, so
|
|
127
|
+
`vite:react-compiler` never runs there. That is correct: the temp runner only
|
|
128
|
+
produces **data** (serialized Flight payloads + the route manifest), not shipped code, and React
|
|
128
129
|
Compiler is a memoization-only transform that does not change rendered output.
|
|
129
130
|
Your shipped client bundle is compiled by the `react({ compiler: true })` in your
|
|
130
131
|
app's own plugin array.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type FlightChunk = string | Uint8Array;
|
|
2
|
+
|
|
3
|
+
/** Where the document's inline scripts push Flight chunks (`self`). */
|
|
4
|
+
export interface FlightDataHost {
|
|
5
|
+
__FLIGHT_DATA?: FlightChunk[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The document's Flight payload as a stream: the server's inline scripts push
|
|
10
|
+
* chunks onto `self.__FLIGHT_DATA` (rsc-html-stream/server, or
|
|
11
|
+
* ssr/inject-rsc-eager.ts), and the browser's own DOMContentLoaded
|
|
12
|
+
* (`isTrusted`) closes it after the last of them ran. rsc-html-stream/client
|
|
13
|
+
* closed on any DOMContentLoaded, so a script re-dispatching the event cut a
|
|
14
|
+
* streaming page short and threw after the load (#1205).
|
|
15
|
+
*/
|
|
16
|
+
export function createDocumentFlightStream(
|
|
17
|
+
doc: Pick<
|
|
18
|
+
Document,
|
|
19
|
+
"readyState" | "addEventListener" | "removeEventListener"
|
|
20
|
+
>,
|
|
21
|
+
host: FlightDataHost,
|
|
22
|
+
): ReadableStream<Uint8Array> {
|
|
23
|
+
const encoder = new TextEncoder();
|
|
24
|
+
let controller!: ReadableStreamDefaultController<Uint8Array>;
|
|
25
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
26
|
+
start(c) {
|
|
27
|
+
controller = c;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const enqueue = (chunk: FlightChunk): void => {
|
|
31
|
+
controller.enqueue(
|
|
32
|
+
typeof chunk === "string" ? encoder.encode(chunk) : chunk,
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
const data = (host.__FLIGHT_DATA ||= []);
|
|
36
|
+
data.forEach(enqueue);
|
|
37
|
+
data.push = (...chunks: FlightChunk[]): number => {
|
|
38
|
+
chunks.forEach(enqueue);
|
|
39
|
+
return data.length;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (doc.readyState !== "loading") {
|
|
43
|
+
controller.close();
|
|
44
|
+
} else {
|
|
45
|
+
const onLoaded = (event: Event): void => {
|
|
46
|
+
if (!event.isTrusted) return;
|
|
47
|
+
doc.removeEventListener("DOMContentLoaded", onLoaded);
|
|
48
|
+
controller.close();
|
|
49
|
+
};
|
|
50
|
+
doc.addEventListener("DOMContentLoaded", onLoaded);
|
|
51
|
+
}
|
|
52
|
+
return stream;
|
|
53
|
+
}
|
|
@@ -59,7 +59,8 @@ import { setActiveInterceptTargets } from "../client-urls/navigation.js";
|
|
|
59
59
|
*/
|
|
60
60
|
export interface InitBrowserAppOptions {
|
|
61
61
|
/**
|
|
62
|
-
* RSC stream containing the initial payload
|
|
62
|
+
* RSC stream containing the initial payload
|
|
63
|
+
* (`@rangojs/router/internal/deps/html-stream-client`)
|
|
63
64
|
*/
|
|
64
65
|
rscStream: ReadableStream<Uint8Array>;
|
|
65
66
|
|
|
@@ -697,7 +698,7 @@ export interface RangoProps {}
|
|
|
697
698
|
* @example
|
|
698
699
|
* ```tsx
|
|
699
700
|
* import { initBrowserApp, Rango } from "@rangojs/router/browser";
|
|
700
|
-
* import { rscStream } from "
|
|
701
|
+
* import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
|
|
701
702
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
702
703
|
*
|
|
703
704
|
* async function main() {
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// The document's Flight stream for the generated browser entry. Outside a
|
|
2
|
+
// browser it stays empty and open, as rsc-html-stream/client's did.
|
|
3
|
+
import {
|
|
4
|
+
createDocumentFlightStream,
|
|
5
|
+
type FlightDataHost,
|
|
6
|
+
} from "../browser/document-flight-stream.js";
|
|
7
|
+
|
|
8
|
+
export const rscStream: ReadableStream<Uint8Array> =
|
|
9
|
+
typeof document === "undefined"
|
|
10
|
+
? new ReadableStream<Uint8Array>()
|
|
11
|
+
: createDocumentFlightStream(document, self as FlightDataHost);
|
|
@@ -45,6 +45,43 @@ export function droppedByRunnerReload(): boolean {
|
|
|
45
45
|
return runnerReloaded;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
type Serve = () => Promise<Response>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Serves a request on this router copy, or on the router a runner full reload
|
|
52
|
+
* re-created when the reload dropped this copy before the request or while it
|
|
53
|
+
* ran. What a request imports after the clear is the fresh copy, so one the
|
|
54
|
+
* reload lands inside fails or answers the wrong route: its outcome is
|
|
55
|
+
* discarded. Only GET and HEAD re-run, since a re-run repeats side effects and
|
|
56
|
+
* cannot re-read a body. A reload after the response is returned still splits
|
|
57
|
+
* its stream.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export async function serveAcrossRunnerReload(
|
|
61
|
+
request: Request,
|
|
62
|
+
serve: Serve,
|
|
63
|
+
recreated: () => Promise<Serve | undefined>,
|
|
64
|
+
): Promise<Response> {
|
|
65
|
+
if (runnerReloaded) {
|
|
66
|
+
const rerun = await recreated();
|
|
67
|
+
return (rerun ?? serve)();
|
|
68
|
+
}
|
|
69
|
+
if (request.method !== "GET" && request.method !== "HEAD") return serve();
|
|
70
|
+
const settled = await serve().then(
|
|
71
|
+
(response) => ({ response }),
|
|
72
|
+
(error: unknown) => ({ error }),
|
|
73
|
+
);
|
|
74
|
+
const rerun = runnerReloaded ? await recreated() : undefined;
|
|
75
|
+
if (rerun) {
|
|
76
|
+
if ("response" in settled) {
|
|
77
|
+
void settled.response.body?.cancel().catch(() => {});
|
|
78
|
+
}
|
|
79
|
+
return rerun();
|
|
80
|
+
}
|
|
81
|
+
if ("error" in settled) throw settled.error;
|
|
82
|
+
return settled.response;
|
|
83
|
+
}
|
|
84
|
+
|
|
48
85
|
/**
|
|
49
86
|
* The router the reload re-created for `routerId`, or undefined when none
|
|
50
87
|
* registered within DEV_RECREATED_ROUTER_WAIT_MS. Importing `sourceFile`
|
package/src/router.ts
CHANGED
|
@@ -134,8 +134,8 @@ import {
|
|
|
134
134
|
resolvePrefetchConcurrency,
|
|
135
135
|
} from "./router/prefetch-limits.js";
|
|
136
136
|
import {
|
|
137
|
-
droppedByRunnerReload,
|
|
138
137
|
routerAfterRunnerReload,
|
|
138
|
+
serveAcrossRunnerReload,
|
|
139
139
|
} from "./router/dev-reload-handoff.js";
|
|
140
140
|
|
|
141
141
|
// Re-export public types and values from extracted modules
|
|
@@ -1293,18 +1293,30 @@ export function createRouter<TEnv = any>(
|
|
|
1293
1293
|
});
|
|
1294
1294
|
}
|
|
1295
1295
|
|
|
1296
|
-
// Dev: a runner full reload
|
|
1297
|
-
// (router/dev-reload-handoff.ts, #1186).
|
|
1298
|
-
if (process.env.NODE_ENV !== "production"
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1296
|
+
// Dev: a runner full reload can drop this router's module copy before
|
|
1297
|
+
// or during the request (router/dev-reload-handoff.ts, #1186).
|
|
1298
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1299
|
+
return serveAcrossRunnerReload(
|
|
1300
|
+
request,
|
|
1301
|
+
() => serve(request, input),
|
|
1302
|
+
async () => {
|
|
1303
|
+
const reloaded = await routerAfterRunnerReload(
|
|
1304
|
+
routerId,
|
|
1305
|
+
__sourceFile,
|
|
1306
|
+
);
|
|
1307
|
+
return reloaded && reloaded !== router
|
|
1308
|
+
? () => reloaded.fetch(request, input)
|
|
1309
|
+
: undefined;
|
|
1310
|
+
},
|
|
1302
1311
|
);
|
|
1303
|
-
if (reloaded && reloaded !== router) {
|
|
1304
|
-
return reloaded.fetch(request, input);
|
|
1305
|
-
}
|
|
1306
1312
|
}
|
|
1313
|
+
return serve(request, input);
|
|
1314
|
+
};
|
|
1307
1315
|
|
|
1316
|
+
async function serve(
|
|
1317
|
+
request: Request,
|
|
1318
|
+
input: RouterRequestInput<TEnv>,
|
|
1319
|
+
): Promise<Response> {
|
|
1308
1320
|
// Trigger lazy import of per-router manifest data before route matching.
|
|
1309
1321
|
// No-op if data is already loaded or no loader is registered.
|
|
1310
1322
|
await ensureRouterManifest(routerId);
|
|
@@ -1325,7 +1337,7 @@ export function createRouter<TEnv = any>(
|
|
|
1325
1337
|
) => Promise<Response>;
|
|
1326
1338
|
}
|
|
1327
1339
|
return handler!(request, input);
|
|
1328
|
-
}
|
|
1340
|
+
}
|
|
1329
1341
|
})(),
|
|
1330
1342
|
|
|
1331
1343
|
// Low-level route matching for request classification
|
package/src/ssr/index.tsx
CHANGED
|
@@ -230,13 +230,15 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
230
230
|
* the `rango({ progressiveChunkSize })` plugin option here (canonical docs
|
|
231
231
|
* on `RangoBaseOptions.progressiveChunkSize` in vite/plugin-types.ts).
|
|
232
232
|
*
|
|
233
|
-
* When UNSET, createSSRHandler auto-
|
|
234
|
-
* document renders
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
233
|
+
* When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
|
|
234
|
+
* Number.MAX_SAFE_INTEGER for document renders and shell captures whose
|
|
235
|
+
* matched chain has a `loader(Def, { ssr: false })` entry (payload segments
|
|
236
|
+
* carry `awaitBeforeFlush`): the loader was awaited before first flush
|
|
237
|
+
* precisely so its content ships in-place, and React 19.2's Fizz would
|
|
238
|
+
* otherwise move any completed boundary over ~500 bytes to an end-of-stream
|
|
239
|
+
* `<div hidden>` + $RC reveal once the shell saturates the default 12800
|
|
240
|
+
* budget. A shell HIT inherits the raise (prelude and resumed holes), so it
|
|
241
|
+
* matches the live render. An explicit value disables the auto-raise.
|
|
240
242
|
*/
|
|
241
243
|
progressiveChunkSize?: number;
|
|
242
244
|
|
|
@@ -448,6 +450,18 @@ function hasAwaitBeforeFlush(payload: {
|
|
|
448
450
|
return payload.metadata?.segments?.some((s) => s.awaitBeforeFlush) === true;
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
/**
|
|
454
|
+
* The progressiveChunkSize renderHTML and captureShellHTML pass to Fizz (see
|
|
455
|
+
* SSRDependencies.progressiveChunkSize): the explicit option, else the
|
|
456
|
+
* ssr:false auto-raise.
|
|
457
|
+
*/
|
|
458
|
+
function documentChunkSize(
|
|
459
|
+
explicit: number | undefined,
|
|
460
|
+
awaitsBeforeFlush: boolean,
|
|
461
|
+
): number | undefined {
|
|
462
|
+
return explicit ?? (awaitsBeforeFlush ? Number.MAX_SAFE_INTEGER : undefined);
|
|
463
|
+
}
|
|
464
|
+
|
|
451
465
|
/**
|
|
452
466
|
* ssr: false documents only: before the first flush, give Fizz the turns a
|
|
453
467
|
* task waiting on an already-settling thenable needs, so a boundary that
|
|
@@ -955,9 +969,10 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
955
969
|
themeConfig: decoded.metadata?.themeConfig ?? null,
|
|
956
970
|
};
|
|
957
971
|
const awaitsBeforeFlush = hasAwaitBeforeFlush(decoded);
|
|
958
|
-
const progressiveChunkSize =
|
|
959
|
-
deps.progressiveChunkSize
|
|
960
|
-
|
|
972
|
+
const progressiveChunkSize = documentChunkSize(
|
|
973
|
+
deps.progressiveChunkSize,
|
|
974
|
+
awaitsBeforeFlush,
|
|
975
|
+
);
|
|
961
976
|
|
|
962
977
|
// Render React tree to HTML stream
|
|
963
978
|
// Pass formState for useActionState progressive enhancement if provided
|
|
@@ -1110,9 +1125,23 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1110
1125
|
const payloadSettled = new Promise<void>((resolve) => {
|
|
1111
1126
|
settlePayload = resolve;
|
|
1112
1127
|
});
|
|
1128
|
+
// Bootstrap resolution raced against the deadline (see resolveBootstrap):
|
|
1129
|
+
// null means the deadline won — the bounded no-shell degrade.
|
|
1130
|
+
const bootstrap = await resolveBootstrap(deps, deadline.promise);
|
|
1131
|
+
if (bootstrap === null) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Decoded after the bootstrap, as SsrRoot's first render used to, so
|
|
1136
|
+
// the capture gate's quiet clock starts only once fizz is about to.
|
|
1137
|
+
const payload = deserializeSsrPayload(
|
|
1138
|
+
createFromReadableStream,
|
|
1139
|
+
rscStream,
|
|
1140
|
+
);
|
|
1113
1141
|
const SsrRoot = createSsrRootComponent({
|
|
1114
1142
|
createFromReadableStream,
|
|
1115
1143
|
rscStream,
|
|
1144
|
+
payload,
|
|
1116
1145
|
onPayloadSettled: settlePayload,
|
|
1117
1146
|
// The shell key's own search: static-part search reads render what
|
|
1118
1147
|
// the key names, and the resume pass seeds the identical string.
|
|
@@ -1120,12 +1149,17 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1120
1149
|
origin: opts.origin,
|
|
1121
1150
|
});
|
|
1122
1151
|
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1152
|
+
// ssr:false auto-raise (SSRDependencies.progressiveChunkSize); resume()
|
|
1153
|
+
// inherits it from the postponed state. A rejected or late payload
|
|
1154
|
+
// skips it and fails or degrades below as before.
|
|
1155
|
+
const awaitsBeforeFlush = await Promise.race([
|
|
1156
|
+
payload.then(hasAwaitBeforeFlush, () => false),
|
|
1157
|
+
deadline.promise.then(() => false),
|
|
1158
|
+
]);
|
|
1159
|
+
const progressiveChunkSize = documentChunkSize(
|
|
1160
|
+
deps.progressiveChunkSize,
|
|
1161
|
+
awaitsBeforeFlush,
|
|
1162
|
+
);
|
|
1129
1163
|
|
|
1130
1164
|
// Start prerender first, then run the abort schedule concurrently. When
|
|
1131
1165
|
// holes are pending, prerender's promise settles only after abort(); when
|
|
@@ -1140,14 +1174,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1140
1174
|
const prerenderPromise = prerender(<SsrRoot />, {
|
|
1141
1175
|
signal: controller.signal,
|
|
1142
1176
|
...bootstrap,
|
|
1143
|
-
|
|
1144
|
-
// (RangoBaseOptions.progressiveChunkSize documents the contract); the
|
|
1145
|
-
// capture handler starts prerender without deserializing the payload,
|
|
1146
|
-
// so segment metadata is not read here. The captured value rides the
|
|
1147
|
-
// postponed state into every later resume().
|
|
1148
|
-
...(deps.progressiveChunkSize !== undefined && {
|
|
1149
|
-
progressiveChunkSize: deps.progressiveChunkSize,
|
|
1150
|
-
}),
|
|
1177
|
+
...(progressiveChunkSize !== undefined && { progressiveChunkSize }),
|
|
1151
1178
|
// Abort is how capture WORKS: once the shell is quiet we abort() to
|
|
1152
1179
|
// freeze the prelude and let the still-pending holes postpone. React
|
|
1153
1180
|
// reports the abort reason for each pending boundary through onError.
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -147,11 +147,11 @@ function createSsrEventController(opts: {
|
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
149
|
* Deserialize the Flight stream branch into the payload SsrRoot renders,
|
|
150
|
-
* fragment expansion included. Factored out of SsrRoot so createSSRHandler
|
|
151
|
-
* create the promise BEFORE fizz starts and read
|
|
152
|
-
* the fizz call itself (the ssr:false
|
|
153
|
-
* awaiting it there is latency-neutral
|
|
154
|
-
* until this same promise settles.
|
|
150
|
+
* fragment expansion included. Factored out of SsrRoot so createSSRHandler and
|
|
151
|
+
* createShellCaptureHandler can create the promise BEFORE fizz starts and read
|
|
152
|
+
* metadata that must influence the fizz call itself (the ssr:false
|
|
153
|
+
* progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
|
|
154
|
+
* because fizz cannot emit even <html> until this same promise settles.
|
|
155
155
|
*/
|
|
156
156
|
export function deserializeSsrPayload(
|
|
157
157
|
createFromReadableStream: CreateFromReadableStream,
|
|
@@ -187,7 +187,8 @@ export async function runShellPrerenderPhase(
|
|
|
187
187
|
prerender: reactDomStatic.prerender ?? reactDomStatic.default?.prerender,
|
|
188
188
|
injectRSCPayload: htmlStream.injectRSCPayload,
|
|
189
189
|
headScripts: "preinit",
|
|
190
|
-
// Build-captured shells
|
|
190
|
+
// Build-captured shells honor the explicit outlining budget, or the
|
|
191
|
+
// ssr:false auto-raise without one (createShellCaptureHandler): the
|
|
191
192
|
// capture value rides the postponed state into every runtime resume().
|
|
192
193
|
...(s.opts?.progressiveChunkSize !== undefined && {
|
|
193
194
|
progressiveChunkSize: s.opts.progressiveChunkSize,
|
|
@@ -135,9 +135,10 @@ export interface DiscoveryState {
|
|
|
135
135
|
| import("../utils/forward-user-plugins.js").ForwardedRunnerConfig
|
|
136
136
|
| undefined;
|
|
137
137
|
/**
|
|
138
|
-
* User resolution plugins (resolveId/load
|
|
139
|
-
* surface, forwarded into the discovery temp
|
|
140
|
-
* resolvers such as vite-tsconfig-paths participate
|
|
138
|
+
* User resolution plugins (resolveId/load, plus import rewriters' transform),
|
|
139
|
+
* stripped to their resolution surface, forwarded into the discovery temp
|
|
140
|
+
* server. Lets third-party resolvers such as vite-tsconfig-paths participate
|
|
141
|
+
* in discovery.
|
|
141
142
|
*/
|
|
142
143
|
userResolvePlugins: import("vite").Plugin[];
|
|
143
144
|
scanFilter: ScanFilter | undefined;
|
package/src/vite/plugin-types.ts
CHANGED
|
@@ -189,12 +189,12 @@ interface RangoBaseOptions {
|
|
|
189
189
|
* the CSS. Trade-off: inline content delays later shell bytes behind it,
|
|
190
190
|
* which is why React outlines by default.
|
|
191
191
|
*
|
|
192
|
-
* When UNSET, document renders whose matched chain
|
|
193
|
-
* `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
194
|
-
* loader was awaited before first flush precisely so its content ships
|
|
195
|
-
* in-place, and outlining the boundary it feeds would defeat that.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
192
|
+
* When UNSET, document renders and PPR shell captures whose matched chain
|
|
193
|
+
* has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
194
|
+
* the loader was awaited before first flush precisely so its content ships
|
|
195
|
+
* in-place, and outlining the boundary it feeds would defeat that. A shell
|
|
196
|
+
* HIT inherits the captured value, so it matches the live render. Setting
|
|
197
|
+
* an explicit value disables the auto-raise.
|
|
198
198
|
*
|
|
199
199
|
* Apps with a custom SSR entry set this per-handler via
|
|
200
200
|
* `SSRDependencies.progressiveChunkSize`.
|
package/src/vite/rango.ts
CHANGED
|
@@ -234,7 +234,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
234
234
|
},
|
|
235
235
|
},
|
|
236
236
|
optimizeDeps: {
|
|
237
|
-
include: [nested("rsc-html-stream/client")],
|
|
238
237
|
exclude: excludeDeps,
|
|
239
238
|
rolldownOptions: sharedRolldownOptions,
|
|
240
239
|
},
|
|
@@ -457,7 +456,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
457
456
|
"react-dom",
|
|
458
457
|
"react/jsx-runtime",
|
|
459
458
|
"react/jsx-dev-runtime",
|
|
460
|
-
nested("rsc-html-stream/client"),
|
|
461
459
|
],
|
|
462
460
|
exclude: excludeDeps,
|
|
463
461
|
rolldownOptions: sharedRolldownOptions,
|
|
@@ -350,7 +350,8 @@ export async function createTempRscServer(
|
|
|
350
350
|
},
|
|
351
351
|
},
|
|
352
352
|
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
353
|
-
// to resolveId/load
|
|
353
|
+
// to resolveId/load (plus transform for import rewriters, see
|
|
354
|
+
// IMPORT_REWRITE_PLUGINS) and placed last so framework resolution runs first;
|
|
354
355
|
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
355
356
|
...state.userResolvePlugins,
|
|
356
357
|
],
|
|
@@ -592,8 +593,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
592
593
|
// Capture user's resolve aliases for the temp server
|
|
593
594
|
s.userResolveAlias = config.resolve.alias;
|
|
594
595
|
// Capture the data-only resolution config (resolve.*, define, oxc) and
|
|
595
|
-
// the user's resolution plugins (resolveId/load
|
|
596
|
-
// server resolves modules the same way
|
|
596
|
+
// the user's resolution plugins (resolveId/load, plus import rewriters'
|
|
597
|
+
// transform) so the discovery temp server resolves modules the same way
|
|
598
|
+
// the real environment does.
|
|
597
599
|
// Without this, both flavors of user resolution are absent during
|
|
598
600
|
// discovery/prerender/static rendering even though they apply at request
|
|
599
601
|
// time: third-party resolvers (e.g. vite-tsconfig-paths, forwarded as
|
|
@@ -37,18 +37,39 @@ function isDenied(name: string): boolean {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Transform-only plugins forwarded with their `transform` hook. A plugin
|
|
41
|
+
* belongs here only if its transform changes the code or imports discovery
|
|
42
|
+
* evaluates, and reads only state built at construction or in
|
|
43
|
+
* `config`/`configResolved`. Neither shows in a hook's shape, so the list is
|
|
44
|
+
* by name: forwarding every transform would let e.g. @tailwindcss/vite cache
|
|
45
|
+
* per-environment state built from the temp server's config.
|
|
46
|
+
*
|
|
47
|
+
* `rolldown-plugin-transform-imports` (@rolldown/plugin-transform-imports)
|
|
48
|
+
* rewrites barrel imports to subpaths (vitejs/vite-plugin-react#1519). Dropped,
|
|
49
|
+
* discovery loads the whole barrel and fails on a leaf importing `client-only`.
|
|
50
|
+
*/
|
|
51
|
+
const IMPORT_REWRITE_PLUGINS: ReadonlySet<string> = new Set([
|
|
52
|
+
"rolldown-plugin-transform-imports",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A plugin participates in resolution if it exposes `resolveId` or `load`, or
|
|
57
|
+
* rewrites imports (IMPORT_REWRITE_PLUGINS). Other plugins that only transform,
|
|
58
|
+
* configure the server, or hook into the build lifecycle are skipped to keep
|
|
43
59
|
* the forwarded surface minimal.
|
|
44
60
|
*/
|
|
45
|
-
function
|
|
46
|
-
return Boolean(
|
|
61
|
+
function isForwardable(p: Plugin): boolean {
|
|
62
|
+
return Boolean(
|
|
63
|
+
(p as any).resolveId ||
|
|
64
|
+
(p as any).load ||
|
|
65
|
+
IMPORT_REWRITE_PLUGINS.has(p.name),
|
|
66
|
+
);
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
/**
|
|
50
|
-
* Strip a resolved plugin instance down to its resolution hooks
|
|
51
|
-
* gating fields that decide whether/where it
|
|
70
|
+
* Strip a resolved plugin instance down to its resolution hooks (and an import
|
|
71
|
+
* rewriter's `transform`) plus the gating fields that decide whether/where it
|
|
72
|
+
* runs.
|
|
52
73
|
*
|
|
53
74
|
* We reuse the SAME instance objects captured from `config.plugins`. By the
|
|
54
75
|
* time `configResolved` fires on the discovery plugin, every plugin's own
|
|
@@ -78,6 +99,8 @@ function stripToResolutionHooks(p: Plugin): Plugin {
|
|
|
78
99
|
(stripped as any).applyToEnvironment = (p as any).applyToEnvironment;
|
|
79
100
|
if ((p as any).resolveId) (stripped as any).resolveId = (p as any).resolveId;
|
|
80
101
|
if ((p as any).load) (stripped as any).load = (p as any).load;
|
|
102
|
+
if (IMPORT_REWRITE_PLUGINS.has(p.name))
|
|
103
|
+
(stripped as any).transform = (p as any).transform;
|
|
81
104
|
return stripped;
|
|
82
105
|
}
|
|
83
106
|
|
|
@@ -95,7 +118,7 @@ export function selectForwardableResolvePlugins(
|
|
|
95
118
|
for (const p of plugins) {
|
|
96
119
|
const name = p?.name;
|
|
97
120
|
if (!name || isDenied(name)) continue;
|
|
98
|
-
if (!
|
|
121
|
+
if (!isForwardable(p)) continue;
|
|
99
122
|
forwarded.push(stripToResolutionHooks(p));
|
|
100
123
|
}
|
|
101
124
|
return forwarded;
|