@rangojs/router 0.26.4 → 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 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.4",
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 (from rsc-html-stream/client)
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 "rsc-html-stream/client";
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 { rscStream } from "rsc-html-stream/client";
1
+ export declare const rscStream: ReadableStream<Uint8Array>;
@@ -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-raises to Number.MAX_SAFE_INTEGER for
173
- * document renders whose matched chain has a `loader(Def, { ssr: false })`
174
- * entry (payload segments carry `awaitBeforeFlush`): the loader was awaited
175
- * before first flush precisely so its content ships in-place, and React
176
- * 19.2's Fizz would otherwise move any completed boundary over ~500 bytes
177
- * to an end-of-stream `<div hidden>` + $RC reveal once the shell saturates
178
- * the default 12800 budget. An explicit value disables the auto-raise.
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 can
32
- * create the promise BEFORE fizz starts and read metadata that must influence
33
- * the fizz call itself (the ssr:false progressiveChunkSize auto-raise) —
34
- * awaiting it there is latency-neutral because fizz cannot emit even <html>
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), stripped to their resolution
130
- * surface, forwarded into the discovery temp server. Lets third-party
131
- * resolvers such as vite-tsconfig-paths participate in discovery.
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 has a
169
- * `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER — the
170
- * loader was awaited before first flush precisely so its content ships
171
- * in-place, and outlining the boundary it feeds would defeat that. Setting
172
- * an explicit value disables the auto-raise. The auto-raise is live-SSR
173
- * only; captured shells use the explicit value or React's default.
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`.
@@ -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.4",
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 must honor the explicit outlining budget: the
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
- function hasResolutionHooks(p) {
12946
- return Boolean(p.resolveId || p.load);
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 (!hasResolutionHooks(p)) continue;
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 and placed last so framework resolution runs first;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.26.4",
3
+ "version": "0.26.5",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -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 raises the budget to `MAX_SAFE_INTEGER` automatically, so the awaited
1140
- content renders where it belongs. Pin the budget yourself (in either
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. The
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`) and denies every `vite:*` plugin, so `vite:react-compiler`
126
- never runs there. That is correct: the temp runner only produces **data**
127
- (serialized Flight payloads + the route manifest), not shipped code, and React
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 (from rsc-html-stream/client)
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 "rsc-html-stream/client";
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
- // Re-export rsc-html-stream/client for internal use by virtual entries
2
- export { rscStream } from "rsc-html-stream/client";
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);
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-raises to Number.MAX_SAFE_INTEGER for
234
- * document renders whose matched chain has a `loader(Def, { ssr: false })`
235
- * entry (payload segments carry `awaitBeforeFlush`): the loader was awaited
236
- * before first flush precisely so its content ships in-place, and React
237
- * 19.2's Fizz would otherwise move any completed boundary over ~500 bytes
238
- * to an end-of-stream `<div hidden>` + $RC reveal once the shell saturates
239
- * the default 12800 budget. An explicit value disables the auto-raise.
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
- (awaitsBeforeFlush ? Number.MAX_SAFE_INTEGER : undefined);
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
- // Bootstrap resolution raced against the deadline (see resolveBootstrap):
1124
- // null means the deadline won — the bounded no-shell degrade.
1125
- const bootstrap = await resolveBootstrap(deps, deadline.promise);
1126
- if (bootstrap === null) {
1127
- return null;
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
- // Explicit option only — the ssr:false auto-raise is live-SSR scoped
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.
@@ -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 can
151
- * create the promise BEFORE fizz starts and read metadata that must influence
152
- * the fizz call itself (the ssr:false progressiveChunkSize auto-raise) —
153
- * awaiting it there is latency-neutral because fizz cannot emit even <html>
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 must honor the explicit outlining budget: the
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), stripped to their resolution
139
- * surface, forwarded into the discovery temp server. Lets third-party
140
- * resolvers such as vite-tsconfig-paths participate in discovery.
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;
@@ -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 has a
193
- * `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER — the
194
- * loader was awaited before first flush precisely so its content ships
195
- * in-place, and outlining the boundary it feeds would defeat that. Setting
196
- * an explicit value disables the auto-raise. The auto-raise is live-SSR
197
- * only; captured shells use the explicit value or React's default.
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 and placed last so framework resolution runs first;
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) so the discovery temp
596
- // server resolves modules the same way the real environment does.
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
- * A plugin participates in resolution if it exposes `resolveId` or `load`.
41
- * Plugins that only transform, configure the server, or hook into the build
42
- * lifecycle do not affect how bare specifiers resolve, so we skip them to keep
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 hasResolutionHooks(p: Plugin): boolean {
46
- return Boolean((p as any).resolveId || (p as any).load);
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 plus the
51
- * gating fields that decide whether/where it runs.
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 (!hasResolutionHooks(p)) continue;
121
+ if (!isForwardable(p)) continue;
99
122
  forwarded.push(stripToResolutionHooks(p));
100
123
  }
101
124
  return forwarded;