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.
- package/README.md +14 -2
- package/dist/468.js +48 -0
- package/dist/511.js +60 -86
- package/dist/819.js +64 -0
- package/dist/build-output-transforms.d.ts +3 -1
- package/dist/constants.d.ts +1 -0
- package/dist/dev-hdr-channel.d.ts +9 -0
- package/dist/dev-hmr.d.ts +1 -22
- package/dist/dev-runtime-controller.d.ts +1 -6
- package/dist/dev-server.d.ts +3 -1
- package/dist/index.cjs +5417 -5080
- package/dist/index.js +1701 -1441
- package/dist/manifest-assets.d.ts +34 -0
- package/dist/manifest-snapshot.d.ts +17 -0
- package/dist/manifest-state.d.ts +12 -0
- package/dist/manifest.d.ts +2 -22
- package/dist/modify-browser-manifest.d.ts +2 -0
- package/dist/node-only-manifest.d.ts +8 -0
- package/dist/plugin-utils.d.ts +1 -1
- package/dist/rsc-prerender.d.ts +3 -2
- package/dist/server-build-worker-client.d.ts +20 -0
- package/dist/server-build-worker-protocol.d.ts +84 -0
- package/dist/server-build-worker.d.ts +1 -0
- package/dist/server-build-worker.js +123 -0
- package/dist/server-utils.d.ts +1 -2
- package/dist/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/build-output-transforms.ts +22 -1
- package/src/classic-mode.ts +0 -1
- package/src/constants.ts +3 -0
- package/src/dev-hdr-channel.ts +38 -0
- package/src/dev-hmr.ts +57 -100
- package/src/dev-runtime-controller.ts +23 -18
- package/src/dev-server.ts +24 -4
- package/src/index.ts +229 -144
- package/src/lazy-compilation.ts +7 -2
- package/src/manifest-assets.ts +228 -0
- package/src/manifest-snapshot.ts +80 -0
- package/src/manifest-state.ts +71 -0
- package/src/manifest.ts +23 -161
- package/src/mode-plan.ts +12 -4
- package/src/modify-browser-manifest.ts +47 -18
- package/src/node-only-manifest.ts +52 -0
- package/src/plugin-utils.ts +6 -2
- package/src/prerender-build.ts +76 -86
- package/src/route-chunks.ts +152 -63
- package/src/rsc-prerender.ts +7 -35
- package/src/server-build-resolution.ts +1 -2
- package/src/server-build-worker-client.ts +221 -0
- package/src/server-build-worker-protocol.ts +69 -0
- package/src/server-build-worker.ts +192 -0
- package/src/server-utils.ts +0 -2
- 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 });
|
package/src/server-utils.ts
CHANGED
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.
|