@rangojs/router 0.0.0-experimental.107 → 0.0.0-experimental.109

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.
Files changed (83) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/rango.js +16 -16
  3. package/dist/vite/index.js +146 -150
  4. package/package.json +6 -6
  5. package/skills/hooks/SKILL.md +2 -0
  6. package/skills/links/SKILL.md +13 -1
  7. package/skills/loader/SKILL.md +1 -1
  8. package/skills/middleware/SKILL.md +3 -3
  9. package/skills/mime-routes/SKILL.md +27 -0
  10. package/skills/prerender/SKILL.md +13 -13
  11. package/skills/rango/SKILL.md +9 -0
  12. package/skills/response-routes/SKILL.md +58 -9
  13. package/skills/router-setup/SKILL.md +3 -3
  14. package/skills/typesafety/SKILL.md +273 -31
  15. package/src/__augment-tests__/augment.ts +81 -0
  16. package/src/__augment-tests__/augmented.check.ts +117 -0
  17. package/src/browser/index.ts +3 -3
  18. package/src/browser/react/location-state-shared.ts +3 -3
  19. package/src/browser/react/use-handle.ts +17 -9
  20. package/src/browser/rsc-router.tsx +14 -14
  21. package/src/browser/segment-structure-assert.ts +2 -2
  22. package/src/build/generate-manifest.ts +3 -3
  23. package/src/build/route-types/codegen.ts +4 -4
  24. package/src/build/route-types/include-resolution.ts +1 -1
  25. package/src/build/route-types/per-module-writer.ts +3 -3
  26. package/src/build/route-types/router-processing.ts +4 -4
  27. package/src/build/route-types/scan-filter.ts +1 -1
  28. package/src/client.tsx +4 -7
  29. package/src/errors.ts +1 -1
  30. package/src/handle.ts +2 -2
  31. package/src/href-client.ts +136 -19
  32. package/src/index.rsc.ts +4 -4
  33. package/src/index.ts +2 -2
  34. package/src/loader.rsc.ts +1 -1
  35. package/src/loader.ts +1 -1
  36. package/src/prerender.ts +4 -4
  37. package/src/route-definition/dsl-helpers.ts +2 -2
  38. package/src/route-definition/helpers-types.ts +2 -2
  39. package/src/router/error-handling.ts +1 -1
  40. package/src/router/lazy-includes.ts +2 -2
  41. package/src/router/metrics.ts +1 -1
  42. package/src/router/middleware-types.ts +1 -1
  43. package/src/router/prerender-match.ts +1 -1
  44. package/src/router/router-interfaces.ts +34 -28
  45. package/src/router/router-options.ts +1 -1
  46. package/src/router/router-registry.ts +2 -5
  47. package/src/router/segment-resolution/fresh.ts +2 -2
  48. package/src/router/segment-resolution/revalidation.ts +2 -2
  49. package/src/router.ts +13 -16
  50. package/src/rsc/handler-context.ts +2 -2
  51. package/src/rsc/index.ts +1 -1
  52. package/src/rsc/types.ts +2 -2
  53. package/src/search-params.ts +4 -4
  54. package/src/serialize.ts +243 -0
  55. package/src/server/context.ts +16 -16
  56. package/src/static-handler.ts +1 -1
  57. package/src/types/global-namespace.ts +39 -26
  58. package/src/types/handler-context.ts +3 -3
  59. package/src/urls/path-helper-types.ts +2 -2
  60. package/src/urls/pattern-types.ts +34 -0
  61. package/src/urls/type-extraction.ts +6 -1
  62. package/src/use-loader.tsx +6 -4
  63. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  64. package/src/vite/discovery/discover-routers.ts +3 -3
  65. package/src/vite/discovery/discovery-errors.ts +1 -1
  66. package/src/vite/discovery/prerender-collection.ts +19 -25
  67. package/src/vite/discovery/route-types-writer.ts +3 -3
  68. package/src/vite/discovery/state.ts +4 -4
  69. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
  70. package/src/vite/plugins/expose-action-id.ts +2 -2
  71. package/src/vite/plugins/expose-id-utils.ts +12 -8
  72. package/src/vite/plugins/expose-ids/export-analysis.ts +33 -9
  73. package/src/vite/plugins/expose-internal-ids.ts +1 -1
  74. package/src/vite/plugins/performance-tracks.ts +12 -16
  75. package/src/vite/plugins/use-cache-transform.ts +1 -1
  76. package/src/vite/plugins/version-plugin.ts +2 -2
  77. package/src/vite/plugins/virtual-entries.ts +2 -2
  78. package/src/vite/rango.ts +11 -11
  79. package/src/vite/router-discovery.ts +26 -29
  80. package/src/vite/utils/ast-handler-extract.ts +15 -15
  81. package/src/vite/utils/bundle-analysis.ts +4 -2
  82. package/src/vite/utils/forward-user-plugins.ts +46 -17
  83. package/src/vite/utils/shared-utils.ts +26 -22
@@ -103,7 +103,7 @@ function ensureCloudflareProtocolLoaderRegistered(): void {
103
103
  // register() requires Node 18.19+ / 20.6+. Older Node still has the
104
104
  // Vite transform as primary defense.
105
105
  console.warn(
106
- `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
106
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
107
107
  );
108
108
  }
109
109
  }
@@ -141,9 +141,8 @@ async function createTempRscServer(
141
141
  const resolveConfig = runnerConfig?.resolve ?? {
142
142
  alias: state.userResolveAlias,
143
143
  };
144
- const esbuildConfig = runnerConfig?.esbuild ?? {
145
- jsx: "automatic",
146
- jsxImportSource: "react",
144
+ const oxcConfig = runnerConfig?.oxc ?? {
145
+ jsx: { runtime: "automatic", importSource: "react" },
147
146
  };
148
147
  return createViteServer({
149
148
  root: state.projectRoot,
@@ -153,7 +152,7 @@ async function createTempRscServer(
153
152
  logLevel: "silent",
154
153
  resolve: resolveConfig,
155
154
  ...(runnerConfig?.define ? { define: runnerConfig.define } : {}),
156
- esbuild: esbuildConfig as any,
155
+ oxc: oxcConfig as any,
157
156
  ...(options.cacheDir && { cacheDir: options.cacheDir }),
158
157
  plugins: [
159
158
  rsc({
@@ -203,7 +202,7 @@ async function resolveBuildEnv(
203
202
  if (option === "auto") {
204
203
  if (factoryCtx.preset !== "cloudflare") {
205
204
  throw new Error(
206
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
205
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
207
206
  "Use a factory function or plain object for other presets.",
208
207
  );
209
208
  }
@@ -225,7 +224,7 @@ async function resolveBuildEnv(
225
224
  };
226
225
  } catch (err: any) {
227
226
  throw new Error(
228
- '[rsc-router] buildEnv: "auto" requires wrangler to be installed.\n' +
227
+ '[rango] buildEnv: "auto" requires wrangler to be installed.\n' +
229
228
  `Install it with: pnpm add -D wrangler\n${err.message}`,
230
229
  );
231
230
  }
@@ -277,7 +276,7 @@ async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
277
276
  try {
278
277
  await s.buildEnvDispose();
279
278
  } catch (err: any) {
280
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
279
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
281
280
  }
282
281
  s.buildEnvDispose = null;
283
282
  }
@@ -330,12 +329,14 @@ export function createRouterDiscoveryPlugin(
330
329
  viteMode = config.mode;
331
330
  // Capture user's resolve aliases for the temp server
332
331
  s.userResolveAlias = config.resolve.alias;
333
- // Capture the data-only resolution config (resolve.*, define, esbuild)
334
- // and the user's resolution plugins (resolveId/load) so the discovery
335
- // temp server resolves modules the same way the real environment does.
336
- // Without this, third-party resolvers (e.g. vite-tsconfig-paths) are
337
- // absent during discovery/prerender/static rendering even though they
338
- // apply at request time. See utils/forward-user-plugins.ts.
332
+ // Capture the data-only resolution config (resolve.*, define, oxc) and
333
+ // the user's resolution plugins (resolveId/load) so the discovery temp
334
+ // server resolves modules the same way the real environment does.
335
+ // Without this, both flavors of user resolution are absent during
336
+ // discovery/prerender/static rendering even though they apply at request
337
+ // time: third-party resolvers (e.g. vite-tsconfig-paths, forwarded as
338
+ // plugins) and Vite 8's native resolve.tsconfigPaths (forwarded in the
339
+ // data slice). See utils/forward-user-plugins.ts.
339
340
  s.userRunnerConfig = pickForwardedRunnerConfig(config);
340
341
  s.userResolvePlugins = selectForwardableResolvePlugins(
341
342
  config.plugins as any,
@@ -543,9 +544,7 @@ export function createRouterDiscoveryPlugin(
543
544
  "getOrCreateTempServer: FAILED message=%s",
544
545
  err.message,
545
546
  );
546
- console.warn(
547
- `[rsc-router] Failed to create temp runner: ${err.message}`,
548
- );
547
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
549
548
  }
550
549
  return null;
551
550
  }
@@ -684,7 +683,7 @@ export function createRouterDiscoveryPlugin(
684
683
  }
685
684
  } catch (err: any) {
686
685
  console.warn(
687
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
686
+ `[rango] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
688
687
  );
689
688
  }
690
689
 
@@ -736,7 +735,7 @@ export function createRouterDiscoveryPlugin(
736
735
  );
737
736
  } catch (err: any) {
738
737
  console.warn(
739
- `[rsc-router] Router discovery failed: ${err.message}\n${err.stack}`,
738
+ `[rango] Router discovery failed: ${err.message}\n${err.stack}`,
740
739
  );
741
740
  } finally {
742
741
  debugDiscovery?.(
@@ -851,7 +850,7 @@ export function createRouterDiscoveryPlugin(
851
850
  registry = serverMod.RouterRegistry ?? null;
852
851
  } catch (err: any) {
853
852
  console.warn(
854
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`,
853
+ `[rango] Dev prerender module refresh failed: ${err.message}`,
855
854
  );
856
855
  res.statusCode = 500;
857
856
  res.end(`Prerender handler error: ${err.message}`);
@@ -917,7 +916,7 @@ export function createRouterDiscoveryPlugin(
917
916
  return;
918
917
  } catch (err: any) {
919
918
  console.warn(
920
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`,
919
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`,
921
920
  );
922
921
  }
923
922
  }
@@ -1031,7 +1030,7 @@ export function createRouterDiscoveryPlugin(
1031
1030
  at: Date.now(),
1032
1031
  };
1033
1032
  console.warn(
1034
- `[rsc-router] Runtime re-discovery failed: ${err.message}`,
1033
+ `[rango] Runtime re-discovery failed: ${err.message}`,
1035
1034
  );
1036
1035
  debugDiscovery?.(
1037
1036
  "hmr: lastDiscoveryError set (%s) — manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
@@ -1074,9 +1073,7 @@ export function createRouterDiscoveryPlugin(
1074
1073
  }
1075
1074
  }
1076
1075
  } catch (err: any) {
1077
- console.error(
1078
- `[rsc-router] Route regeneration error: ${err.message}`,
1079
- );
1076
+ console.error(`[rango] Route regeneration error: ${err.message}`);
1080
1077
  }
1081
1078
  debugDiscovery?.(
1082
1079
  "watcher: regenerated gen files (%sms)",
@@ -1088,7 +1085,7 @@ export function createRouterDiscoveryPlugin(
1088
1085
  if (s.perRouterManifests.length > 0) {
1089
1086
  refreshRuntimeDiscovery().catch((err: any) => {
1090
1087
  console.warn(
1091
- `[rsc-router] Runtime re-discovery error: ${err.message}`,
1088
+ `[rango] Runtime re-discovery error: ${err.message}`,
1092
1089
  );
1093
1090
  // Even on error, unblock the gate so workerd's reload
1094
1091
  // doesn't hang indefinitely against the previous manifest.
@@ -1259,7 +1256,7 @@ export function createRouterDiscoveryPlugin(
1259
1256
  const rscEnv = (tempServer.environments as any)?.rsc;
1260
1257
  if (!rscEnv?.runner) {
1261
1258
  console.warn(
1262
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation",
1259
+ "[rango] RSC environment runner not available during build, skipping manifest generation",
1263
1260
  );
1264
1261
  return;
1265
1262
  }
@@ -1302,7 +1299,7 @@ export function createRouterDiscoveryPlugin(
1302
1299
  .filter(Boolean)
1303
1300
  .join("\n");
1304
1301
  throw new Error(
1305
- `[rsc-router] Build-time router discovery failed:\n${details}`,
1302
+ `[rango] Build-time router discovery failed:\n${details}`,
1306
1303
  { cause: err },
1307
1304
  );
1308
1305
  } finally {
@@ -1330,7 +1327,7 @@ export function createRouterDiscoveryPlugin(
1330
1327
  // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
1331
1328
  // AND vite's own HMR pipeline (which invalidates the gen file's
1332
1329
  // importers and triggers a second workerd full reload — visible to the
1333
- // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
1330
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
1334
1331
  //
1335
1332
  // `peekSelfGenWrite` is the authoritative filter: its map only contains
1336
1333
  // paths that `markSelfGenWrite` has registered, so it natively works
@@ -48,7 +48,7 @@ function findImportInsertionPos(
48
48
  ): number {
49
49
  let program: ProgramNode;
50
50
  try {
51
- program = parseAst(code, { jsx: true });
51
+ program = parseAst(code, { lang: "tsx" });
52
52
  } catch {
53
53
  return 0;
54
54
  }
@@ -127,7 +127,7 @@ export function findHandlerCalls(
127
127
  ): HandlerCallSite[] {
128
128
  let program: ProgramNode;
129
129
  try {
130
- program = parseAst(code, { jsx: true });
130
+ program = parseAst(code, { lang: "tsx" });
131
131
  } catch {
132
132
  return [];
133
133
  }
@@ -239,7 +239,7 @@ export function getImportedLocalNames(
239
239
  parseAst: (code: string, options?: any) => ProgramNode,
240
240
  ): Set<string> {
241
241
  try {
242
- const program = parseAst(code, { jsx: true });
242
+ const program = parseAst(code, { lang: "tsx" });
243
243
  return getImportedLocalNamesFromProgram(program, importedName);
244
244
  } catch {
245
245
  return new Set<string>();
@@ -256,7 +256,7 @@ export function extractImportDeclarations(
256
256
  ): string[] {
257
257
  let program: ProgramNode;
258
258
  try {
259
- program = parseAst(code, { jsx: true });
259
+ program = parseAst(code, { lang: "tsx" });
260
260
  } catch {
261
261
  return [];
262
262
  }
@@ -380,7 +380,7 @@ export function extractModuleLevelDeclarations(
380
380
  ): string[] {
381
381
  let program: ProgramNode;
382
382
  try {
383
- program = parseAst(code, { jsx: true });
383
+ program = parseAst(code, { lang: "tsx" });
384
384
  } catch {
385
385
  return [];
386
386
  }
@@ -468,19 +468,19 @@ export function transformInlineHandlers(
468
468
  handlerNames,
469
469
  );
470
470
 
471
- // Track line occurrences for same-line collision handling
472
- const lineCounts = new Map<number, number>();
473
-
474
471
  // Collect all import statements to prepend
475
472
  const importStatements: string[] = [];
476
473
 
477
- for (const site of inlineSites) {
478
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
479
- lineCounts.set(site.lineNumber, lineCount + 1);
480
-
481
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
474
+ for (const [siteIndex, site] of inlineSites.entries()) {
475
+ // Key the extracted handler on its source-order index (per fnName), NOT its
476
+ // line number. The id flows into BOTH the export name and the virtual module
477
+ // path (which hashId hashes for the runtime $$id), and line numbers shift
478
+ // between the prerender and production build contexts. The index is invariant
479
+ // to those shifts, keeping the prerender manifest key == the runtime id.
480
+ const hash = hashInlineId(filePath, fnName, siteIndex);
482
481
  const exportName = `__sh_${hash}`;
483
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
482
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
483
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
484
484
 
485
485
  // Extract the full handler call expression text
486
486
  const handlerCode = code.slice(site.callStart, site.callEnd);
@@ -498,7 +498,7 @@ export function transformInlineHandlers(
498
498
  s.overwrite(site.callStart, site.callEnd, exportName);
499
499
 
500
500
  // Build the import specifier for this virtual module
501
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
501
+ const importId = `${virtualPrefix}${idSuffix}`;
502
502
  importStatements.push(`import { ${exportName} } from "${importId}";`);
503
503
  }
504
504
 
@@ -59,7 +59,7 @@ export function extractHandlerExportsFromChunk(
59
59
  if (detectPassthrough) {
60
60
  const eFnName = escapeRegExp(fnName);
61
61
  const callStartRe = new RegExp(
62
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
62
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
63
63
  );
64
64
  const callStart = callStartRe.exec(chunkCode);
65
65
  if (callStart) {
@@ -98,8 +98,10 @@ export function evictHandlerCode(
98
98
  if (passthrough) continue;
99
99
 
100
100
  const eName = escapeRegExp(name);
101
+ // Match const/let/var: Rolldown (Vite 8) emits top-level bindings in the
102
+ // non-minified RSC bundle as `var`, whereas Rollup used `const`.
101
103
  const callStartRe = new RegExp(
102
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
104
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
103
105
  );
104
106
  const startMatch = callStartRe.exec(modified);
105
107
  if (!startMatch) continue;
@@ -4,14 +4,20 @@
4
4
  * The discovery temp server (createTempRscServer) runs the user's handler
5
5
  * graph through a throwaway Node Vite server built with `configFile: false`.
6
6
  * Without help, that server only sees a fixed Rango-owned plugin set, so any
7
- * user resolution provided by third-party plugins (e.g. vite-tsconfig-paths)
8
- * is absent during discovery, prerender, and static handler rendering even
9
- * though it applies at request time.
7
+ * user resolution is absent during discovery, prerender, and static handler
8
+ * rendering even though it applies at request time. Two flavors of user
9
+ * resolution must be carried across:
10
+ *
11
+ * - Third-party resolveId plugins (e.g. vite-tsconfig-paths) — forwarded as
12
+ * plugin instances, see selectForwardableResolvePlugins.
13
+ * - Native config-driven resolution, including Vite 8's built-in
14
+ * `resolve.tsconfigPaths` (which supersedes vite-tsconfig-paths) — forwarded
15
+ * as the data slice, see pickForwardedRunnerConfig.
10
16
  *
11
17
  * These helpers extract the resolution-relevant slice of the user's resolved
12
- * config (resolve.*, define, esbuild) and forward the user's resolution
13
- * plugins into the temp server so discovery resolves modules the same way the
14
- * real environment does.
18
+ * config (resolve.*, define, oxc) and forward the user's resolution plugins
19
+ * into the temp server so discovery resolves modules the same way the real
20
+ * environment does.
15
21
  */
16
22
 
17
23
  import type { Plugin, ResolvedConfig, UserConfig } from "vite";
@@ -120,22 +126,31 @@ export function selectForwardableResolvePlugins(
120
126
  /**
121
127
  * The resolution-relevant slice of the user's resolved config that is plain
122
128
  * data (no plugin re-execution): everything under `resolve` that influences
123
- * how specifiers map to files, plus `define` and `esbuild` so transforms and
129
+ * how specifiers map to files, plus `define` and `oxc` so transforms and
124
130
  * compile-time constants match request time.
125
131
  */
126
132
  export interface ForwardedRunnerConfig {
127
133
  resolve: UserConfig["resolve"];
128
134
  define: UserConfig["define"];
129
- esbuild: UserConfig["esbuild"];
135
+ oxc: UserConfig["oxc"];
130
136
  }
131
137
 
132
138
  /**
133
139
  * Extract the data-only config slice to mirror into the discovery temp server.
134
140
  * `alias` is included here so callers no longer need to thread it separately.
135
141
  *
136
- * `esbuild` keeps the user's options but always pins the RSC-required JSX
137
- * runtime, since the temp server compiles the handler graph as React server
138
- * components regardless of the user's app-level JSX config.
142
+ * `tsconfigPaths` is forwarded so Vite 8's native tsconfig `paths` resolution
143
+ * (a top-level `resolve` flag, off by default) reaches the temp server. The
144
+ * server is created with `configFile: false` and an explicit, allowlisted
145
+ * resolve slice, so a flag that is not copied here is simply absent during
146
+ * discovery — which would make path-aliased imports fail at prerender/static
147
+ * time the same way unforwarded resolveId plugins did (issue #500).
148
+ *
149
+ * `oxc` keeps the user's options but always pins the RSC-required JSX runtime
150
+ * (automatic, react), since the temp server compiles the handler graph as
151
+ * React server components regardless of the user's app-level JSX config. Vite 8
152
+ * replaced the deprecated `esbuild` transform option with `oxc`, so we read and
153
+ * forward `oxc` exclusively — no `esbuild` field is touched.
139
154
  */
140
155
  export function pickForwardedRunnerConfig(
141
156
  config: ResolvedConfig,
@@ -149,16 +164,30 @@ export function pickForwardedRunnerConfig(
149
164
  if (r.extensions !== undefined) resolve.extensions = r.extensions;
150
165
  if (r.preserveSymlinks !== undefined)
151
166
  resolve.preserveSymlinks = r.preserveSymlinks;
167
+ if (r.tsconfigPaths !== undefined) resolve.tsconfigPaths = r.tsconfigPaths;
152
168
 
153
- const userEsbuild = config.esbuild;
154
- const esbuild: UserConfig["esbuild"] =
155
- userEsbuild && typeof userEsbuild === "object"
156
- ? { ...userEsbuild, jsx: "automatic", jsxImportSource: "react" }
157
- : { jsx: "automatic", jsxImportSource: "react" };
169
+ // Pin the RSC JSX runtime on top of the user's oxc options. The user's
170
+ // jsx sub-options (e.g. `development`) are preserved when present; only
171
+ // `runtime`/`importSource` are forced to the values the RSC compile needs.
172
+ const userOxc = config.oxc;
173
+ const userJsx =
174
+ userOxc &&
175
+ typeof userOxc === "object" &&
176
+ typeof userOxc.jsx === "object" &&
177
+ userOxc.jsx !== null
178
+ ? userOxc.jsx
179
+ : {};
180
+ const oxc: UserConfig["oxc"] =
181
+ userOxc && typeof userOxc === "object"
182
+ ? {
183
+ ...userOxc,
184
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" },
185
+ }
186
+ : { jsx: { runtime: "automatic", importSource: "react" } };
158
187
 
159
188
  return {
160
189
  resolve,
161
190
  define: config.define,
162
- esbuild,
191
+ oxc,
163
192
  };
164
193
  }
@@ -6,39 +6,40 @@ import {
6
6
  VIRTUAL_ENTRY_BROWSER,
7
7
  VIRTUAL_ENTRY_SSR,
8
8
  getVirtualEntryRSC,
9
+ getVirtualVersionContent,
9
10
  VIRTUAL_IDS,
10
11
  } from "../plugins/virtual-entries.js";
11
12
 
12
13
  /**
13
- * esbuild plugin to provide rsc-router:version virtual module during optimization.
14
- * This is needed because esbuild runs during Vite's dependency optimization phase,
15
- * before Vite's plugin system can handle virtual modules.
14
+ * Rolldown plugin to provide the version virtual module during dependency
15
+ * optimization. Vite 8 optimizes deps with Rolldown (a Rollup-style plugin
16
+ * pipeline that is separate from the main plugin set), so this is a
17
+ * resolveId/load plugin under optimizeDeps.rolldownOptions. Any dep pulled into
18
+ * optimization that imports the version virtual module gets a "dev" stub here;
19
+ * the real VERSION is injected into runtime modules by the version plugin.
16
20
  */
17
- const versionEsbuildPlugin = {
21
+ const versionRolldownPlugin = {
18
22
  name: "@rangojs/router-version",
19
- setup(build: any): void {
20
- build.onResolve({ filter: /^rsc-router:version$/ }, (args: any) => ({
21
- path: args.path,
22
- namespace: "@rangojs/router-virtual",
23
- }));
24
- build.onLoad(
25
- { filter: /.*/, namespace: "@rangojs/router-virtual" },
26
- () => ({
27
- contents: `export const VERSION = "dev";`,
28
- loader: "js",
29
- }),
30
- );
23
+ resolveId(id: string): string | undefined {
24
+ if (id === VIRTUAL_IDS.version) return "\0" + VIRTUAL_IDS.version;
25
+ return undefined;
26
+ },
27
+ load(id: string): string | undefined {
28
+ if (id === "\0" + VIRTUAL_IDS.version) {
29
+ return getVirtualVersionContent("dev");
30
+ }
31
+ return undefined;
31
32
  },
32
33
  };
33
34
 
34
35
  /**
35
- * Shared esbuild options for dependency optimization.
36
- * Includes the version stub plugin for all environments.
36
+ * Shared Rolldown options for dependency optimization (Vite 8).
37
+ * Includes the version stub plugin and the performance-tracks RSDW patch.
37
38
  */
38
- export const sharedEsbuildOptions: {
39
+ export const sharedRolldownOptions: {
39
40
  plugins: any[];
40
41
  } = {
41
- plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()],
42
+ plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()],
42
43
  };
43
44
 
44
45
  /**
@@ -108,7 +109,9 @@ export function createVirtualEntriesPlugin(
108
109
  * - "use client" directives: handled by the RSC plugin, not relevant to Rollup
109
110
  * - sourcemap errors: caused by "use client" directive at line 1:0 confusing sourcemap resolution
110
111
  * - sourcemap incomplete: plugins that transform without generating sourcemaps (router + RSC plugin)
111
- * - dynamic/static mixed imports: expected for router internals (e.g. request-context, cache-scope)
112
+ * - dynamic/static mixed imports: expected for router internals (e.g. request-context, cache-scope).
113
+ * Under Rolldown (Vite 8) this surfaces as the INEFFECTIVE_DYNAMIC_IMPORT code emitted directly
114
+ * by the bundler, rather than the vite:reporter message handled below (Rollup/Vite 7 shape).
112
115
  * - empty bundle: @vitejs/plugin-rsc scan build (step 1/5) produces an empty "index" chunk
113
116
  * because the RSC entry is fully externalized during client-reference analysis
114
117
  */
@@ -119,7 +122,8 @@ export function onwarn(
119
122
  if (
120
123
  warning.code === "MODULE_LEVEL_DIRECTIVE" ||
121
124
  warning.code === "SOURCEMAP_ERROR" ||
122
- warning.code === "EMPTY_BUNDLE"
125
+ warning.code === "EMPTY_BUNDLE" ||
126
+ warning.code === "INEFFECTIVE_DYNAMIC_IMPORT"
123
127
  ) {
124
128
  return;
125
129
  }