rsbuild-plugin-react-router 0.7.2 → 0.8.0

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 (53) hide show
  1. package/README.md +14 -2
  2. package/dist/468.js +48 -0
  3. package/dist/511.js +60 -86
  4. package/dist/819.js +64 -0
  5. package/dist/build-output-transforms.d.ts +3 -1
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/dev-hdr-channel.d.ts +9 -0
  8. package/dist/dev-hmr.d.ts +1 -22
  9. package/dist/dev-runtime-controller.d.ts +1 -6
  10. package/dist/dev-server.d.ts +3 -1
  11. package/dist/index.cjs +5417 -5080
  12. package/dist/index.js +1701 -1441
  13. package/dist/manifest-assets.d.ts +34 -0
  14. package/dist/manifest-snapshot.d.ts +17 -0
  15. package/dist/manifest-state.d.ts +12 -0
  16. package/dist/manifest.d.ts +2 -22
  17. package/dist/modify-browser-manifest.d.ts +2 -0
  18. package/dist/node-only-manifest.d.ts +8 -0
  19. package/dist/plugin-utils.d.ts +1 -1
  20. package/dist/rsc-prerender.d.ts +3 -2
  21. package/dist/server-build-worker-client.d.ts +20 -0
  22. package/dist/server-build-worker-protocol.d.ts +84 -0
  23. package/dist/server-build-worker.d.ts +1 -0
  24. package/dist/server-build-worker.js +123 -0
  25. package/dist/server-utils.d.ts +1 -2
  26. package/dist/types.d.ts +6 -0
  27. package/package.json +4 -4
  28. package/src/build-output-transforms.ts +22 -1
  29. package/src/classic-mode.ts +0 -1
  30. package/src/constants.ts +3 -0
  31. package/src/dev-hdr-channel.ts +38 -0
  32. package/src/dev-hmr.ts +57 -100
  33. package/src/dev-runtime-controller.ts +23 -18
  34. package/src/dev-server.ts +24 -4
  35. package/src/index.ts +229 -144
  36. package/src/lazy-compilation.ts +7 -2
  37. package/src/manifest-assets.ts +228 -0
  38. package/src/manifest-snapshot.ts +80 -0
  39. package/src/manifest-state.ts +71 -0
  40. package/src/manifest.ts +23 -161
  41. package/src/mode-plan.ts +12 -4
  42. package/src/modify-browser-manifest.ts +47 -18
  43. package/src/node-only-manifest.ts +52 -0
  44. package/src/plugin-utils.ts +6 -2
  45. package/src/prerender-build.ts +76 -86
  46. package/src/route-chunks.ts +152 -63
  47. package/src/rsc-prerender.ts +7 -35
  48. package/src/server-build-resolution.ts +1 -2
  49. package/src/server-build-worker-client.ts +221 -0
  50. package/src/server-build-worker-protocol.ts +69 -0
  51. package/src/server-build-worker.ts +192 -0
  52. package/src/server-utils.ts +0 -2
  53. package/src/types.ts +7 -0
@@ -0,0 +1,69 @@
1
+ // Messages between the build process and `server-build-worker`.
2
+
3
+ /** Headers as a structured-cloneable list (the DOM lib's Headers is not iterable here). */
4
+ export const headerEntries = (headers: Headers): [string, string][] => {
5
+ const entries: [string, string][] = [];
6
+ headers.forEach((value, key) => entries.push([key, value]));
7
+ return entries;
8
+ };
9
+
10
+ export type ServerBuildWorkerData = {
11
+ serverBuildPath: string;
12
+ mode: 'classic' | 'rsc';
13
+ };
14
+
15
+ export type ServerBuildWorkerRequest =
16
+ | { type: 'close' }
17
+ | {
18
+ id: number;
19
+ type: 'request';
20
+ url: string;
21
+ method: string;
22
+ headers: [string, string][];
23
+ body?: Uint8Array<ArrayBuffer>;
24
+ }
25
+ /** Consume a response body only when the parent asks for it. */
26
+ | { id: number; type: 'read' }
27
+ /** The parent released the request, possibly without reading its body. */
28
+ | { id: number; type: 'abort' };
29
+
30
+ export type SerializedResponse = {
31
+ status: number;
32
+ statusText: string;
33
+ headers: [string, string][];
34
+ hasBody: boolean;
35
+ };
36
+
37
+ export type SerializedError = {
38
+ message: string;
39
+ stack?: string;
40
+ name?: string;
41
+ };
42
+
43
+ export type ServerBuildWorkerResponse =
44
+ | { type: 'closed' }
45
+ /** Sent once the bundle is evaluated; carries the classic build description. */
46
+ | { type: 'ready'; description?: ServerBuildDescription }
47
+ | { type: 'reply'; id: number; ok: true; response: SerializedResponse }
48
+ | { type: 'body'; id: number; ok: true; body: Uint8Array<ArrayBuffer> }
49
+ | { type: 'reply'; id: number; ok: false; error: SerializedError };
50
+
51
+ /**
52
+ * The parts of a classic React Router server build that build-time rendering
53
+ * reads, as plain data: route module exports are reported by presence only.
54
+ */
55
+ export type ServerBuildDescription = {
56
+ prerender?: string[];
57
+ routes: Record<
58
+ string,
59
+ {
60
+ id?: string;
61
+ parentId?: string;
62
+ path?: string;
63
+ index?: boolean;
64
+ caseSensitive?: boolean;
65
+ module: { default: boolean; ErrorBoundary: boolean; loader: boolean };
66
+ }
67
+ >;
68
+ assets: { routes: Record<string, { hasLoader?: boolean }> };
69
+ };
@@ -0,0 +1,192 @@
1
+ // Worker entry: evaluates a built server bundle and serves requests to it for
2
+ // build-time rendering; see `startServerBuildWorker` for why this is a worker.
3
+ // `IS_RR_BUILD_REQUEST` is set for this module graph only.
4
+ import { parentPort, workerData } from 'node:worker_threads';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { createRequestHandler, type ServerBuild } from 'react-router';
7
+ import { PLUGIN_NAME } from './constants.js';
8
+ import { resolveServerBuildModule } from './server-build-resolution.js';
9
+ import {
10
+ headerEntries,
11
+ type ServerBuildWorkerData,
12
+ type ServerBuildWorkerRequest,
13
+ type ServerBuildWorkerResponse,
14
+ type ServerBuildDescription,
15
+ type SerializedError,
16
+ } from './server-build-worker-protocol.js';
17
+
18
+ const port = parentPort;
19
+ if (!port) {
20
+ throw new Error('server-build-worker must run as a worker thread');
21
+ }
22
+
23
+ const { serverBuildPath, mode } = workerData as ServerBuildWorkerData;
24
+ process.env.IS_RR_BUILD_REQUEST = 'yes';
25
+
26
+ const post = (
27
+ message: ServerBuildWorkerResponse,
28
+ transfer: ArrayBuffer[] = []
29
+ ): void => {
30
+ port.postMessage(message, transfer);
31
+ };
32
+
33
+ const serializeError = (error: unknown): SerializedError => {
34
+ const value = error as { message?: unknown; stack?: unknown; name?: unknown };
35
+ return {
36
+ message: String(value?.message ?? error),
37
+ stack: typeof value?.stack === 'string' ? value.stack : undefined,
38
+ name: typeof value?.name === 'string' ? value.name : undefined,
39
+ };
40
+ };
41
+
42
+ const describeClassicBuild = (build: ServerBuild): ServerBuildDescription => ({
43
+ prerender: build.prerender,
44
+ routes: Object.fromEntries(
45
+ Object.entries(build.routes).flatMap(([id, route]) =>
46
+ route
47
+ ? [
48
+ [
49
+ id,
50
+ {
51
+ id: route.id,
52
+ parentId: route.parentId,
53
+ path: route.path,
54
+ index: route.index,
55
+ caseSensitive: route.caseSensitive,
56
+ module: {
57
+ default: route.module.default !== undefined,
58
+ ErrorBoundary: route.module.ErrorBoundary !== undefined,
59
+ loader: route.module.loader !== undefined,
60
+ },
61
+ },
62
+ ],
63
+ ]
64
+ : []
65
+ )
66
+ ),
67
+ assets: {
68
+ routes: Object.fromEntries(
69
+ Object.entries(build.assets.routes).flatMap(([id, route]) =>
70
+ route ? [[id, { hasLoader: route.hasLoader }]] : []
71
+ )
72
+ ),
73
+ },
74
+ });
75
+
76
+ const resolveRscFetch = (
77
+ buildModule: unknown
78
+ ): ((request: Request) => Promise<Response>) => {
79
+ const moduleRecord = buildModule as
80
+ | { default?: { fetch?: unknown; default?: { fetch?: unknown } } }
81
+ | undefined;
82
+ const fetch =
83
+ typeof moduleRecord?.default?.fetch === 'function'
84
+ ? moduleRecord.default.fetch
85
+ : typeof moduleRecord?.default?.default?.fetch === 'function'
86
+ ? moduleRecord.default.default.fetch
87
+ : null;
88
+ if (!fetch) {
89
+ throw new Error(
90
+ `[${PLUGIN_NAME}] RSC server build ${JSON.stringify(
91
+ serverBuildPath
92
+ )} must default-export an object with a fetch function.`
93
+ );
94
+ }
95
+ return fetch as (request: Request) => Promise<Response>;
96
+ };
97
+
98
+ const buildModule = await import(pathToFileURL(serverBuildPath).href);
99
+ let description: ServerBuildDescription | undefined;
100
+ let handler: (request: Request) => Promise<Response>;
101
+ if (mode === 'classic') {
102
+ const build = await resolveServerBuildModule(
103
+ buildModule,
104
+ `Server build ${JSON.stringify(serverBuildPath)}`
105
+ );
106
+ description = describeClassicBuild(build);
107
+ handler = createRequestHandler(build, 'production');
108
+ } else {
109
+ handler = resolveRscFetch(buildModule);
110
+ }
111
+
112
+ // One AbortController per in-flight request, so the Request the app receives
113
+ // is aborted when the parent releases it (`createBuildRequestEffect`) or once
114
+ // its response has been consumed here, mirroring the in-process contract.
115
+ const controllers = new Map<number, AbortController>();
116
+ const responses = new Map<number, Response>();
117
+ const release = (id: number): void => {
118
+ controllers.get(id)?.abort();
119
+ controllers.delete(id);
120
+ responses.delete(id);
121
+ };
122
+
123
+ port.on('message', async (message: ServerBuildWorkerRequest) => {
124
+ if (message.type === 'close') {
125
+ for (const id of controllers.keys()) release(id);
126
+ post({ type: 'closed' });
127
+ return;
128
+ }
129
+ if (message.type === 'abort') {
130
+ // Cancellation can itself stay pending in application streams. It must
131
+ // not prevent request cleanup or worker shutdown.
132
+ void responses
133
+ .get(message.id)
134
+ ?.body?.cancel()
135
+ .catch(() => {});
136
+ release(message.id);
137
+ return;
138
+ }
139
+ if (message.type === 'read') {
140
+ try {
141
+ const response = responses.get(message.id);
142
+ if (!response) throw new Error('Server build response was released');
143
+ const body = new Uint8Array(await response.arrayBuffer());
144
+ release(message.id);
145
+ post({ type: 'body', id: message.id, ok: true, body }, [body.buffer]);
146
+ } catch (error) {
147
+ release(message.id);
148
+ post({
149
+ type: 'reply',
150
+ id: message.id,
151
+ ok: false,
152
+ error: serializeError(error),
153
+ });
154
+ }
155
+ return;
156
+ }
157
+ const controller = new AbortController();
158
+ controllers.set(message.id, controller);
159
+ try {
160
+ const response = await handler(
161
+ new Request(message.url, {
162
+ method: message.method,
163
+ headers: message.headers,
164
+ body: message.body,
165
+ signal: controller.signal,
166
+ })
167
+ );
168
+ if (response.body) responses.set(message.id, response);
169
+ else release(message.id);
170
+ post({
171
+ type: 'reply',
172
+ id: message.id,
173
+ ok: true,
174
+ response: {
175
+ status: response.status,
176
+ statusText: response.statusText,
177
+ headers: headerEntries(response.headers),
178
+ hasBody: response.body !== null,
179
+ },
180
+ });
181
+ } catch (error) {
182
+ release(message.id);
183
+ post({
184
+ type: 'reply',
185
+ id: message.id,
186
+ ok: false,
187
+ error: serializeError(error),
188
+ });
189
+ }
190
+ });
191
+
192
+ post({ type: 'ready', description });
@@ -88,8 +88,6 @@ export function generateServerBuild(
88
88
  `;
89
89
  }
90
90
 
91
- export { resolveServerBuildModule };
92
-
93
91
  export function resolveReactRouterServerBuild(
94
92
  buildModule: unknown
95
93
  ): Promise<ServerBuild> {
package/src/types.ts CHANGED
@@ -11,6 +11,13 @@ export type Route = {
11
11
  };
12
12
 
13
13
  export type PluginOptions = {
14
+ /**
15
+ * Generate React Router route types during development and builds.
16
+ * Set to false when type generation is managed separately.
17
+ * @default true
18
+ */
19
+ typegen?: boolean;
20
+
14
21
  /**
15
22
  * Whether to disable automatic middleware setup for custom server implementation.
16
23
  * Use this when you want to handle server setup manually.