vite-plugin-react-server 1.3.6 → 1.4.1

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 (56) hide show
  1. package/README.md +33 -19
  2. package/dist/package.json +4 -2
  3. package/dist/plugin/config/resolveOptions.d.ts.map +1 -1
  4. package/dist/plugin/config/resolveOptions.js +4 -2
  5. package/dist/plugin/dev-server/configureReactServer.client.js +1 -1
  6. package/dist/plugin/dev-server/plugin.client.d.ts.map +1 -1
  7. package/dist/plugin/dev-server/plugin.client.js +17 -15
  8. package/dist/plugin/dev-server/plugin.server.d.ts.map +1 -1
  9. package/dist/plugin/dev-server/plugin.server.js +54 -4
  10. package/dist/plugin/helpers/requestInfo.d.ts.map +1 -1
  11. package/dist/plugin/helpers/requestInfo.js +4 -3
  12. package/dist/plugin/helpers/requestToRoute.d.ts.map +1 -1
  13. package/dist/plugin/helpers/requestToRoute.js +2 -2
  14. package/dist/plugin/react-static/plugin.server.d.ts.map +1 -1
  15. package/dist/plugin/react-static/plugin.server.js +9 -1
  16. package/dist/plugin/react-static/renderPagesBatched.d.ts.map +1 -1
  17. package/dist/plugin/react-static/renderPagesBatched.js +136 -36
  18. package/dist/plugin/react-static/types.d.ts +1 -0
  19. package/dist/plugin/react-static/types.d.ts.map +1 -1
  20. package/dist/plugin/types.d.ts +15 -0
  21. package/dist/plugin/types.d.ts.map +1 -1
  22. package/dist/plugin/utils/createReactFetcher.js +24 -2
  23. package/dist/plugin/utils/useRscHmr.js +10 -2
  24. package/dist/plugin/vendor/register-vendor.js +1 -1
  25. package/dist/plugin/vendor/vendor-alias.d.ts +5 -2
  26. package/dist/plugin/vendor/vendor-alias.d.ts.map +1 -1
  27. package/dist/plugin/vendor/vendor-alias.js +60 -26
  28. package/dist/plugin/vendor/vendor.server.d.ts.map +1 -1
  29. package/dist/plugin/vendor/vendor.server.js +2 -2
  30. package/dist/plugin/vendor/vendor.static.d.ts.map +1 -1
  31. package/dist/plugin/vendor/vendor.static.js +2 -2
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +1 -1
  34. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.production.js +1 -1
  35. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +1 -1
  36. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.production.js +1 -1
  37. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1 -1
  38. package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +1 -1
  39. package/oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +1 -1
  40. package/oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.production.js +1 -1
  41. package/oss-experimental/react-server-dom-esm/package.json +3 -3
  42. package/package.json +4 -2
  43. package/plugin/config/resolveOptions.ts +2 -0
  44. package/plugin/dev-server/configureReactServer.client.ts +1 -1
  45. package/plugin/dev-server/plugin.client.ts +23 -20
  46. package/plugin/dev-server/plugin.server.ts +71 -4
  47. package/plugin/helpers/requestInfo.ts +6 -3
  48. package/plugin/helpers/requestToRoute.ts +3 -2
  49. package/plugin/react-static/plugin.server.ts +11 -1
  50. package/plugin/react-static/renderPagesBatched.ts +148 -39
  51. package/plugin/react-static/types.ts +1 -0
  52. package/plugin/types.ts +15 -0
  53. package/plugin/vendor/register-vendor.ts +1 -1
  54. package/plugin/vendor/vendor-alias.ts +61 -39
  55. package/plugin/vendor/vendor.server.ts +3 -3
  56. package/plugin/vendor/vendor.static.ts +3 -2
@@ -10,6 +10,8 @@ import type { RenderPagesFn, RenderPageFn, RenderPagesHandlerOptions } from "./t
10
10
  import { handleError } from "../error/handleError.js";
11
11
  import { fileWriter } from "./fileWriter.js";
12
12
  import type { Manifest } from "vite";
13
+ import { createRenderMetrics } from "../metrics/createRenderMetrics.js";
14
+ import { createStreamMetrics } from "../metrics/createStreamMetrics.js";
13
15
 
14
16
  const DEFAULT_BATCH_SIZE = 8;
15
17
 
@@ -23,13 +25,15 @@ function resolvePathWithManifest(path: string, manifest: Manifest): string {
23
25
 
24
26
  /**
25
27
  * Renders a single route completely, consuming all yields from renderPage
26
- * and writing the RSC and HTML files
28
+ * and writing the RSC and HTML files. Collects metrics and handles events
29
+ * identically to the sequential renderPages.
27
30
  */
28
31
  async function renderSingleRoute(
29
32
  route: string,
30
33
  handlerOptions: RenderPagesHandlerOptions,
31
34
  renderPage: RenderPageFn,
32
35
  manifest: Manifest,
36
+ failedRoutes: Map<string, unknown>,
33
37
  ): Promise<{ route: string; results: RenderPageResult[]; error?: Error }> {
34
38
  const { autoDiscoveredFiles, cssFilesByPage, ...options } = handlerOptions;
35
39
  const { page, props, root, html } = autoDiscoveredFiles.urlMap?.get(route) || {};
@@ -44,6 +48,131 @@ async function renderSingleRoute(
44
48
  const resolvedRootPath = root ? resolvePathWithManifest(root, manifest) : undefined;
45
49
  const resolvedHtmlPath = html ? resolvePathWithManifest(html, manifest) : undefined;
46
50
 
51
+ // Store results for metrics tracking
52
+ const routeResults = new Map<string, RenderPageResult>();
53
+
54
+ // Create onEvent wrapper that handles route.error and metrics collection
55
+ // This mirrors the sequential renderPages behavior exactly
56
+ const wrapperOnEvent = (event: any) => {
57
+ // Call the original onEvent first
58
+ if (options.onEvent) {
59
+ options.onEvent(event);
60
+ }
61
+
62
+ // Handle route.error events
63
+ if (event.type === "route.error") {
64
+ const detectedPanicError = handleError({
65
+ error: event.data.error,
66
+ logger: options.logger,
67
+ panicThreshold: event.data.panicThreshold,
68
+ context: `route.error (${event.data.route})`,
69
+ });
70
+
71
+ if (detectedPanicError != null) {
72
+ options.logger?.error(
73
+ `[renderPagesBatched] Panic error for route ${event.data.route}: ${event.data.error.message}`
74
+ );
75
+ failedRoutes.set(event.data.route, event.data.error);
76
+ } else {
77
+ options.logger?.warn(
78
+ `[renderPagesBatched] Non-panic error for route ${event.data.route}: ${event.data.error.message}`
79
+ );
80
+ }
81
+ }
82
+
83
+ // Handle metrics collection on file.write.done
84
+ if (event.type === "file.write.done" && event.data.route === route) {
85
+ const routeResult = routeResults.get(route);
86
+ if (routeResult && routeResult.type === "success") {
87
+ if (event.data.fileType === "html") {
88
+ const endTime = performance.now();
89
+ const htmlMetrics = createRenderMetrics({
90
+ route: route,
91
+ type: routeResult.metrics.html.type,
92
+ fromMainThread: routeResult.metrics.html.fromMainThread,
93
+ fromRscWorker: routeResult.metrics.html.fromRscWorker,
94
+ fromHtmlWorker: routeResult.metrics.html.fromHtmlWorker,
95
+ fileSize: event.data.content.length,
96
+ chunks: event.data.chunks || 0,
97
+ processingTime: endTime - routeResult.metrics.html.streamMetrics.startTime,
98
+ chunkRate: (event.data.chunks || 0) / ((endTime - routeResult.metrics.html.streamMetrics.startTime) / 1000),
99
+ fileName: event.data.fileName,
100
+ outputPath: event.data.path,
101
+ baseDir: event.data.baseDir,
102
+ routePath: event.data.routePath,
103
+ streamMetrics: createStreamMetrics({
104
+ ...routeResult.metrics.html.streamMetrics,
105
+ chunks: event.data.chunks || 0,
106
+ bytes: event.data.content.length,
107
+ duration: endTime - routeResult.metrics.html.streamMetrics.startTime,
108
+ endTime: endTime,
109
+ }),
110
+ });
111
+
112
+ if (options.onMetrics) {
113
+ options.onMetrics(htmlMetrics);
114
+ }
115
+
116
+ // Also emit RSC Full metrics if available
117
+ if (routeResult.metrics?.rscFull) {
118
+ const rscFullEndTime = performance.now();
119
+ const rscFullMetrics = createRenderMetrics({
120
+ route: route,
121
+ type: routeResult.metrics.rscFull.type,
122
+ fromMainThread: routeResult.metrics.rscFull.fromMainThread,
123
+ fromRscWorker: routeResult.metrics.rscFull.fromRscWorker,
124
+ fromHtmlWorker: routeResult.metrics.rscFull.fromHtmlWorker,
125
+ processingTime: rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime,
126
+ chunks: routeResult.metrics.rscFull.streamMetrics.chunks,
127
+ chunkRate: routeResult.metrics.rscFull.streamMetrics.chunks / ((rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime) / 1000),
128
+ fileName: event.data.fileName,
129
+ outputPath: event.data.path,
130
+ baseDir: event.data.baseDir,
131
+ routePath: event.data.routePath,
132
+ streamMetrics: createStreamMetrics({
133
+ ...routeResult.metrics.rscFull.streamMetrics,
134
+ duration: rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime,
135
+ endTime: rscFullEndTime,
136
+ }),
137
+ });
138
+
139
+ if (options.onMetrics) {
140
+ options.onMetrics(rscFullMetrics);
141
+ }
142
+ }
143
+ } else if (event.data.fileType === "rsc") {
144
+ const rscEndTime = performance.now();
145
+ const rscMetrics = createRenderMetrics({
146
+ route: route,
147
+ type: routeResult.metrics.rscHeadless.type,
148
+ fromMainThread: routeResult.metrics.rscHeadless.fromMainThread,
149
+ fromRscWorker: routeResult.metrics.rscHeadless.fromRscWorker,
150
+ fromHtmlWorker: routeResult.metrics.rscHeadless.fromHtmlWorker,
151
+ fileSize: event.data.content.length,
152
+ chunks: event.data.chunks || 0,
153
+ processingTime: rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime,
154
+ chunkRate: (event.data.chunks || 0) / ((rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime) / 1000),
155
+ fileName: event.data.fileName,
156
+ outputPath: event.data.path,
157
+ baseDir: event.data.baseDir,
158
+ routePath: event.data.routePath,
159
+ streamMetrics: createStreamMetrics({
160
+ ...routeResult.metrics.rscHeadless.streamMetrics,
161
+ chunks: event.data.chunks || 0,
162
+ bytes: event.data.content.length,
163
+ duration: rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime,
164
+ endTime: rscEndTime,
165
+ }),
166
+ });
167
+
168
+ if (options.onMetrics) {
169
+ options.onMetrics(rscMetrics);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ };
175
+
47
176
  const routeHandlerOptions = {
48
177
  ...options,
49
178
  manifest,
@@ -55,6 +184,7 @@ async function renderSingleRoute(
55
184
  cssFiles: cssFilesByPage?.get(route) ?? new Map(),
56
185
  globalCss: options.globalCss ?? new Map(),
57
186
  id: `${route}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
187
+ onEvent: wrapperOnEvent,
58
188
  };
59
189
 
60
190
  const pageRenderer = renderPage(routeHandlerOptions);
@@ -65,24 +195,25 @@ async function renderSingleRoute(
65
195
  for await (const result of pageRenderer) {
66
196
  results.push(result);
67
197
 
68
- // Track error results
69
198
  if (result.type === "error" && result.error) {
70
199
  routeError = result.error instanceof Error ? result.error : new Error(String(result.error));
71
200
  }
72
201
 
73
- // Write files for success and skip results
74
- if ((result.type === "success" || result.type === "skip") && result.html && result.rsc) {
202
+ if (result.type === "success" || result.type === "skip") {
203
+ // Store result for metrics tracking (wrapperOnEvent needs this)
204
+ routeResults.set(route, result);
205
+
75
206
  const rscWritePromise = fileWriter(
76
207
  result.rsc as any,
77
208
  "rsc",
78
- { ...options, route, logger: options.logger },
209
+ { ...options, route, onEvent: wrapperOnEvent, logger: options.logger },
79
210
  options.signal
80
211
  );
81
212
 
82
213
  const htmlWritePromise = fileWriter(
83
214
  result.html as any,
84
215
  "html",
85
- { ...options, route, logger: options.logger },
216
+ { ...options, route, onEvent: wrapperOnEvent, logger: options.logger },
86
217
  options.signal
87
218
  );
88
219
 
@@ -90,7 +221,6 @@ async function renderSingleRoute(
90
221
  }
91
222
  }
92
223
 
93
- // Return error if any result was an error
94
224
  if (routeError) {
95
225
  return { route, results, error: routeError };
96
226
  }
@@ -173,7 +303,7 @@ export const renderPagesBatched: RenderPagesFn = (
173
303
 
174
304
  // Render all pages in this batch concurrently
175
305
  const batchPromises = batch.map(route =>
176
- renderSingleRoute(route, handlerOptions, renderPage, manifest)
306
+ renderSingleRoute(route, handlerOptions, renderPage, manifest, failedRoutes)
177
307
  );
178
308
 
179
309
  const batchResults = await Promise.all(batchPromises);
@@ -191,13 +321,22 @@ export const renderPagesBatched: RenderPagesFn = (
191
321
  if (panicError != null) {
192
322
  failedRoutes.set(route, error);
193
323
  options.logger?.error(`[renderPagesBatched] Panic error for route ${route}: ${error.message}`);
324
+ const errorResult: RenderPagesResult = {
325
+ type: "error",
326
+ error,
327
+ route,
328
+ failedRoutes,
329
+ completedRoutes,
330
+ results,
331
+ };
332
+ yield errorResult;
333
+ return errorResult;
194
334
  } else {
195
335
  options.logger?.warn(`[renderPagesBatched] Non-panic error for route ${route}: ${error.message}`);
196
336
  }
197
337
  } else {
198
338
  completedRoutes.add(route);
199
339
 
200
- // Yield each result from this page
201
340
  for (const result of pageResults) {
202
341
  if (result.type === "success" || result.type === "skip") {
203
342
  results.set(route, result);
@@ -220,36 +359,6 @@ export const renderPagesBatched: RenderPagesFn = (
220
359
  }
221
360
  }
222
361
 
223
- // Check if we should panic based on failed routes
224
- if (failedRoutes.size > 0) {
225
- const firstError = Array.from(failedRoutes.values())[0];
226
- const panicError = handleError({
227
- error: firstError,
228
- logger: options.logger,
229
- panicThreshold: options.panicThreshold,
230
- context: `renderPagesBatched final check`,
231
- });
232
-
233
- if (panicError != null) {
234
- if (options.verbose) {
235
- options.logger?.error(
236
- `[renderPagesBatched] Build failed due to panic threshold: ${failedRoutes.size} routes failed`
237
- );
238
- }
239
- // Yield error before returning
240
- const errorResult: RenderPagesResult = {
241
- type: "error",
242
- error: panicError,
243
- route: "",
244
- failedRoutes,
245
- completedRoutes,
246
- results,
247
- };
248
- yield errorResult;
249
- return errorResult;
250
- }
251
- }
252
-
253
362
  // Final success result
254
363
  const finalResult: RenderPagesResult = {
255
364
  type: "success",
@@ -133,6 +133,7 @@ export type RenderPagesHandlerOptions = Omit<
133
133
  cssFilesByPage: Map<string, Map<string, CssContent>>;
134
134
  serverPipeableStreamOptions: any;
135
135
  staticManifest?: Manifest; // Static manifest for consistent module IDs
136
+ batchSize?: number; // Concurrency for parallel rendering
136
137
  };
137
138
 
138
139
  export type RenderPagesFn = (
package/plugin/types.ts CHANGED
@@ -1102,6 +1102,21 @@ export type BuildConfig = {
1102
1102
  rscExtension?: string;
1103
1103
  cssModuleExtension?: string;
1104
1104
  nodeExtension?: string;
1105
+ /**
1106
+ * Controls how pages are rendered during static generation.
1107
+ *
1108
+ * - `"parallel"` (default): Renders pages in concurrent batches for faster builds.
1109
+ * Use `batchSize` to control concurrency (default: 8).
1110
+ * - `"sequential"`: Renders pages one at a time. Slower but uses less memory
1111
+ * and produces deterministic output order.
1112
+ */
1113
+ renderMode?: "parallel" | "sequential";
1114
+ /**
1115
+ * Number of pages to render concurrently when `renderMode` is `"parallel"`.
1116
+ * Higher values use more memory but build faster.
1117
+ * @default 8
1118
+ */
1119
+ batchSize?: number;
1105
1120
  };
1106
1121
 
1107
1122
  export type DevConfig = {
@@ -35,7 +35,7 @@ register("data:text/javascript," + encodeURIComponent(`
35
35
  "react-server-dom-esm/client.node": join(ossDir, "client.node.js"),
36
36
  "react-server-dom-esm/server": join(ossDir, "esm", "react-server-dom-esm-server.node.js"),
37
37
  "react-server-dom-esm/server.node": join(ossDir, "esm", "react-server-dom-esm-server.node.js"),
38
- "react-server-dom-esm/static": join(ossDir, "static.js"),
38
+ "react-server-dom-esm/static": join(ossDir, "static.node.js"),
39
39
  "react-server-dom-esm/static.node": join(ossDir, "static.node.js"),
40
40
  };
41
41
 
@@ -1,7 +1,7 @@
1
1
  import type { Plugin } from "vite";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { existsSync } from "node:fs";
4
+ import { existsSync, lstatSync, readlinkSync, symlinkSync, unlinkSync } from "node:fs";
5
5
 
6
6
  // Find package root by walking up from current file until we find oss-experimental/
7
7
  // Works from both plugin/vendor/ (source) and dist/plugin/vendor/ (built)
@@ -23,12 +23,13 @@ const ossDir = join(pkgRoot, "oss-experimental");
23
23
  * install `react-server-dom-esm` separately or use patch-package.
24
24
  *
25
25
  * Browser client entries use true ESM files for Rollup tree-shaking.
26
- * Server/static entries are marked external during builds they're CJS
27
- * modules loaded at runtime via createRequire in vendor.*.ts files.
26
+ * Server/static entries are CJS and must be loadable via native Node import()
27
+ * (not eval'd as ESM by Vite's module runner, which lacks require()).
28
+ *
29
+ * In dev mode, we ensure the vendored package is reachable from node_modules
30
+ * so Vite's module runner can externalize and natively import() CJS entries.
28
31
  */
29
32
  export function vitePluginVendorAlias(): Plugin {
30
- let isBuild = false;
31
-
32
33
  return {
33
34
  name: "vite-plugin-react-server:vendor-alias",
34
35
  enforce: "pre",
@@ -36,17 +37,16 @@ export function vitePluginVendorAlias(): Plugin {
36
37
  config(_config, env) {
37
38
  const pkg = join(ossDir, "react-server-dom-esm");
38
39
  const isProd = env.mode === "production";
39
-
40
- // Only alias browser client to ESM for Rollup tree-shaking.
41
- // Server/static are handled by resolveId with external:true.
40
+
42
41
  return {
43
42
  resolve: {
44
43
  alias: [
45
- {
46
- find: "react-server-dom-esm/client.browser",
47
- replacement: join(pkg, "esm", isProd
48
- ? "react-server-dom-esm-client.browser.production.js"
49
- : "react-server-dom-esm-client.browser.development.js")
44
+ // Browser client → ESM for Rollup tree-shaking
45
+ {
46
+ find: "react-server-dom-esm/client.browser",
47
+ replacement: join(pkg, "esm", isProd
48
+ ? "react-server-dom-esm-client.browser.production.js"
49
+ : "react-server-dom-esm-client.browser.development.js")
50
50
  },
51
51
  ],
52
52
  },
@@ -54,42 +54,67 @@ export function vitePluginVendorAlias(): Plugin {
54
54
  },
55
55
 
56
56
  configResolved(config) {
57
- isBuild = config.command === "build";
57
+ // Allow serving vendored files when the plugin is linked or in a monorepo.
58
+ // Must be done in configResolved to append to the resolved allow list
59
+ // (setting in config hook can override Vite's defaults).
60
+ if (config.command === "serve" && config.server?.fs?.allow) {
61
+ if (!config.server.fs.allow.includes(pkgRoot)) {
62
+ config.server.fs.allow.push(pkgRoot);
63
+ }
64
+ }
65
+
66
+ // Ensure vendored package is reachable via Node resolution in ALL Vite
67
+ // contexts (dev server, vitest, SSR workers, custom scripts).
68
+ // Vite's module runner resolves bare imports via Node — not plugin hooks —
69
+ // so the package must be in node_modules for CJS entries to work.
70
+ ensureVendoredPackageLinked(config.root);
58
71
  },
59
72
 
60
73
  resolveId(source) {
61
- // Only handle react-server-dom-esm specifiers (not already aliased paths)
62
- if (!source.startsWith("react-server-dom-esm")) {
63
- return;
64
- }
65
-
66
- // Skip client.browser — handled by config alias above
67
- if (source === "react-server-dom-esm/client.browser") {
68
- return;
69
- }
74
+ if (!source.startsWith("react-server-dom-esm")) return;
75
+ if (source === "react-server-dom-esm/client.browser") return;
70
76
 
71
- // For server/static entries during build: mark external with resolved path.
72
- // At runtime, vendor.*.ts uses createRequire to load from this path.
73
- if (isBuild && isServerEntry(source)) {
74
- const resolved = resolveVendored(source);
75
- return { id: resolved, external: true };
77
+ // Server/static entries: mark external so the runner/bundler uses native
78
+ // import() rather than eval(). The resolved path points into the vendored
79
+ // copy (reachable via symlink in dev, directly in build).
80
+ if (isServerEntry(source)) {
81
+ return { id: resolveVendored(source), external: true };
76
82
  }
77
83
 
78
- // For all other entries (client.node, client, index), resolve to vendored path
79
84
  return resolveVendored(source);
80
85
  },
81
86
  };
82
87
  }
83
88
 
89
+ /**
90
+ * Ensure `node_modules/react-server-dom-esm` links to the vendored copy.
91
+ * Only creates a symlink if no real install exists. Safe to call multiple times.
92
+ */
93
+ function ensureVendoredPackageLinked(root?: string): void {
94
+ const pkg = join(ossDir, "react-server-dom-esm");
95
+ const target = join(root ?? process.cwd(), "node_modules", "react-server-dom-esm");
96
+ try {
97
+ const stat = (() => { try { return lstatSync(target); } catch { return null; } })();
98
+ if (stat?.isSymbolicLink()) {
99
+ // Update symlink if it points elsewhere
100
+ if (readlinkSync(target) !== pkg) {
101
+ unlinkSync(target);
102
+ symlinkSync(pkg, target, "junction");
103
+ }
104
+ } else if (!stat) {
105
+ // No existing file — create symlink
106
+ symlinkSync(pkg, target, "junction");
107
+ }
108
+ // If a real directory/file exists (user installed it), leave it alone
109
+ } catch {
110
+ // Non-fatal: symlink creation can fail on some systems
111
+ }
112
+ }
113
+
84
114
  function isServerEntry(source: string): boolean {
85
- return (
86
- source.includes("/server") ||
87
- source.includes("/static")
88
- );
115
+ return source.includes("/server") || source.includes("/static");
89
116
  }
90
117
 
91
- // Explicit subpath → file mapping. Server entries always resolve to .node
92
- // variants to bypass the react-server condition guard in server.js.
93
118
  const subpathMap: Record<string, string> = {
94
119
  "react-server-dom-esm": "index.js",
95
120
  "react-server-dom-esm/client": "client.js",
@@ -103,10 +128,7 @@ const subpathMap: Record<string, string> = {
103
128
 
104
129
  function resolveVendored(source: string): string {
105
130
  const file = subpathMap[source];
106
- if (file) {
107
- return join(ossDir, "react-server-dom-esm", file);
108
- }
109
- // Fallback for unknown subpaths
131
+ if (file) return join(ossDir, "react-server-dom-esm", file);
110
132
  const subpath = source.replace("react-server-dom-esm", "");
111
133
  return join(ossDir, "react-server-dom-esm", subpath || "index.js");
112
134
  }
@@ -14,10 +14,10 @@ function findPkgRoot(): string {
14
14
  }
15
15
  const ossDir = join(findPkgRoot(), "oss-experimental");
16
16
 
17
- // Load react-server-dom-esm/server.node directly from vendored copy
18
- // Use server.node.js (not server.js which is a react-server condition guard)
17
+ // Load react-server-dom-esm/server from vendored copy
18
+ // The vendored package.json exports map defaults to server.node.js
19
19
  const vendorRequire = createRequire(join(ossDir, "react-server-dom-esm", "package.json"));
20
- const ReactDOMServer = vendorRequire(join(ossDir, "react-server-dom-esm", "server.node.js")) as typeof import("react-server-dom-esm/server.node");
20
+ const ReactDOMServer = vendorRequire("react-server-dom-esm/server") as typeof import("react-server-dom-esm/server.node");
21
21
 
22
22
  // React still comes from the consumer's project
23
23
  const projectRoot = process.env["npm_config_local_prefix"] || process.cwd();
@@ -14,9 +14,10 @@ function findPkgRoot(): string {
14
14
  }
15
15
  const ossDir = join(findPkgRoot(), "oss-experimental");
16
16
 
17
- // Load react-server-dom-esm/static.node directly from vendored copy
17
+ // Load react-server-dom-esm/static from vendored copy
18
+ // The vendored package.json exports map defaults to static.node.js
18
19
  const vendorRequire = createRequire(join(ossDir, "react-server-dom-esm", "package.json"));
19
- const ReactDOMServer = vendorRequire(join(ossDir, "react-server-dom-esm", "static.node.js")) as typeof import("react-server-dom-esm/static.node");
20
+ const ReactDOMServer = vendorRequire("react-server-dom-esm/static") as typeof import("react-server-dom-esm/static.node");
20
21
 
21
22
  // React still comes from the consumer's project
22
23
  const projectRoot = process.env["npm_config_local_prefix"] || process.cwd();