vite-plugin-react-server 1.1.8 → 1.1.10

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 (163) hide show
  1. package/dist/index.js.map +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/plugin/config/defaults.d.ts +2 -1
  4. package/dist/plugin/config/defaults.d.ts.map +1 -1
  5. package/dist/plugin/config/defaults.js +3 -1
  6. package/dist/plugin/config/defaults.js.map +1 -1
  7. package/dist/plugin/config/resolveDevServerConfig.d.ts +2 -0
  8. package/dist/plugin/config/resolveDevServerConfig.d.ts.map +1 -0
  9. package/dist/plugin/config/resolveDevServerConfig.js +1 -0
  10. package/dist/plugin/config/resolveEnv.d.ts +3 -0
  11. package/dist/plugin/config/resolveEnv.d.ts.map +1 -0
  12. package/dist/plugin/config/resolveEnv.js +21 -0
  13. package/dist/plugin/config/resolveEnv.js.map +1 -0
  14. package/dist/plugin/config/resolveOptions.d.ts.map +1 -1
  15. package/dist/plugin/config/resolveOptions.js +25 -9
  16. package/dist/plugin/config/resolveOptions.js.map +1 -1
  17. package/dist/plugin/config/resolveUrlOption.js.map +1 -1
  18. package/dist/plugin/config/resolveUserConfig.d.ts.map +1 -1
  19. package/dist/plugin/config/resolveUserConfig.js +45 -52
  20. package/dist/plugin/config/resolveUserConfig.js.map +1 -1
  21. package/dist/plugin/helpers/collectManifestCss.js.map +1 -1
  22. package/dist/plugin/helpers/createCssProps.d.ts.map +1 -1
  23. package/dist/plugin/helpers/createCssProps.js +10 -2
  24. package/dist/plugin/helpers/createCssProps.js.map +1 -1
  25. package/dist/plugin/helpers/getRouteFiles.js +18 -35
  26. package/dist/plugin/helpers/inputNormalizer.d.ts +2 -1
  27. package/dist/plugin/helpers/inputNormalizer.d.ts.map +1 -1
  28. package/dist/plugin/helpers/inputNormalizer.js +34 -9
  29. package/dist/plugin/helpers/inputNormalizer.js.map +1 -1
  30. package/dist/plugin/helpers/requestInfo.d.ts +16 -0
  31. package/dist/plugin/helpers/requestInfo.d.ts.map +1 -0
  32. package/dist/plugin/helpers/requestInfo.js +66 -0
  33. package/dist/plugin/helpers/requestInfo.js.map +1 -0
  34. package/dist/plugin/helpers/requestToRoute.js +14 -22
  35. package/dist/plugin/helpers/serializeUserOptions.js.map +1 -1
  36. package/dist/plugin/loader/css-loader.development.d.ts.map +1 -1
  37. package/dist/plugin/loader/css-loader.development.js +1 -7
  38. package/dist/plugin/loader/css-loader.development.js.map +1 -1
  39. package/dist/plugin/loader/css-loader.production.js.map +1 -1
  40. package/dist/plugin/loader/react-loader.d.ts.map +1 -1
  41. package/dist/plugin/loader/react-loader.js +1 -8
  42. package/dist/plugin/loader/react-loader.js.map +1 -1
  43. package/dist/plugin/metrics/formatMetrics.d.ts +6 -1
  44. package/dist/plugin/metrics/formatMetrics.d.ts.map +1 -1
  45. package/dist/plugin/metrics/formatMetrics.js +21 -3
  46. package/dist/plugin/metrics/formatMetrics.js.map +1 -1
  47. package/dist/plugin/metrics.js +1 -1
  48. package/dist/plugin/plugin.client.js.map +1 -1
  49. package/dist/plugin/plugin.server.js.map +1 -1
  50. package/dist/plugin/preserver/plugin.js.map +1 -1
  51. package/dist/plugin/react-client/createMessageHandlers.d.ts +10 -0
  52. package/dist/plugin/react-client/createMessageHandlers.d.ts.map +1 -0
  53. package/dist/plugin/react-client/createMessageHandlers.js +36 -0
  54. package/dist/plugin/react-client/createMessageHandlers.js.map +1 -0
  55. package/dist/plugin/react-client/createWorkerStream.d.ts +3 -3
  56. package/dist/plugin/react-client/createWorkerStream.d.ts.map +1 -1
  57. package/dist/plugin/react-client/createWorkerStream.js +60 -69
  58. package/dist/plugin/react-client/createWorkerStream.js.map +1 -1
  59. package/dist/plugin/react-client/plugin.d.ts.map +1 -1
  60. package/dist/plugin/react-client/plugin.js +1 -3
  61. package/dist/plugin/react-client/plugin.js.map +1 -1
  62. package/dist/plugin/react-client/restartWorker.js +2 -2
  63. package/dist/plugin/react-client/restartWorker.js.map +1 -1
  64. package/dist/plugin/react-client/server.d.ts.map +1 -1
  65. package/dist/plugin/react-client/server.js +29 -40
  66. package/dist/plugin/react-client/server.js.map +1 -1
  67. package/dist/plugin/react-server/server.d.ts.map +1 -1
  68. package/dist/plugin/react-server/server.js +17 -19
  69. package/dist/plugin/react-server/server.js.map +1 -1
  70. package/dist/plugin/react-static/collectHtmlWorkerContent.js.map +1 -1
  71. package/dist/plugin/react-static/collectRscContent.js.map +1 -1
  72. package/dist/plugin/react-static/configurePreviewServer.d.ts.map +1 -1
  73. package/dist/plugin/react-static/configurePreviewServer.js +58 -54
  74. package/dist/plugin/react-static/configurePreviewServer.js.map +1 -1
  75. package/dist/plugin/react-static/fileWriter.js.map +1 -1
  76. package/dist/plugin/react-static/plugin.d.ts.map +1 -1
  77. package/dist/plugin/react-static/plugin.js +12 -6
  78. package/dist/plugin/react-static/plugin.js.map +1 -1
  79. package/dist/plugin/react-static/renderPages.js.map +1 -1
  80. package/dist/plugin/transformer/plugin.client.d.ts.map +1 -1
  81. package/dist/plugin/transformer/plugin.client.js +3 -12
  82. package/dist/plugin/transformer/plugin.client.js.map +1 -1
  83. package/dist/plugin/transformer/plugin.js.map +1 -1
  84. package/dist/plugin/transformer/plugin.server.d.ts.map +1 -1
  85. package/dist/plugin/transformer/plugin.server.js +17 -24
  86. package/dist/plugin/transformer/plugin.server.js.map +1 -1
  87. package/dist/plugin/types.d.ts +4 -1
  88. package/dist/plugin/types.d.ts.map +1 -1
  89. package/dist/plugin/utils/callServer.d.ts.map +1 -1
  90. package/dist/plugin/utils/callServer.js +2 -2
  91. package/dist/plugin/utils/callServer.js.map +1 -1
  92. package/dist/plugin/utils/createReactFetcher.d.ts +1 -1
  93. package/dist/plugin/utils/createReactFetcher.d.ts.map +1 -1
  94. package/dist/plugin/utils/createReactFetcher.js +6 -8
  95. package/dist/plugin/utils/createReactFetcher.js.map +1 -1
  96. package/dist/plugin/utils/env.d.ts +2 -0
  97. package/dist/plugin/utils/env.d.ts.map +1 -0
  98. package/dist/plugin/utils/env.js +9 -0
  99. package/dist/plugin/utils/env.js.map +1 -0
  100. package/dist/plugin/utils/index.d.ts +1 -0
  101. package/dist/plugin/utils/index.d.ts.map +1 -1
  102. package/dist/plugin/utils/index.js +1 -0
  103. package/dist/plugin/utils/pageURL.d.ts +4 -1
  104. package/dist/plugin/utils/pageURL.d.ts.map +1 -1
  105. package/dist/plugin/utils/pageURL.js +23 -11
  106. package/dist/plugin/utils/pageURL.js.map +1 -1
  107. package/dist/plugin/utils.js +1 -0
  108. package/dist/plugin/utils.js.map +1 -1
  109. package/dist/plugin/worker/createWorker.d.ts.map +1 -1
  110. package/dist/plugin/worker/createWorker.js +4 -3
  111. package/dist/plugin/worker/createWorker.js.map +1 -1
  112. package/dist/plugin/worker/html/createHtmlWorkerRenderState.d.ts.map +1 -1
  113. package/dist/plugin/worker/html/createHtmlWorkerRenderState.js +3 -0
  114. package/dist/plugin/worker/html/createHtmlWorkerRenderState.js.map +1 -1
  115. package/dist/plugin/worker/rsc/handleRender.d.ts +2 -7
  116. package/dist/plugin/worker/rsc/handleRender.d.ts.map +1 -1
  117. package/dist/plugin/worker/rsc/handleRender.js.map +1 -1
  118. package/dist/plugin/worker/rsc/messageHandler.d.ts.map +1 -1
  119. package/dist/plugin/worker/rsc/messageHandler.js +1 -1
  120. package/dist/plugin/worker/rsc/messageHandler.js.map +1 -1
  121. package/dist/plugin/worker/rsc/rsc-worker.development.js.map +1 -1
  122. package/dist/plugin/worker/rsc/state.d.ts.map +1 -1
  123. package/dist/plugin/worker/rsc/state.js.map +1 -1
  124. package/dist/plugin/worker/types.d.ts +6 -0
  125. package/dist/plugin/worker/types.d.ts.map +1 -1
  126. package/dist/tsconfig.tsbuildinfo +1 -1
  127. package/package.json +1 -1
  128. package/plugin/config/defaults.tsx +2 -1
  129. package/plugin/config/resolveDevServerConfig.tsx +0 -0
  130. package/plugin/config/resolveEnv.ts +37 -0
  131. package/plugin/config/resolveOptions.ts +49 -24
  132. package/plugin/config/resolveUserConfig.ts +73 -70
  133. package/plugin/helpers/createCssProps.tsx +14 -6
  134. package/plugin/helpers/inputNormalizer.ts +66 -17
  135. package/plugin/helpers/requestInfo.ts +81 -0
  136. package/plugin/loader/css-loader.development.ts +2 -8
  137. package/plugin/loader/react-loader.ts +1 -10
  138. package/plugin/metrics/formatMetrics.ts +29 -4
  139. package/plugin/preserver/plugin.ts +1 -0
  140. package/plugin/react-client/createMessageHandlers.ts +37 -0
  141. package/plugin/react-client/createWorkerStream.ts +76 -83
  142. package/plugin/react-client/plugin.ts +1 -4
  143. package/plugin/react-client/restartWorker.ts +2 -2
  144. package/plugin/react-client/server.ts +36 -62
  145. package/plugin/react-server/server.ts +17 -30
  146. package/plugin/react-static/configurePreviewServer.ts +75 -62
  147. package/plugin/react-static/plugin.ts +17 -11
  148. package/plugin/transformer/plugin.client.ts +4 -12
  149. package/plugin/transformer/plugin.server.ts +18 -25
  150. package/plugin/types.ts +12 -9
  151. package/plugin/utils/callServer.ts +20 -19
  152. package/plugin/utils/createReactFetcher.ts +6 -8
  153. package/plugin/utils/env.ts +1 -0
  154. package/plugin/utils/index.ts +2 -1
  155. package/plugin/utils/pageURL.ts +29 -24
  156. package/plugin/worker/createWorker.ts +4 -9
  157. package/plugin/worker/html/createHtmlWorkerRenderState.tsx +3 -0
  158. package/plugin/worker/rsc/handleRender.ts +2 -7
  159. package/plugin/worker/rsc/messageHandler.tsx +4 -7
  160. package/plugin/worker/rsc/rsc-worker.development.ts +1 -1
  161. package/plugin/worker/types.ts +7 -0
  162. package/dist/plugin/helpers/getRouteFiles.js.map +0 -1
  163. package/dist/plugin/helpers/requestToRoute.js.map +0 -1
@@ -1,7 +1,10 @@
1
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";
2
+ import type { RscWorkerOutputMessage, RscRenderMessage } from "../worker/types.js";
3
+ import type { StreamMetrics } from "../types.js";
4
+ import type { Worker as NodeWorker } from "node:worker_threads";
5
+ import type { StreamHandlers } from "../worker/types.js";
6
+ import { createMessageHandler } from "./createMessageHandlers.js";
7
+
5
8
  /**
6
9
  * Creates an async generator that yields RSC chunks from the worker.
7
10
  * Handles both module requests and RSC streaming.
@@ -13,95 +16,85 @@ import { Worker } from "node:worker_threads";
13
16
  * @returns An async generator that yields RSC chunks
14
17
  */
15
18
  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");
19
+ worker: NodeWorker,
20
+ message: Omit<RscRenderMessage, "type" | "id">,
21
+ logger: Logger,
22
+ onMetrics?: (metrics: StreamMetrics) => void
23
+ ): AsyncGenerator<Uint8Array> {
24
+ let messageHandler: ((message: RscWorkerOutputMessage | undefined) => void) | null = null;
25
+ let currentResolve: ((chunk: Uint8Array) => void) | null = null;
26
+ const handlers: StreamHandlers = {
27
+ onError: (error: any, errorInfo?: any) => {
28
+ logger.error(error.message + error.stack, {
29
+ error,
30
+ });
31
+ if (errorInfo) {
32
+ logger.error(errorInfo.componentStack);
33
+ }
34
+ },
35
+ onData: (chunk: Uint8Array) => {
36
+ currentResolve?.(chunk);
37
+ },
38
+ onEnd: () => {
39
+ currentResolve?.(new Uint8Array());
40
+ if (messageHandler) {
41
+ worker.removeListener("message", messageHandler);
42
+ messageHandler = null;
36
43
  }
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
44
+ },
45
+ onMetrics: (metrics: StreamMetrics) => {
46
+ onMetrics?.(metrics);
47
+ },
48
+ };
49
+
50
+ try {
51
+ // Remove any existing message handler before starting
52
+ if (messageHandler) {
53
+ worker.removeListener("message", messageHandler);
54
+ messageHandler = null;
55
+ }
56
+
57
+ worker.postMessage({
58
+ ...message,
59
+ type: "RSC_RENDER",
60
+ id: Math.random().toString(36).slice(2),
61
+ });
62
+
43
63
  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,
64
+ currentResolve = resolve;
65
+ messageHandler = createMessageHandler({
66
+ handlers,
67
+ logger,
74
68
  });
69
+ worker.on("message", messageHandler);
75
70
  });
76
-
77
- // Subsequent yields: handle RSC chunks until stream ends
71
+
78
72
  while (true) {
79
73
  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);
74
+ currentResolve = resolve;
75
+ // Create new message handler for each iteration
76
+ if (messageHandler) {
77
+ worker.removeListener("message", messageHandler);
78
+ }
79
+ messageHandler = createMessageHandler({
80
+ handlers,
81
+ logger,
82
+ });
83
+ worker.on("message", messageHandler);
100
84
  });
101
-
85
+
102
86
  if (chunk.length === 0) {
103
87
  break;
104
88
  }
89
+
105
90
  yield chunk;
106
91
  }
107
- }
92
+ } finally {
93
+ // Clean up message handler in finally block
94
+ if (messageHandler) {
95
+ worker.removeListener("message", messageHandler);
96
+ messageHandler = null;
97
+ }
98
+ handlers.onEnd();
99
+ }
100
+ }
@@ -65,7 +65,6 @@ export function reactClientPlugin(options: StreamPluginOptions): Plugin {
65
65
  userConfig = resolvedConfig.userConfig;
66
66
  return userConfig;
67
67
  },
68
-
69
68
  async configurePreviewServer(server) {
70
69
  await configurePreviewServer({
71
70
  server,
@@ -82,9 +81,7 @@ export function reactClientPlugin(options: StreamPluginOptions): Plugin {
82
81
  autoDiscoveredFiles,
83
82
  userOptions,
84
83
  hmrChannel,
85
- onMetrics: userOptions.onMetrics ? (metrics) => {
86
- userOptions.onMetrics?.(metrics);
87
- } : undefined,
84
+ onMetrics: userOptions.onMetrics,
88
85
  });
89
86
  },
90
87
 
@@ -22,8 +22,8 @@ export async function restartWorker(
22
22
  try {
23
23
  // Terminate the current worker if it exists
24
24
  if (currentWorker) {
25
- currentWorker.terminate();
26
- currentWorker = null;
25
+ currentWorker.removeAllListeners();
26
+ return currentWorker;
27
27
  }
28
28
  const routeCount = autoDiscoveredFiles.urlMap.size;
29
29
  const hmrBuffer = 20; // Buffer for HMR and other operations
@@ -6,21 +6,15 @@ import type {
6
6
  ResolvedUserOptions,
7
7
  StreamMetrics,
8
8
  } from "../types.js";
9
- import type {
10
- RscRenderMessage,
11
- } from "../worker/types.js";
9
+ import type { RscRenderMessage } from "../worker/types.js";
12
10
  import type { Worker as NodeWorker } from "node:worker_threads";
13
11
  import { MessageChannel } from "node:worker_threads";
14
- import {
15
- serializedOptions,
16
- } from "../helpers/serializeUserOptions.js";
17
- import { getRouteFiles } from "../helpers/getRouteFiles.js";
18
- import { requestToRoute } from "../helpers/requestToRoute.js";
12
+ import { serializedOptions } from "../helpers/serializeUserOptions.js";
13
+ import { requestInfo } from "../helpers/requestInfo.js";
19
14
  import { performance } from "node:perf_hooks";
20
15
  import { createWorkerStream } from "./createWorkerStream.js";
21
16
  import { restartWorker } from "./restartWorker.js";
22
17
 
23
-
24
18
  /**
25
19
  * Handles the RSC stream from the worker.
26
20
  * Creates a ReadableStream that pipes RSC chunks to the response.
@@ -103,65 +97,45 @@ export async function configureWorkerRequestHandler({
103
97
  ...handlerUserOptions
104
98
  } = _userOptions;
105
99
  const handlerOptions = Object.assign({}, handlerUserOptions, {
106
- moduleBaseURL:
107
- typeof server.config.server.host === "string"
108
- ? `${server.config.server.https ? "https" : "http"}://${
109
- server.config.server.host
110
- }:${server.config.server.port}`
111
- : _moduleBaseURL,
112
- moduleBasePath:
113
- server.config.base === "/"
114
- ? ""
115
- : server.config.base.endsWith("/")
116
- ? server.config.base.slice(0, -1)
117
- : server.config.base,
100
+ moduleBaseURL: server.config.base,
101
+ moduleBasePath: _moduleBasePath,
118
102
  projectRoot: server.config.root,
119
103
  });
120
104
 
121
105
  // Start the worker
122
- const currentWorker = await restartWorker(server, autoDiscoveredFiles, handlerOptions, hmrChannel);
106
+ const currentWorker = await restartWorker(
107
+ server,
108
+ autoDiscoveredFiles,
109
+ handlerOptions,
110
+ hmrChannel
111
+ );
123
112
 
124
113
  // Create the request handler
125
114
  const handler: RequestHandler = async (req, res, next) => {
126
- if (!req.url || req.headers.accept !== "text/x-component") return next();
127
- try {
128
- if (!currentWorker) {
129
- server.config.logger.error("[react-client] No worker available");
130
- return next();
131
- }
115
+ if (!req.url) return next();
132
116
 
133
- // Get the route from the request
134
- let route = requestToRoute(req, {
135
- moduleBasePath: handlerOptions.moduleBasePath,
136
- build: handlerOptions.build,
137
- });
138
- if (!route) {
139
- return next();
140
- }
141
- // in the case of the no build.pages and a async Page and or props userOption, we need to await those
142
- // if they are already autoDiscovered then the promise will resolve immediately
143
- const routeFiles = await getRouteFiles(
144
- route,
145
- autoDiscoveredFiles,
146
- handlerOptions
147
- );
148
- if (routeFiles.type === "error") {
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
- );
157
- return next();
158
- }
159
- const { page, props } = routeFiles;
117
+ const info = requestInfo(req, handlerOptions, "");
118
+ if (!info.isRscRequest) return next();
119
+
120
+ if (!currentWorker) {
121
+ server.config.logger.warn("[react-client] No worker available");
122
+ return next();
123
+ }
124
+
125
+ if (!autoDiscoveredFiles.urlMap.has(info.route)) {
126
+ server.config.logger.warn(`[react-client] No route found for route: ${info.route}`);
127
+ return next();
128
+ }
129
+ try {
130
+ const routeFiles = autoDiscoveredFiles.urlMap.get(info.route)!;
131
+ const pagePath = routeFiles.page;
132
+ const propsPath = routeFiles.props;
160
133
 
161
134
  // Set up response headers for streaming
162
- res.setHeader("Content-Type", "text/x-component; charset=utf-8");
135
+ res.setHeader("Content-Type", info.contentType);
163
136
  res.setHeader("Transfer-Encoding", "chunked");
164
137
  res.setHeader("Connection", "keep-alive");
138
+
165
139
  const serializedUserOptions = serializedOptions(
166
140
  handlerOptions,
167
141
  autoDiscoveredFiles
@@ -172,9 +146,9 @@ export async function configureWorkerRequestHandler({
172
146
  {
173
147
  ...serializedUserOptions,
174
148
  // we make the worker stream aware of the route, pagePath, propsPath
175
- route,
176
- pagePath: page,
177
- propsPath: props,
149
+ route: info.route,
150
+ pagePath: pagePath,
151
+ propsPath: propsPath,
178
152
  // override these at all times to ensure the settings will work for the dev server
179
153
  projectRoot: server.config.root,
180
154
  build: serializedUserOptions.build,
@@ -187,7 +161,7 @@ export async function configureWorkerRequestHandler({
187
161
  ? (metrics) => {
188
162
  const elapsedTime = performance.now() - startTime;
189
163
  const formattedMetrics = {
190
- route,
164
+ route: info.route,
191
165
  htmlSize: 0,
192
166
  rscSize: metrics.bytes,
193
167
  processingTime: elapsedTime,
@@ -196,10 +170,10 @@ export async function configureWorkerRequestHandler({
196
170
  memoryUsage: process.memoryUsage(),
197
171
  streamMetrics: {
198
172
  ...metrics,
199
- duration: elapsedTime
173
+ duration: elapsedTime,
200
174
  },
201
175
  htmlSizes: new Map(),
202
- rscSizes: new Map([[route, metrics.bytes]]),
176
+ rscSizes: new Map([[info.route, metrics.bytes]]),
203
177
  } satisfies RenderMetrics;
204
178
  onMetrics(formattedMetrics);
205
179
  }
@@ -6,6 +6,7 @@ import { collectViteModuleGraphCss } from "../helpers/collectViteModuleGraphCss.
6
6
  import { resolvePageAndProps } from "../helpers/resolvePageAndProps.js";
7
7
  import { createHandler } from "../helpers/createHandler.js";
8
8
  import React from "react";
9
+ import { requestInfo } from "../helpers/requestInfo.js";
9
10
 
10
11
  export async function configureReactServer({
11
12
  server,
@@ -31,18 +32,8 @@ export async function configureReactServer({
31
32
  } = _userOptions;
32
33
 
33
34
  const handlerOptions = Object.assign({}, handlerUserOptions, {
34
- moduleBaseURL:
35
- typeof server.config.server.host === "string"
36
- ? `${server.config.server.https ? "https" : "http"}://${
37
- server.config.server.host
38
- }:${server.config.server.port}`
39
- : _moduleBaseURL,
40
- moduleBasePath:
41
- server.config.base === "/"
42
- ? ""
43
- : server.config.base.endsWith("/")
44
- ? server.config.base.slice(0, -1)
45
- : server.config.base,
35
+ moduleBaseURL: server.config.base,
36
+ moduleBasePath: _moduleBasePath,
46
37
  projectRoot: server.config.root,
47
38
  });
48
39
  // Handle Vite server restarts
@@ -64,32 +55,28 @@ export async function configureReactServer({
64
55
  });
65
56
 
66
57
  server.middlewares.use(async (req, res, next) => {
58
+ if(!req.url) {
59
+ return next();
60
+ }
61
+ const info = requestInfo(req, handlerOptions, "");
62
+ if (!info.isRscRequest) return next();
67
63
  try {
68
- if (req.headers.accept !== "text/x-component") return next();
69
- let route = req.url?.replace("/" + handlerOptions.build.rscOutputPath, "");
70
- if(handlerOptions.moduleBasePath !== '' && !route?.startsWith(handlerOptions.moduleBasePath)) {
71
- next();
72
- } else if(route && handlerOptions.moduleBasePath.length) {
73
- route = route.slice(handlerOptions.moduleBasePath.length);
74
- }
75
- if (!route || route === "") {
76
- route = "/";
77
- }
78
- if(!route.startsWith("/")) {
79
- route = "/" + route;
80
- }
81
- if (!autoDiscoveredFiles.urlMap.has(route)) {
64
+ if (!autoDiscoveredFiles.urlMap.has(info.route)) {
82
65
  return next();
83
66
  }
84
- const routeFiles = autoDiscoveredFiles.urlMap.get(route)!;
67
+ const routeFiles = autoDiscoveredFiles.urlMap.get(info.route)!;
85
68
  const pagePath = routeFiles.page;
86
69
  const propsPath = routeFiles.props;
87
-
70
+ const port = server.config.server.port ?? 5173;
71
+ const host = server.config.server.host ?? 'localhost';
72
+ const protocol = server.config.server.https ? 'https' : 'http';
73
+ process.env['VITE_BASE_URL'] = `${server.config.base}${server.config.base.endsWith('/') ? '' : '/'}`;
74
+ process.env['VITE_PUBLIC_ORIGIN'] = `${protocol}://${host}:${port}`;
88
75
  // first load the page and props
89
76
  const pageAndPropsResult = await resolvePageAndProps({
90
77
  pagePath,
91
78
  propsPath,
92
- route,
79
+ route: info.route,
93
80
  loader: server.ssrLoadModule,
94
81
  pageExportName: handlerOptions.pageExportName ?? "default",
95
82
  propsExportName: handlerOptions.propsExportName ?? "default",
@@ -130,7 +117,7 @@ export async function configureReactServer({
130
117
  onEvent: eventHandler,
131
118
  manifest: serverManifest,
132
119
  worker: server as any,
133
- route,
120
+ route: info.route,
134
121
  pagePath,
135
122
  propsPath,
136
123
  cssFiles: cssFilesResult.cssFiles ?? new Map(),
@@ -1,10 +1,14 @@
1
1
  import type { PreviewServer } from "vite";
2
- import { MIME_TYPES } from "../config/mimeTypes.js";
3
2
  import type { ResolvedUserOptions } from "../types.js";
4
3
  import { join } from "node:path";
5
4
  import { createReadStream } from "node:fs";
6
5
  import { stat } from "node:fs/promises";
7
6
  import { pipeline } from "node:stream/promises";
7
+ import { requestInfo } from "../helpers/requestInfo.js";
8
+
9
+ interface StreamError extends Error {
10
+ code?: string;
11
+ }
8
12
 
9
13
  export async function configurePreviewServer({
10
14
  server,
@@ -13,77 +17,86 @@ export async function configurePreviewServer({
13
17
  server: PreviewServer;
14
18
  userOptions: ResolvedUserOptions;
15
19
  }) {
16
- const staticHostDir = join(userOptions.projectRoot, userOptions.build.outDir, userOptions.build.static);
20
+ const staticHostDir = join(
21
+ userOptions.projectRoot,
22
+ userOptions.build.outDir,
23
+ userOptions.build.static
24
+ );
17
25
  server.middlewares.use(async (req, res, next) => {
18
- if(!req.url) {
19
- console.log("no url")
26
+ if (!req.url) {
20
27
  return next();
21
28
  }
22
- const [withoutQuery] = req.url.split("?");
23
- const [, value] = userOptions.normalizer(withoutQuery);
24
- const ext = value.slice(value.lastIndexOf("."));
25
- // handle index.html
26
- const isHtml = userOptions.autoDiscover.htmlPattern(value)
27
- if (isHtml || (req.headers.accept?.includes("text/html"))) {
28
- const indexHtml = isHtml ? join(staticHostDir, value) : join(staticHostDir, value, userOptions.build.htmlOutputPath);
29
- try {
30
- const stats = await stat(indexHtml);
31
- if (stats.isFile()) {
32
- res.setHeader("Content-Type", "text/html; charset=utf-8");
33
- await pipeline(createReadStream(indexHtml), res);
34
- return;
35
- }
36
- } catch {
37
- // File doesn't exist, continue to next middleware
38
- }
39
- }
40
- const isRsc = userOptions.autoDiscover.rscPattern(value)
41
- if (isRsc || (req.headers.accept?.includes("text/x-component"))) {
42
- const rsc = isRsc ? join(staticHostDir, value) : join(staticHostDir, value, userOptions.build.rscOutputPath);
43
- try {
44
- const stats = await stat(rsc);
45
- if (stats.isFile()) {
46
- res.setHeader("Content-Type", "text/x-component; charset=utf-8");
47
- await pipeline(createReadStream(rsc), res);
48
- return;
49
- }
50
- } catch {
51
- // File doesn't exist, continue to next middleware
52
- }
53
- }
54
- const isCss = userOptions.autoDiscover.cssPattern(value)
55
- if (isCss || (req.headers.accept?.includes("text/css") && (ext === ""))) {
56
- const css = isCss ? join(staticHostDir, value) : join(staticHostDir, value);
57
- try {
58
- const stats = await stat(css);
59
- if (stats.isFile()) {
60
- res.setHeader("Content-Type", "text/css; charset=utf-8");
61
- await pipeline(createReadStream(css), res);
62
- return;
63
- }
64
- } catch {
65
- // File doesn't exist, continue to next middleware
66
- }
67
- }
29
+ const { contentType, filePath } = requestInfo(req, userOptions, staticHostDir);
30
+
68
31
  // Handle static files including CSS
69
- if (ext) {
70
- const filePath = join(staticHostDir, value);
32
+ if (filePath) {
71
33
  try {
72
34
  const stats = await stat(filePath);
73
35
  if (stats.isFile()) {
74
- // Set proper MIME type based on file extension
75
- const contentType = MIME_TYPES[ext];
76
- // Ensure CSS files are served with the correct MIME type
77
- if (contentType) {
78
- res.setHeader("Content-Type", `${contentType}; charset=utf-8`);
79
- } else {
80
- res.setHeader("Content-Type", "application/octet-stream");
36
+ res.setHeader("Content-Type", contentType);
37
+
38
+ // Create abort controller for the stream
39
+ const controller = new AbortController();
40
+ const { signal } = controller;
41
+
42
+ // Check if response is still writable before streaming
43
+ if (!res.writable) {
44
+ res.statusCode = 499;
45
+ res.end("Client closed request");
46
+ return;
47
+ }
48
+
49
+ try {
50
+ const readStream = createReadStream(filePath);
51
+ readStream.on('error', () => {
52
+ if (!res.writable) {
53
+ controller.abort();
54
+ }
55
+ });
56
+ await pipeline(readStream, res, { signal });
57
+ } catch (error) {
58
+ const streamError = error as StreamError;
59
+ // Handle different error cases
60
+ if (streamError.code === 'ERR_STREAM_PREMATURE_CLOSE' ||
61
+ streamError.name === 'AbortError') {
62
+ // Client closed the connection
63
+ if (res.writable) {
64
+ res.statusCode = 499;
65
+ res.end("Client closed request");
66
+ }
67
+ } else if (streamError.code === 'ENOENT') {
68
+ // File not found
69
+ res.statusCode = 404;
70
+ server.config.logger.error(`File not found: ${filePath}. ${streamError.message}`, {
71
+ error: streamError,
72
+ });
73
+ res.end("File not found");
74
+ } else {
75
+ // Server error
76
+ server.config.logger.error(`Error loading file: ${filePath}. ${streamError.message}`, {
77
+ error: streamError,
78
+ });
79
+ res.statusCode = 500;
80
+ res.end("Internal server error");
81
+ }
82
+ return;
81
83
  }
82
- await pipeline(createReadStream(filePath), res);
83
84
  return;
84
85
  }
85
- } catch {
86
- // File doesn't exist, continue to next middleware
86
+ } catch (error) {
87
+ const err = error as Error;
88
+ // Handle file system errors
89
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
90
+ res.statusCode = 404;
91
+ res.end("File not found");
92
+ } else {
93
+ server.config.logger.error(`Error loading file: ${filePath}.`, {
94
+ error: err,
95
+ });
96
+ res.statusCode = 500;
97
+ res.end("Internal server error");
98
+ }
99
+ return;
87
100
  }
88
101
  }
89
102
  next();