vite-plugin-react-server 1.1.7 → 1.1.8
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/package.json +10 -5
- package/dist/plugin/config/defaults.d.ts +1 -1
- package/dist/plugin/config/defaults.js +1 -1
- package/dist/plugin/config/defaults.js.map +1 -1
- package/dist/plugin/config/resolveAutoDiscover.d.ts +2 -0
- package/dist/plugin/config/resolveAutoDiscover.d.ts.map +1 -1
- package/dist/plugin/config/resolveAutoDiscover.js +15 -18
- package/dist/plugin/config/resolveAutoDiscover.js.map +1 -1
- package/dist/plugin/config/resolveOptions.d.ts.map +1 -1
- package/dist/plugin/config/resolveOptions.js +4 -1
- package/dist/plugin/config/resolveOptions.js.map +1 -1
- package/dist/plugin/config/resolveUserConfig.d.ts.map +1 -1
- package/dist/plugin/config/resolveUserConfig.js +56 -29
- package/dist/plugin/config/resolveUserConfig.js.map +1 -1
- package/dist/plugin/helpers/collectBundleManifestCss.d.ts +0 -6
- package/dist/plugin/helpers/collectBundleManifestCss.d.ts.map +1 -1
- package/dist/plugin/helpers/collectBundleManifestCss.js +2 -110
- package/dist/plugin/helpers/collectViteModuleGraphCss.d.ts +2 -1
- package/dist/plugin/helpers/collectViteModuleGraphCss.d.ts.map +1 -1
- package/dist/plugin/helpers/collectViteModuleGraphCss.js +19 -18
- package/dist/plugin/helpers/collectViteModuleGraphCss.js.map +1 -1
- package/dist/plugin/helpers/createCssProps.d.ts +3 -2
- package/dist/plugin/helpers/createCssProps.d.ts.map +1 -1
- package/dist/plugin/helpers/createCssProps.js +10 -6
- package/dist/plugin/helpers/createCssProps.js.map +1 -1
- package/dist/plugin/helpers/createRscStream.d.ts.map +1 -1
- package/dist/plugin/helpers/createRscStream.js +37 -43
- package/dist/plugin/helpers/createRscStream.js.map +1 -1
- package/dist/plugin/helpers/formatMetrics.d.ts +4 -0
- package/dist/plugin/helpers/formatMetrics.d.ts.map +1 -0
- package/dist/plugin/helpers/formatMetrics.js +24 -0
- package/dist/plugin/helpers/tryManifest.d.ts.map +1 -1
- package/dist/plugin/helpers/tryManifest.js +0 -8
- package/dist/plugin/helpers/tryManifest.js.map +1 -1
- package/dist/plugin/html.js +1 -1
- package/dist/plugin/html.js.map +1 -1
- package/dist/plugin/loader/createBuildLoader.d.ts.map +1 -1
- package/dist/plugin/loader/createBuildLoader.js +2 -0
- package/dist/plugin/loader/createBuildLoader.js.map +1 -1
- package/dist/plugin/metrics/formatMetrics.d.ts +4 -0
- package/dist/plugin/metrics/formatMetrics.d.ts.map +1 -0
- package/dist/plugin/metrics/formatMetrics.js +39 -0
- package/dist/plugin/metrics/formatMetrics.js.map +1 -0
- package/dist/plugin/metrics/index.d.ts +3 -0
- package/dist/plugin/metrics/index.d.ts.map +1 -0
- package/dist/plugin/metrics/index.js +1 -0
- package/dist/plugin/metrics.js +7 -0
- package/dist/plugin/metrics.js.map +1 -0
- package/dist/plugin/react-client/createWorkerStream.d.ts +16 -0
- package/dist/plugin/react-client/createWorkerStream.d.ts.map +1 -0
- package/dist/plugin/react-client/createWorkerStream.js +88 -0
- package/dist/plugin/react-client/createWorkerStream.js.map +1 -0
- package/dist/plugin/react-client/plugin.d.ts.map +1 -1
- package/dist/plugin/react-client/plugin.js +4 -1
- package/dist/plugin/react-client/plugin.js.map +1 -1
- package/dist/plugin/react-client/restartWorker.d.ts +6 -0
- package/dist/plugin/react-client/restartWorker.d.ts.map +1 -0
- package/dist/plugin/react-client/restartWorker.js +53 -0
- package/dist/plugin/react-client/restartWorker.js.map +1 -0
- package/dist/plugin/react-client/server.d.ts +5 -4
- package/dist/plugin/react-client/server.d.ts.map +1 -1
- package/dist/plugin/react-client/server.js +79 -110
- package/dist/plugin/react-client/server.js.map +1 -1
- package/dist/plugin/react-server/server.d.ts.map +1 -1
- package/dist/plugin/react-server/server.js +23 -28
- package/dist/plugin/react-server/server.js.map +1 -1
- package/dist/plugin/react-static/collectHtmlWorkerContent.js +1 -1
- package/dist/plugin/react-static/collectHtmlWorkerContent.js.map +1 -1
- package/dist/plugin/react-static/collectRscContent.js +1 -1
- package/dist/plugin/react-static/collectRscContent.js.map +1 -1
- package/dist/plugin/react-static/configurePreviewServer.d.ts.map +1 -1
- package/dist/plugin/react-static/configurePreviewServer.js +23 -4
- package/dist/plugin/react-static/configurePreviewServer.js.map +1 -1
- package/dist/plugin/react-static/fileWriter.d.ts.map +1 -1
- package/dist/plugin/react-static/fileWriter.js +5 -1
- package/dist/plugin/react-static/fileWriter.js.map +1 -1
- package/dist/plugin/react-static/plugin.d.ts.map +1 -1
- package/dist/plugin/react-static/plugin.js +50 -33
- package/dist/plugin/react-static/plugin.js.map +1 -1
- package/dist/plugin/types.d.ts +6 -7
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/utils/callServer.d.ts +2 -0
- package/dist/plugin/utils/callServer.d.ts.map +1 -0
- package/dist/plugin/utils/callServer.js +26 -0
- package/dist/plugin/utils/callServer.js.map +1 -0
- package/dist/plugin/utils/createReactFetcher.d.ts +7 -0
- package/dist/plugin/utils/createReactFetcher.d.ts.map +1 -0
- package/dist/plugin/utils/createReactFetcher.js +33 -0
- package/dist/plugin/utils/createReactFetcher.js.map +1 -0
- package/dist/plugin/utils/index.d.ts +4 -0
- package/dist/plugin/utils/index.d.ts.map +1 -0
- package/dist/plugin/utils/index.js +3 -0
- package/dist/plugin/utils/pageURL.d.ts +2 -0
- package/dist/plugin/utils/pageURL.d.ts.map +1 -0
- package/dist/plugin/utils/pageURL.js +21 -0
- package/dist/plugin/utils/pageURL.js.map +1 -0
- package/dist/plugin/utils.js +9 -0
- package/dist/plugin/utils.js.map +1 -0
- package/dist/plugin/worker/rsc/handleRender.d.ts +6 -2
- package/dist/plugin/worker/rsc/handleRender.d.ts.map +1 -1
- package/dist/plugin/worker/rsc/handleRender.js +26 -55
- package/dist/plugin/worker/rsc/handleRender.js.map +1 -1
- package/dist/plugin/worker/rsc/messageHandler.d.ts +1 -2
- package/dist/plugin/worker/rsc/messageHandler.d.ts.map +1 -1
- package/dist/plugin/worker/rsc/messageHandler.js +45 -2
- package/dist/plugin/worker/rsc/messageHandler.js.map +1 -1
- package/dist/plugin/worker/rsc/state.d.ts.map +1 -1
- package/dist/plugin/worker/rsc/state.js +1 -5
- package/dist/plugin/worker/rsc/state.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -5
- package/plugin/config/defaults.tsx +1 -1
- package/plugin/config/resolveAutoDiscover.ts +17 -22
- package/plugin/config/resolveOptions.ts +5 -1
- package/plugin/config/resolveUserConfig.ts +64 -36
- package/plugin/helpers/collectBundleManifestCss.ts +1 -160
- package/plugin/helpers/collectViteModuleGraphCss.ts +31 -29
- package/plugin/helpers/createCssProps.tsx +22 -11
- package/plugin/helpers/createRscStream.tsx +42 -46
- package/plugin/helpers/formatMetrics.ts +37 -0
- package/plugin/helpers/tryManifest.ts +0 -9
- package/plugin/html.tsx +1 -1
- package/plugin/loader/createBuildLoader.ts +2 -0
- package/plugin/metrics/formatMetrics.ts +37 -0
- package/plugin/metrics/index.ts +2 -0
- package/plugin/react-client/createWorkerStream.ts +107 -0
- package/plugin/react-client/plugin.ts +3 -0
- package/plugin/react-client/restartWorker.ts +65 -0
- package/plugin/react-client/server.ts +97 -146
- package/plugin/react-server/server.ts +24 -29
- package/plugin/react-static/collectHtmlWorkerContent.ts +1 -1
- package/plugin/react-static/collectRscContent.ts +2 -2
- package/plugin/react-static/configurePreviewServer.ts +29 -6
- package/plugin/react-static/fileWriter.ts +5 -1
- package/plugin/react-static/plugin.ts +58 -38
- package/plugin/types.ts +11 -11
- package/plugin/utils/callServer.ts +25 -0
- package/plugin/utils/createReactFetcher.ts +31 -0
- package/plugin/utils/index.ts +3 -0
- package/plugin/utils/pageURL.ts +28 -0
- package/plugin/worker/rsc/handleRender.ts +33 -71
- package/plugin/worker/rsc/messageHandler.tsx +48 -6
- package/plugin/worker/rsc/state.ts +1 -5
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RenderMetrics } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export function formatMetrics(metrics: RenderMetrics): string {
|
|
4
|
+
const {
|
|
5
|
+
route,
|
|
6
|
+
rscSize,
|
|
7
|
+
chunks,
|
|
8
|
+
chunkRate,
|
|
9
|
+
processingTime,
|
|
10
|
+
memoryUsage,
|
|
11
|
+
streamMetrics,
|
|
12
|
+
} = metrics;
|
|
13
|
+
|
|
14
|
+
// Format memory usage in MB
|
|
15
|
+
const formatMemory = (bytes: number) => `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
16
|
+
|
|
17
|
+
return `
|
|
18
|
+
Route: ${route}
|
|
19
|
+
Size: ${(rscSize / 1024).toFixed(2)}KB
|
|
20
|
+
Chunks: ${chunks} (${chunkRate.toFixed(2)} chunks/s)
|
|
21
|
+
Processing Time: ${processingTime.toFixed(2)}ms
|
|
22
|
+
Memory:
|
|
23
|
+
RSS: ${formatMemory(memoryUsage.rss)}
|
|
24
|
+
Heap Total: ${formatMemory(memoryUsage.heapTotal)}
|
|
25
|
+
Heap Used: ${formatMemory(memoryUsage.heapUsed)}
|
|
26
|
+
External: ${formatMemory(memoryUsage.external)}
|
|
27
|
+
Stream:
|
|
28
|
+
Duration: ${streamMetrics.duration.toFixed(2)}ms
|
|
29
|
+
Backpressure: ${streamMetrics.backpressureCount}
|
|
30
|
+
Drain: ${streamMetrics.drainCount}
|
|
31
|
+
Errors: ${streamMetrics.errorCount}
|
|
32
|
+
`.trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function logMetrics(metrics: RenderMetrics) {
|
|
36
|
+
console.log(formatMetrics(metrics));
|
|
37
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Logger } from "vite";
|
|
2
|
+
import type { RscRenderMessage, RscWorkerOutputMessage } from "../worker/types.js";
|
|
3
|
+
import type { StreamMetrics } from "../../types.js";
|
|
4
|
+
import { Worker } from "node:worker_threads";
|
|
5
|
+
/**
|
|
6
|
+
* Creates an async generator that yields RSC chunks from the worker.
|
|
7
|
+
* Handles both module requests and RSC streaming.
|
|
8
|
+
*
|
|
9
|
+
* @param worker - The worker thread
|
|
10
|
+
* @param server - The Vite dev server
|
|
11
|
+
* @param message - The RSC render message
|
|
12
|
+
* @param rscWorkerLoaderPort - Optional loader port for module loading
|
|
13
|
+
* @returns An async generator that yields RSC chunks
|
|
14
|
+
*/
|
|
15
|
+
export async function* createWorkerStream(
|
|
16
|
+
worker: Worker,
|
|
17
|
+
message: Omit<RscRenderMessage, "type" | "id">,
|
|
18
|
+
logger: Logger,
|
|
19
|
+
onMetrics?: (metrics: StreamMetrics) => void
|
|
20
|
+
): AsyncGenerator<Uint8Array, void, unknown> {
|
|
21
|
+
let messageHandler: (message: RscWorkerOutputMessage) => void;
|
|
22
|
+
let cleanup: () => void = () => {};
|
|
23
|
+
let onError = (error: any) => {
|
|
24
|
+
let err;
|
|
25
|
+
if (typeof error === "string") {
|
|
26
|
+
err = new Error(error);
|
|
27
|
+
} else if (typeof error === "object" && error != null) {
|
|
28
|
+
const stackTrace = "stack" in error ? String(error.stack) : "";
|
|
29
|
+
const msg = "message" in error ? String(error.message) : "";
|
|
30
|
+
err = {
|
|
31
|
+
message: msg,
|
|
32
|
+
stack: stackTrace,
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
err = new Error("Failed to load page content");
|
|
36
|
+
}
|
|
37
|
+
// Format the error using the worker's error details
|
|
38
|
+
return new TextEncoder().encode(`0:E{"digest":"","name":"Error","message":"${
|
|
39
|
+
err.message
|
|
40
|
+
}","stack":${JSON.stringify(err.stack)},"env":"Server"}`);
|
|
41
|
+
};
|
|
42
|
+
// First yield: wait for initial message and handle module requests
|
|
43
|
+
yield await new Promise<Uint8Array>((resolve) => {
|
|
44
|
+
messageHandler = (message: RscWorkerOutputMessage) => {
|
|
45
|
+
switch (message.type) {
|
|
46
|
+
case "RSC_CHUNK":
|
|
47
|
+
resolve(message.chunk);
|
|
48
|
+
break;
|
|
49
|
+
case "RSC_END":
|
|
50
|
+
resolve(new Uint8Array());
|
|
51
|
+
break;
|
|
52
|
+
case "ERROR":
|
|
53
|
+
const errorResponse = onError(message.error);
|
|
54
|
+
resolve(errorResponse);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
logger.warn(`Unknown initial message type: ${message.type}`);
|
|
58
|
+
resolve(new Uint8Array());
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
cleanup = () => {
|
|
64
|
+
worker.off("message", messageHandler);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
worker.on("message", messageHandler);
|
|
68
|
+
|
|
69
|
+
// Send the render message to start the RSC stream
|
|
70
|
+
worker.postMessage({
|
|
71
|
+
type: "RSC_RENDER",
|
|
72
|
+
id: message.route,
|
|
73
|
+
...message,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Subsequent yields: handle RSC chunks until stream ends
|
|
78
|
+
while (true) {
|
|
79
|
+
const chunk = await new Promise<Uint8Array>((resolve) => {
|
|
80
|
+
messageHandler = (message: RscWorkerOutputMessage) => {
|
|
81
|
+
switch (message.type) {
|
|
82
|
+
case "RSC_END":
|
|
83
|
+
cleanup();
|
|
84
|
+
resolve(new Uint8Array());
|
|
85
|
+
return;
|
|
86
|
+
case "RSC_CHUNK":
|
|
87
|
+
resolve(message.chunk);
|
|
88
|
+
return;
|
|
89
|
+
case "RSC_METRICS":
|
|
90
|
+
onMetrics?.(message.metrics);
|
|
91
|
+
break;
|
|
92
|
+
case "ERROR":
|
|
93
|
+
cleanup();
|
|
94
|
+
const errorResponse = onError(message.error);
|
|
95
|
+
resolve(errorResponse);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
worker.once("message", messageHandler);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (chunk.length === 0) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
yield chunk;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ResolvedUserOptions } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
import type { ViteDevServer } from "vite";
|
|
4
|
+
import type { AutoDiscoveredFiles } from "../../types.js";
|
|
5
|
+
import { createWorker } from "../worker/createWorker.js";
|
|
6
|
+
import { serializedDevServerConfig } from "../helpers/serializeUserOptions.js";
|
|
7
|
+
import { serializedOptions } from "../helpers/serializeUserOptions.js";
|
|
8
|
+
import type { MessageChannel, Worker } from "node:worker_threads";
|
|
9
|
+
|
|
10
|
+
let currentWorker: Worker | null = null;
|
|
11
|
+
let isRestarting = false;
|
|
12
|
+
|
|
13
|
+
export async function restartWorker(
|
|
14
|
+
server: ViteDevServer,
|
|
15
|
+
autoDiscoveredFiles: AutoDiscoveredFiles,
|
|
16
|
+
userOptions: ResolvedUserOptions,
|
|
17
|
+
hmrChannel: MessageChannel
|
|
18
|
+
) {
|
|
19
|
+
if (isRestarting) return;
|
|
20
|
+
isRestarting = true;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Terminate the current worker if it exists
|
|
24
|
+
if (currentWorker) {
|
|
25
|
+
currentWorker.terminate();
|
|
26
|
+
currentWorker = null;
|
|
27
|
+
}
|
|
28
|
+
const routeCount = autoDiscoveredFiles.urlMap.size;
|
|
29
|
+
const hmrBuffer = 20; // Buffer for HMR and other operations
|
|
30
|
+
const maxListeners = routeCount + hmrBuffer;
|
|
31
|
+
const workerResult = await createWorker({
|
|
32
|
+
projectRoot: server.config.root,
|
|
33
|
+
workerPath: userOptions.rscWorkerPath,
|
|
34
|
+
reverseCondition: "react-server",
|
|
35
|
+
currentCondition: "react-client",
|
|
36
|
+
maxListeners: maxListeners,
|
|
37
|
+
envPrefix:
|
|
38
|
+
typeof server.config.envPrefix === "string"
|
|
39
|
+
? server.config.envPrefix
|
|
40
|
+
: Array.isArray(server.config.envPrefix)
|
|
41
|
+
? server.config.envPrefix[0]
|
|
42
|
+
: "VITE_",
|
|
43
|
+
workerData: {
|
|
44
|
+
hmrPort: hmrChannel.port2,
|
|
45
|
+
resolvedConfig: serializedDevServerConfig(server.config),
|
|
46
|
+
userOptions: serializedOptions(userOptions, autoDiscoveredFiles),
|
|
47
|
+
},
|
|
48
|
+
transferList: [hmrChannel.port2],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (workerResult.type === "success") {
|
|
52
|
+
currentWorker = workerResult.worker;
|
|
53
|
+
server.config.logger.info(
|
|
54
|
+
`[react-client] Set max listeners to ${maxListeners} for ${routeCount} routes`
|
|
55
|
+
);
|
|
56
|
+
} else if (workerResult.type === "error") {
|
|
57
|
+
server.config.logger.error("Failed to start rsc-worker", {
|
|
58
|
+
error: workerResult.error,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
isRestarting = false;
|
|
63
|
+
}
|
|
64
|
+
return currentWorker;
|
|
65
|
+
}
|
|
@@ -1,136 +1,25 @@
|
|
|
1
|
-
import type { ViteDevServer } from "vite";
|
|
1
|
+
import type { Logger, ViteDevServer } from "vite";
|
|
2
2
|
import type {
|
|
3
3
|
AutoDiscoveredFiles,
|
|
4
|
+
RenderMetrics,
|
|
4
5
|
RequestHandler,
|
|
5
6
|
ResolvedUserOptions,
|
|
7
|
+
StreamMetrics,
|
|
6
8
|
} from "../types.js";
|
|
7
9
|
import type {
|
|
8
|
-
RscWorkerOutputMessage,
|
|
9
10
|
RscRenderMessage,
|
|
10
11
|
} from "../worker/types.js";
|
|
11
12
|
import type { Worker as NodeWorker } from "node:worker_threads";
|
|
12
13
|
import { MessageChannel } from "node:worker_threads";
|
|
13
14
|
import {
|
|
14
|
-
serializedDevServerConfig,
|
|
15
15
|
serializedOptions,
|
|
16
16
|
} from "../helpers/serializeUserOptions.js";
|
|
17
|
-
import { createWorker } from "../worker/createWorker.js";
|
|
18
17
|
import { getRouteFiles } from "../helpers/getRouteFiles.js";
|
|
19
18
|
import { requestToRoute } from "../helpers/requestToRoute.js";
|
|
19
|
+
import { performance } from "node:perf_hooks";
|
|
20
|
+
import { createWorkerStream } from "./createWorkerStream.js";
|
|
21
|
+
import { restartWorker } from "./restartWorker.js";
|
|
20
22
|
|
|
21
|
-
let currentWorker: NodeWorker | null = null;
|
|
22
|
-
let isRestarting = false;
|
|
23
|
-
|
|
24
|
-
async function restartWorker(
|
|
25
|
-
server: ViteDevServer,
|
|
26
|
-
autoDiscoveredFiles: AutoDiscoveredFiles,
|
|
27
|
-
userOptions: ResolvedUserOptions,
|
|
28
|
-
hmrChannel: MessageChannel
|
|
29
|
-
) {
|
|
30
|
-
if (isRestarting) return;
|
|
31
|
-
isRestarting = true;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
// Terminate the current worker if it exists
|
|
35
|
-
if (currentWorker) {
|
|
36
|
-
currentWorker.terminate();
|
|
37
|
-
currentWorker = null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const workerResult = await createWorker({
|
|
41
|
-
projectRoot: server.config.root,
|
|
42
|
-
workerPath: userOptions.rscWorkerPath,
|
|
43
|
-
reverseCondition: "react-server",
|
|
44
|
-
currentCondition: "react-client",
|
|
45
|
-
workerData: {
|
|
46
|
-
hmrPort: hmrChannel.port2,
|
|
47
|
-
resolvedConfig: serializedDevServerConfig(server.config),
|
|
48
|
-
userOptions: serializedOptions(userOptions, autoDiscoveredFiles),
|
|
49
|
-
},
|
|
50
|
-
transferList: [hmrChannel.port2],
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
if (workerResult.type === "success") {
|
|
54
|
-
currentWorker = workerResult.worker;
|
|
55
|
-
} else if (workerResult.type === "error") {
|
|
56
|
-
server.config.logger.error("Failed to start rsc-worker", {
|
|
57
|
-
error: workerResult.error,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
} finally {
|
|
61
|
-
isRestarting = false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Creates an async generator that yields RSC chunks from the worker.
|
|
67
|
-
* Handles both module requests and RSC streaming.
|
|
68
|
-
*
|
|
69
|
-
* @param worker - The worker thread
|
|
70
|
-
* @param server - The Vite dev server
|
|
71
|
-
* @param message - The RSC render message
|
|
72
|
-
* @param rscWorkerLoaderPort - Optional loader port for module loading
|
|
73
|
-
* @returns An async generator that yields RSC chunks
|
|
74
|
-
*/
|
|
75
|
-
async function* createWorkerStream(
|
|
76
|
-
worker: NodeWorker,
|
|
77
|
-
message: Omit<RscRenderMessage, "type" | "id">
|
|
78
|
-
): AsyncGenerator<Uint8Array, void, unknown> {
|
|
79
|
-
let messageHandler: (message: RscWorkerOutputMessage) => void;
|
|
80
|
-
let cleanup: () => void = () => {};
|
|
81
|
-
|
|
82
|
-
// First yield: wait for initial message and handle module requests
|
|
83
|
-
yield await new Promise<Uint8Array>((resolve) => {
|
|
84
|
-
messageHandler = (message: RscWorkerOutputMessage) => {
|
|
85
|
-
if (message.type === "RSC_CHUNK") {
|
|
86
|
-
resolve(message.chunk);
|
|
87
|
-
}
|
|
88
|
-
if (message.type === "ERROR") {
|
|
89
|
-
resolve(new Uint8Array());
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
cleanup = () => {
|
|
94
|
-
worker.off("message", messageHandler);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
worker.on("message", messageHandler);
|
|
98
|
-
|
|
99
|
-
// Send the render message to start the RSC stream
|
|
100
|
-
worker.postMessage({
|
|
101
|
-
type: "RSC_RENDER",
|
|
102
|
-
id: message.route,
|
|
103
|
-
...message,
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Subsequent yields: handle RSC chunks until stream ends
|
|
108
|
-
while (true) {
|
|
109
|
-
const chunk = await new Promise<Uint8Array>((resolve) => {
|
|
110
|
-
messageHandler = (message: RscWorkerOutputMessage) => {
|
|
111
|
-
if (message.type === "RSC_END") {
|
|
112
|
-
cleanup();
|
|
113
|
-
resolve(new Uint8Array());
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (message.type === "RSC_CHUNK") {
|
|
117
|
-
resolve(message.chunk);
|
|
118
|
-
}
|
|
119
|
-
if (message.type === "ERROR") {
|
|
120
|
-
cleanup();
|
|
121
|
-
resolve(new Uint8Array());
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
worker.once("message", messageHandler);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (chunk.length === 0) {
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
yield chunk;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
23
|
|
|
135
24
|
/**
|
|
136
25
|
* Handles the RSC stream from the worker.
|
|
@@ -142,17 +31,44 @@ async function* createWorkerStream(
|
|
|
142
31
|
*/
|
|
143
32
|
export function handleWorkerRscStream(
|
|
144
33
|
worker: NodeWorker,
|
|
145
|
-
message: Omit<RscRenderMessage, "type" | "id"
|
|
34
|
+
message: Omit<RscRenderMessage, "type" | "id">,
|
|
35
|
+
logger: Logger,
|
|
36
|
+
onMetrics?: (metrics: StreamMetrics) => void
|
|
146
37
|
): ReadableStream<Uint8Array> {
|
|
147
38
|
// Create a ReadableStream from the async generator
|
|
148
39
|
return new ReadableStream<Uint8Array>({
|
|
149
40
|
async start(controller) {
|
|
150
41
|
try {
|
|
151
|
-
for await (const chunk of createWorkerStream(
|
|
42
|
+
for await (const chunk of createWorkerStream(
|
|
43
|
+
worker,
|
|
44
|
+
message,
|
|
45
|
+
logger,
|
|
46
|
+
onMetrics
|
|
47
|
+
)) {
|
|
152
48
|
controller.enqueue(chunk);
|
|
153
49
|
}
|
|
154
50
|
} catch (error) {
|
|
155
|
-
|
|
51
|
+
const err =
|
|
52
|
+
error instanceof Error
|
|
53
|
+
? error
|
|
54
|
+
: typeof error === "string"
|
|
55
|
+
? new Error(error)
|
|
56
|
+
: typeof error === "object" && error != null
|
|
57
|
+
? {
|
|
58
|
+
message:
|
|
59
|
+
"message" in error ? String(error.message) : "Unknown error",
|
|
60
|
+
stack: "stack" in error ? String(error.stack) : "",
|
|
61
|
+
name: "name" in error ? String(error.name) : "Error",
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
message: "Unknown error",
|
|
65
|
+
stack: "",
|
|
66
|
+
name: "Error",
|
|
67
|
+
};
|
|
68
|
+
logger.error(err.message, {
|
|
69
|
+
error: err,
|
|
70
|
+
});
|
|
71
|
+
controller.error(err);
|
|
156
72
|
} finally {
|
|
157
73
|
controller.close();
|
|
158
74
|
}
|
|
@@ -171,16 +87,19 @@ export async function configureWorkerRequestHandler({
|
|
|
171
87
|
autoDiscoveredFiles,
|
|
172
88
|
userOptions: _userOptions,
|
|
173
89
|
hmrChannel,
|
|
90
|
+
onMetrics,
|
|
174
91
|
}: {
|
|
175
92
|
server: ViteDevServer;
|
|
176
93
|
autoDiscoveredFiles: AutoDiscoveredFiles;
|
|
177
94
|
userOptions: ResolvedUserOptions;
|
|
178
95
|
hmrChannel: MessageChannel;
|
|
96
|
+
onMetrics?: (metrics: RenderMetrics) => void;
|
|
179
97
|
}) {
|
|
180
98
|
let {
|
|
181
99
|
// remove these
|
|
182
|
-
moduleBaseURL: _moduleBaseURL,
|
|
183
100
|
projectRoot: _projectRoot,
|
|
101
|
+
moduleBaseURL: _moduleBaseURL,
|
|
102
|
+
moduleBasePath: _moduleBasePath,
|
|
184
103
|
...handlerUserOptions
|
|
185
104
|
} = _userOptions;
|
|
186
105
|
const handlerOptions = Object.assign({}, handlerUserOptions, {
|
|
@@ -189,7 +108,7 @@ export async function configureWorkerRequestHandler({
|
|
|
189
108
|
? `${server.config.server.https ? "https" : "http"}://${
|
|
190
109
|
server.config.server.host
|
|
191
110
|
}:${server.config.server.port}`
|
|
192
|
-
:
|
|
111
|
+
: _moduleBaseURL,
|
|
193
112
|
moduleBasePath:
|
|
194
113
|
server.config.base === "/"
|
|
195
114
|
? ""
|
|
@@ -200,7 +119,7 @@ export async function configureWorkerRequestHandler({
|
|
|
200
119
|
});
|
|
201
120
|
|
|
202
121
|
// Start the worker
|
|
203
|
-
await restartWorker(server, autoDiscoveredFiles, handlerOptions, hmrChannel);
|
|
122
|
+
const currentWorker = await restartWorker(server, autoDiscoveredFiles, handlerOptions, hmrChannel);
|
|
204
123
|
|
|
205
124
|
// Create the request handler
|
|
206
125
|
const handler: RequestHandler = async (req, res, next) => {
|
|
@@ -214,7 +133,7 @@ export async function configureWorkerRequestHandler({
|
|
|
214
133
|
// Get the route from the request
|
|
215
134
|
let route = requestToRoute(req, {
|
|
216
135
|
moduleBasePath: handlerOptions.moduleBasePath,
|
|
217
|
-
build: handlerOptions.build
|
|
136
|
+
build: handlerOptions.build,
|
|
218
137
|
});
|
|
219
138
|
if (!route) {
|
|
220
139
|
return next();
|
|
@@ -227,10 +146,14 @@ export async function configureWorkerRequestHandler({
|
|
|
227
146
|
handlerOptions
|
|
228
147
|
);
|
|
229
148
|
if (routeFiles.type === "error") {
|
|
230
|
-
server.config.logger.error(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
149
|
+
server.config.logger.error(
|
|
150
|
+
`[react-client] Error fetching route files for ${route}`,
|
|
151
|
+
{
|
|
152
|
+
error: routeFiles.error,
|
|
153
|
+
timestamp: true,
|
|
154
|
+
environment: "server",
|
|
155
|
+
}
|
|
156
|
+
);
|
|
234
157
|
return next();
|
|
235
158
|
}
|
|
236
159
|
const { page, props } = routeFiles;
|
|
@@ -243,19 +166,45 @@ export async function configureWorkerRequestHandler({
|
|
|
243
166
|
handlerOptions,
|
|
244
167
|
autoDiscoveredFiles
|
|
245
168
|
);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
169
|
+
const startTime = performance.now();
|
|
170
|
+
const stream = handleWorkerRscStream(
|
|
171
|
+
currentWorker,
|
|
172
|
+
{
|
|
173
|
+
...serializedUserOptions,
|
|
174
|
+
// we make the worker stream aware of the route, pagePath, propsPath
|
|
175
|
+
route,
|
|
176
|
+
pagePath: page,
|
|
177
|
+
propsPath: props,
|
|
178
|
+
// override these at all times to ensure the settings will work for the dev server
|
|
179
|
+
projectRoot: server.config.root,
|
|
180
|
+
build: serializedUserOptions.build,
|
|
181
|
+
manifest: autoDiscoveredFiles.staticManifest,
|
|
182
|
+
cssFiles: new Map(),
|
|
183
|
+
globalCss: new Map(),
|
|
184
|
+
},
|
|
185
|
+
server.config.logger,
|
|
186
|
+
typeof onMetrics === "function"
|
|
187
|
+
? (metrics) => {
|
|
188
|
+
const elapsedTime = performance.now() - startTime;
|
|
189
|
+
const formattedMetrics = {
|
|
190
|
+
route,
|
|
191
|
+
htmlSize: 0,
|
|
192
|
+
rscSize: metrics.bytes,
|
|
193
|
+
processingTime: elapsedTime,
|
|
194
|
+
chunks: metrics.chunks,
|
|
195
|
+
chunkRate: metrics.chunks / (elapsedTime / 1000),
|
|
196
|
+
memoryUsage: process.memoryUsage(),
|
|
197
|
+
streamMetrics: {
|
|
198
|
+
...metrics,
|
|
199
|
+
duration: elapsedTime
|
|
200
|
+
},
|
|
201
|
+
htmlSizes: new Map(),
|
|
202
|
+
rscSizes: new Map([[route, metrics.bytes]]),
|
|
203
|
+
} satisfies RenderMetrics;
|
|
204
|
+
onMetrics(formattedMetrics);
|
|
205
|
+
}
|
|
206
|
+
: undefined
|
|
207
|
+
);
|
|
259
208
|
const writeStream = new WritableStream({
|
|
260
209
|
write(chunk) {
|
|
261
210
|
res.write(chunk);
|
|
@@ -276,14 +225,16 @@ export async function configureWorkerRequestHandler({
|
|
|
276
225
|
);
|
|
277
226
|
res.end();
|
|
278
227
|
},
|
|
279
|
-
})
|
|
280
|
-
let timeout
|
|
281
|
-
server.config.logger.error("[react-client] RSC render timeout");
|
|
282
|
-
res.end();
|
|
283
|
-
}, 5000);
|
|
228
|
+
});
|
|
229
|
+
let timeout: NodeJS.Timeout;
|
|
284
230
|
|
|
285
231
|
// Pipe the stream to the response
|
|
286
232
|
stream.pipeTo(writeStream);
|
|
233
|
+
// wait for timeout
|
|
234
|
+
timeout = setTimeout(() => {
|
|
235
|
+
server.config.logger.error("RSC render timeout");
|
|
236
|
+
res.end();
|
|
237
|
+
}, 5000);
|
|
287
238
|
} catch (error) {
|
|
288
239
|
if (error instanceof Error) {
|
|
289
240
|
server.config.logger.error(error.message, {
|
|
@@ -36,7 +36,7 @@ export async function configureReactServer({
|
|
|
36
36
|
? `${server.config.server.https ? "https" : "http"}://${
|
|
37
37
|
server.config.server.host
|
|
38
38
|
}:${server.config.server.port}`
|
|
39
|
-
:
|
|
39
|
+
: _moduleBaseURL,
|
|
40
40
|
moduleBasePath:
|
|
41
41
|
server.config.base === "/"
|
|
42
42
|
? ""
|
|
@@ -47,7 +47,7 @@ export async function configureReactServer({
|
|
|
47
47
|
});
|
|
48
48
|
// Handle Vite server restarts
|
|
49
49
|
server.ws.on("restart", (path) => {
|
|
50
|
-
|
|
50
|
+
server.config.logger.info(
|
|
51
51
|
"[vite-plugin-react-server] 🔧 Plugin changed, preparing for restart:",
|
|
52
52
|
path
|
|
53
53
|
);
|
|
@@ -58,7 +58,7 @@ export async function configureReactServer({
|
|
|
58
58
|
"Content-Type": "text/x-component",
|
|
59
59
|
"Retry-After": "1",
|
|
60
60
|
});
|
|
61
|
-
res.end(
|
|
61
|
+
res.end(`0:E{"digest":"","name":"Error","message":"Server restarting...","stack":"","env":"Server"}`);
|
|
62
62
|
}
|
|
63
63
|
activeStreams.clear();
|
|
64
64
|
});
|
|
@@ -67,14 +67,11 @@ export async function configureReactServer({
|
|
|
67
67
|
try {
|
|
68
68
|
if (req.headers.accept !== "text/x-component") return next();
|
|
69
69
|
let route = req.url?.replace("/" + handlerOptions.build.rscOutputPath, "");
|
|
70
|
-
if(!route?.startsWith(handlerOptions.moduleBasePath)) {
|
|
70
|
+
if(handlerOptions.moduleBasePath !== '' && !route?.startsWith(handlerOptions.moduleBasePath)) {
|
|
71
71
|
next();
|
|
72
|
-
} else {
|
|
72
|
+
} else if(route && handlerOptions.moduleBasePath.length) {
|
|
73
73
|
route = route.slice(handlerOptions.moduleBasePath.length);
|
|
74
74
|
}
|
|
75
|
-
if(typeof route !== "string" ) {
|
|
76
|
-
throw new Error("req.url is not a string");
|
|
77
|
-
}
|
|
78
75
|
if (!route || route === "") {
|
|
79
76
|
route = "/";
|
|
80
77
|
}
|
|
@@ -88,27 +85,7 @@ export async function configureReactServer({
|
|
|
88
85
|
const pagePath = routeFiles.page;
|
|
89
86
|
const propsPath = routeFiles.props;
|
|
90
87
|
|
|
91
|
-
//
|
|
92
|
-
await server.warmupRequest(pagePath);
|
|
93
|
-
const eventHandler = createEventHandler(onEvent);
|
|
94
|
-
const cssFilesResult = await collectViteModuleGraphCss({
|
|
95
|
-
moduleGraph: server.moduleGraph,
|
|
96
|
-
pagePath,
|
|
97
|
-
loader: (i) => server.ssrLoadModule(i, { fixStacktrace: true }),
|
|
98
|
-
// explicitly set for development server
|
|
99
|
-
moduleBaseURL: handlerOptions.moduleBaseURL,
|
|
100
|
-
moduleBasePath: handlerOptions.moduleBasePath,
|
|
101
|
-
moduleRootPath: handlerOptions.moduleRootPath,
|
|
102
|
-
projectRoot: handlerOptions.projectRoot,
|
|
103
|
-
css: handlerOptions.css,
|
|
104
|
-
parentUrl: pagePath,
|
|
105
|
-
});
|
|
106
|
-
if (cssFilesResult.type === "skip") {
|
|
107
|
-
return next();
|
|
108
|
-
}
|
|
109
|
-
if (cssFilesResult.type === "error") {
|
|
110
|
-
throw cssFilesResult.error;
|
|
111
|
-
}
|
|
88
|
+
// first load the page and props
|
|
112
89
|
const pageAndPropsResult = await resolvePageAndProps({
|
|
113
90
|
pagePath,
|
|
114
91
|
propsPath,
|
|
@@ -123,6 +100,24 @@ export async function configureReactServer({
|
|
|
123
100
|
if (pageAndPropsResult.type === "skip") {
|
|
124
101
|
return next();
|
|
125
102
|
}
|
|
103
|
+
|
|
104
|
+
const eventHandler = createEventHandler(onEvent);
|
|
105
|
+
const cssFilesResult = await collectViteModuleGraphCss({
|
|
106
|
+
moduleGraph: server.moduleGraph, // by having loaded the page and props, we can get them from the module graph
|
|
107
|
+
parentUrl: pagePath,
|
|
108
|
+
handlerOptions: {
|
|
109
|
+
pagePath,
|
|
110
|
+
loader: server.ssrLoadModule,
|
|
111
|
+
// explicitly set for development server
|
|
112
|
+
...handlerOptions,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (cssFilesResult.type === "skip") {
|
|
116
|
+
return next();
|
|
117
|
+
}
|
|
118
|
+
if (cssFilesResult.type === "error") {
|
|
119
|
+
throw cssFilesResult.error;
|
|
120
|
+
}
|
|
126
121
|
const { PageComponent, pageProps } = pageAndPropsResult;
|
|
127
122
|
// Create the headless RSC stream directly;
|
|
128
123
|
const rscResult = await createHandler({
|
|
@@ -28,7 +28,7 @@ export async function collectRscContent(
|
|
|
28
28
|
handlerOptions: CreateHandlerOptions
|
|
29
29
|
): Promise<{ stream: PassThrough; metrics: StreamMetrics }> {
|
|
30
30
|
const metrics = createStreamMetrics();
|
|
31
|
-
const startTime = performance.now()
|
|
31
|
+
const startTime = performance.now();
|
|
32
32
|
|
|
33
33
|
const outputPath = join(
|
|
34
34
|
handlerOptions.build.outDir,
|
|
@@ -49,7 +49,7 @@ export async function collectRscContent(
|
|
|
49
49
|
callback(null, chunk);
|
|
50
50
|
},
|
|
51
51
|
flush(callback) {
|
|
52
|
-
metrics.duration =
|
|
52
|
+
metrics.duration = performance.now() - startTime;
|
|
53
53
|
callback();
|
|
54
54
|
}
|
|
55
55
|
});
|