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.
Files changed (143) hide show
  1. package/dist/package.json +10 -5
  2. package/dist/plugin/config/defaults.d.ts +1 -1
  3. package/dist/plugin/config/defaults.js +1 -1
  4. package/dist/plugin/config/defaults.js.map +1 -1
  5. package/dist/plugin/config/resolveAutoDiscover.d.ts +2 -0
  6. package/dist/plugin/config/resolveAutoDiscover.d.ts.map +1 -1
  7. package/dist/plugin/config/resolveAutoDiscover.js +15 -18
  8. package/dist/plugin/config/resolveAutoDiscover.js.map +1 -1
  9. package/dist/plugin/config/resolveOptions.d.ts.map +1 -1
  10. package/dist/plugin/config/resolveOptions.js +4 -1
  11. package/dist/plugin/config/resolveOptions.js.map +1 -1
  12. package/dist/plugin/config/resolveUserConfig.d.ts.map +1 -1
  13. package/dist/plugin/config/resolveUserConfig.js +56 -29
  14. package/dist/plugin/config/resolveUserConfig.js.map +1 -1
  15. package/dist/plugin/helpers/collectBundleManifestCss.d.ts +0 -6
  16. package/dist/plugin/helpers/collectBundleManifestCss.d.ts.map +1 -1
  17. package/dist/plugin/helpers/collectBundleManifestCss.js +2 -110
  18. package/dist/plugin/helpers/collectViteModuleGraphCss.d.ts +2 -1
  19. package/dist/plugin/helpers/collectViteModuleGraphCss.d.ts.map +1 -1
  20. package/dist/plugin/helpers/collectViteModuleGraphCss.js +19 -18
  21. package/dist/plugin/helpers/collectViteModuleGraphCss.js.map +1 -1
  22. package/dist/plugin/helpers/createCssProps.d.ts +3 -2
  23. package/dist/plugin/helpers/createCssProps.d.ts.map +1 -1
  24. package/dist/plugin/helpers/createCssProps.js +10 -6
  25. package/dist/plugin/helpers/createCssProps.js.map +1 -1
  26. package/dist/plugin/helpers/createRscStream.d.ts.map +1 -1
  27. package/dist/plugin/helpers/createRscStream.js +37 -43
  28. package/dist/plugin/helpers/createRscStream.js.map +1 -1
  29. package/dist/plugin/helpers/formatMetrics.d.ts +4 -0
  30. package/dist/plugin/helpers/formatMetrics.d.ts.map +1 -0
  31. package/dist/plugin/helpers/formatMetrics.js +24 -0
  32. package/dist/plugin/helpers/tryManifest.d.ts.map +1 -1
  33. package/dist/plugin/helpers/tryManifest.js +0 -8
  34. package/dist/plugin/helpers/tryManifest.js.map +1 -1
  35. package/dist/plugin/html.js +1 -1
  36. package/dist/plugin/html.js.map +1 -1
  37. package/dist/plugin/loader/createBuildLoader.d.ts.map +1 -1
  38. package/dist/plugin/loader/createBuildLoader.js +2 -0
  39. package/dist/plugin/loader/createBuildLoader.js.map +1 -1
  40. package/dist/plugin/metrics/formatMetrics.d.ts +4 -0
  41. package/dist/plugin/metrics/formatMetrics.d.ts.map +1 -0
  42. package/dist/plugin/metrics/formatMetrics.js +39 -0
  43. package/dist/plugin/metrics/formatMetrics.js.map +1 -0
  44. package/dist/plugin/metrics/index.d.ts +3 -0
  45. package/dist/plugin/metrics/index.d.ts.map +1 -0
  46. package/dist/plugin/metrics/index.js +1 -0
  47. package/dist/plugin/metrics.js +7 -0
  48. package/dist/plugin/metrics.js.map +1 -0
  49. package/dist/plugin/react-client/createWorkerStream.d.ts +16 -0
  50. package/dist/plugin/react-client/createWorkerStream.d.ts.map +1 -0
  51. package/dist/plugin/react-client/createWorkerStream.js +88 -0
  52. package/dist/plugin/react-client/createWorkerStream.js.map +1 -0
  53. package/dist/plugin/react-client/plugin.d.ts.map +1 -1
  54. package/dist/plugin/react-client/plugin.js +4 -1
  55. package/dist/plugin/react-client/plugin.js.map +1 -1
  56. package/dist/plugin/react-client/restartWorker.d.ts +6 -0
  57. package/dist/plugin/react-client/restartWorker.d.ts.map +1 -0
  58. package/dist/plugin/react-client/restartWorker.js +53 -0
  59. package/dist/plugin/react-client/restartWorker.js.map +1 -0
  60. package/dist/plugin/react-client/server.d.ts +5 -4
  61. package/dist/plugin/react-client/server.d.ts.map +1 -1
  62. package/dist/plugin/react-client/server.js +79 -110
  63. package/dist/plugin/react-client/server.js.map +1 -1
  64. package/dist/plugin/react-server/server.d.ts.map +1 -1
  65. package/dist/plugin/react-server/server.js +23 -28
  66. package/dist/plugin/react-server/server.js.map +1 -1
  67. package/dist/plugin/react-static/collectHtmlWorkerContent.js +1 -1
  68. package/dist/plugin/react-static/collectHtmlWorkerContent.js.map +1 -1
  69. package/dist/plugin/react-static/collectRscContent.js +1 -1
  70. package/dist/plugin/react-static/collectRscContent.js.map +1 -1
  71. package/dist/plugin/react-static/configurePreviewServer.d.ts.map +1 -1
  72. package/dist/plugin/react-static/configurePreviewServer.js +23 -4
  73. package/dist/plugin/react-static/configurePreviewServer.js.map +1 -1
  74. package/dist/plugin/react-static/fileWriter.d.ts.map +1 -1
  75. package/dist/plugin/react-static/fileWriter.js +5 -1
  76. package/dist/plugin/react-static/fileWriter.js.map +1 -1
  77. package/dist/plugin/react-static/plugin.d.ts.map +1 -1
  78. package/dist/plugin/react-static/plugin.js +50 -33
  79. package/dist/plugin/react-static/plugin.js.map +1 -1
  80. package/dist/plugin/types.d.ts +6 -7
  81. package/dist/plugin/types.d.ts.map +1 -1
  82. package/dist/plugin/utils/callServer.d.ts +2 -0
  83. package/dist/plugin/utils/callServer.d.ts.map +1 -0
  84. package/dist/plugin/utils/callServer.js +26 -0
  85. package/dist/plugin/utils/callServer.js.map +1 -0
  86. package/dist/plugin/utils/createReactFetcher.d.ts +7 -0
  87. package/dist/plugin/utils/createReactFetcher.d.ts.map +1 -0
  88. package/dist/plugin/utils/createReactFetcher.js +33 -0
  89. package/dist/plugin/utils/createReactFetcher.js.map +1 -0
  90. package/dist/plugin/utils/index.d.ts +4 -0
  91. package/dist/plugin/utils/index.d.ts.map +1 -0
  92. package/dist/plugin/utils/index.js +3 -0
  93. package/dist/plugin/utils/pageURL.d.ts +2 -0
  94. package/dist/plugin/utils/pageURL.d.ts.map +1 -0
  95. package/dist/plugin/utils/pageURL.js +21 -0
  96. package/dist/plugin/utils/pageURL.js.map +1 -0
  97. package/dist/plugin/utils.js +9 -0
  98. package/dist/plugin/utils.js.map +1 -0
  99. package/dist/plugin/worker/rsc/handleRender.d.ts +6 -2
  100. package/dist/plugin/worker/rsc/handleRender.d.ts.map +1 -1
  101. package/dist/plugin/worker/rsc/handleRender.js +26 -55
  102. package/dist/plugin/worker/rsc/handleRender.js.map +1 -1
  103. package/dist/plugin/worker/rsc/messageHandler.d.ts +1 -2
  104. package/dist/plugin/worker/rsc/messageHandler.d.ts.map +1 -1
  105. package/dist/plugin/worker/rsc/messageHandler.js +45 -2
  106. package/dist/plugin/worker/rsc/messageHandler.js.map +1 -1
  107. package/dist/plugin/worker/rsc/state.d.ts.map +1 -1
  108. package/dist/plugin/worker/rsc/state.js +1 -5
  109. package/dist/plugin/worker/rsc/state.js.map +1 -1
  110. package/dist/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +10 -5
  112. package/plugin/config/defaults.tsx +1 -1
  113. package/plugin/config/resolveAutoDiscover.ts +17 -22
  114. package/plugin/config/resolveOptions.ts +5 -1
  115. package/plugin/config/resolveUserConfig.ts +64 -36
  116. package/plugin/helpers/collectBundleManifestCss.ts +1 -160
  117. package/plugin/helpers/collectViteModuleGraphCss.ts +31 -29
  118. package/plugin/helpers/createCssProps.tsx +22 -11
  119. package/plugin/helpers/createRscStream.tsx +42 -46
  120. package/plugin/helpers/formatMetrics.ts +37 -0
  121. package/plugin/helpers/tryManifest.ts +0 -9
  122. package/plugin/html.tsx +1 -1
  123. package/plugin/loader/createBuildLoader.ts +2 -0
  124. package/plugin/metrics/formatMetrics.ts +37 -0
  125. package/plugin/metrics/index.ts +2 -0
  126. package/plugin/react-client/createWorkerStream.ts +107 -0
  127. package/plugin/react-client/plugin.ts +3 -0
  128. package/plugin/react-client/restartWorker.ts +65 -0
  129. package/plugin/react-client/server.ts +97 -146
  130. package/plugin/react-server/server.ts +24 -29
  131. package/plugin/react-static/collectHtmlWorkerContent.ts +1 -1
  132. package/plugin/react-static/collectRscContent.ts +2 -2
  133. package/plugin/react-static/configurePreviewServer.ts +29 -6
  134. package/plugin/react-static/fileWriter.ts +5 -1
  135. package/plugin/react-static/plugin.ts +58 -38
  136. package/plugin/types.ts +11 -11
  137. package/plugin/utils/callServer.ts +25 -0
  138. package/plugin/utils/createReactFetcher.ts +31 -0
  139. package/plugin/utils/index.ts +3 -0
  140. package/plugin/utils/pageURL.ts +28 -0
  141. package/plugin/worker/rsc/handleRender.ts +33 -71
  142. package/plugin/worker/rsc/messageHandler.tsx +48 -6
  143. 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,2 @@
1
+ export * from "./formatMetrics.js";
2
+ export type { RenderMetrics, StreamMetrics } from "../types.js";
@@ -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
+ }
@@ -82,6 +82,9 @@ export function reactClientPlugin(options: StreamPluginOptions): Plugin {
82
82
  autoDiscoveredFiles,
83
83
  userOptions,
84
84
  hmrChannel,
85
+ onMetrics: userOptions.onMetrics ? (metrics) => {
86
+ userOptions.onMetrics?.(metrics);
87
+ } : undefined,
85
88
  });
86
89
  },
87
90
 
@@ -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(worker, message)) {
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
- controller.error(error);
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(routeFiles.error.message, {
231
- error: routeFiles.error,
232
- timestamp: true,
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 stream = handleWorkerRscStream(currentWorker, {
247
- ...serializedUserOptions,
248
- // we make the worker stream aware of the route, pagePath, propsPath
249
- route,
250
- pagePath: page,
251
- propsPath: props,
252
- // override these at all times to ensure the settings will work for the dev server
253
- projectRoot: server.config.root,
254
- build: serializedUserOptions.build,
255
- manifest: autoDiscoveredFiles.staticManifest,
256
- cssFiles: new Map(),
257
- globalCss: new Map(),
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 = setTimeout(() => {
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
- console.log(
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('{"error":"Server restarting..."}');
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
- // Create a unified event handler
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({
@@ -47,7 +47,7 @@ export async function collectHtmlWorkerContent(
47
47
  callback(null, chunk);
48
48
  },
49
49
  flush(callback) {
50
- metrics.duration = Date.now() - startTime;
50
+ metrics.duration = performance.now() - startTime;
51
51
  callback();
52
52
  },
53
53
  });
@@ -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 = Date.now() - startTime;
52
+ metrics.duration = performance.now() - startTime;
53
53
  callback();
54
54
  }
55
55
  });