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.
- package/README.md +33 -19
- package/dist/package.json +4 -2
- package/dist/plugin/config/resolveOptions.d.ts.map +1 -1
- package/dist/plugin/config/resolveOptions.js +4 -2
- package/dist/plugin/dev-server/configureReactServer.client.js +1 -1
- package/dist/plugin/dev-server/plugin.client.d.ts.map +1 -1
- package/dist/plugin/dev-server/plugin.client.js +17 -15
- package/dist/plugin/dev-server/plugin.server.d.ts.map +1 -1
- package/dist/plugin/dev-server/plugin.server.js +54 -4
- package/dist/plugin/helpers/requestInfo.d.ts.map +1 -1
- package/dist/plugin/helpers/requestInfo.js +4 -3
- package/dist/plugin/helpers/requestToRoute.d.ts.map +1 -1
- package/dist/plugin/helpers/requestToRoute.js +2 -2
- package/dist/plugin/react-static/plugin.server.d.ts.map +1 -1
- package/dist/plugin/react-static/plugin.server.js +9 -1
- package/dist/plugin/react-static/renderPagesBatched.d.ts.map +1 -1
- package/dist/plugin/react-static/renderPagesBatched.js +136 -36
- package/dist/plugin/react-static/types.d.ts +1 -0
- package/dist/plugin/react-static/types.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +15 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/utils/createReactFetcher.js +24 -2
- package/dist/plugin/utils/useRscHmr.js +10 -2
- package/dist/plugin/vendor/register-vendor.js +1 -1
- package/dist/plugin/vendor/vendor-alias.d.ts +5 -2
- package/dist/plugin/vendor/vendor-alias.d.ts.map +1 -1
- package/dist/plugin/vendor/vendor-alias.js +60 -26
- package/dist/plugin/vendor/vendor.server.d.ts.map +1 -1
- package/dist/plugin/vendor/vendor.server.js +2 -2
- package/dist/plugin/vendor/vendor.static.d.ts.map +1 -1
- package/dist/plugin/vendor/vendor.static.js +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.production.js +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.production.js +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1 -1
- package/oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +1 -1
- package/oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +1 -1
- package/oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.production.js +1 -1
- package/oss-experimental/react-server-dom-esm/package.json +3 -3
- package/package.json +4 -2
- package/plugin/config/resolveOptions.ts +2 -0
- package/plugin/dev-server/configureReactServer.client.ts +1 -1
- package/plugin/dev-server/plugin.client.ts +23 -20
- package/plugin/dev-server/plugin.server.ts +71 -4
- package/plugin/helpers/requestInfo.ts +6 -3
- package/plugin/helpers/requestToRoute.ts +3 -2
- package/plugin/react-static/plugin.server.ts +11 -1
- package/plugin/react-static/renderPagesBatched.ts +148 -39
- package/plugin/react-static/types.ts +1 -0
- package/plugin/types.ts +15 -0
- package/plugin/vendor/register-vendor.ts +1 -1
- package/plugin/vendor/vendor-alias.ts +61 -39
- package/plugin/vendor/vendor.server.ts +3 -3
- 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
|
-
|
|
74
|
-
|
|
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
|
|
27
|
-
*
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
if (
|
|
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
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return { id:
|
|
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
|
|
18
|
-
//
|
|
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(
|
|
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
|
|
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(
|
|
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();
|