rsbuild-plugin-react-router 0.3.0 → 0.3.1
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 +55 -24
- package/dist/451.js +36 -20
- package/dist/build-manifest.d.ts +6 -3
- package/dist/concurrency.d.ts +2 -1
- package/dist/config-imports.d.ts +7 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +2 -2
- package/dist/dev-runtime-artifacts.d.ts +9 -1
- package/dist/dev-runtime-compilation.d.ts +17 -2
- package/dist/dev-server.d.ts +5 -0
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +2 -2
- package/dist/index.cjs +11448 -891
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7950 -758
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +2 -1
- package/dist/manifest.d.ts +19 -4
- package/dist/parallel-route-transforms.d.ts +17 -2
- package/dist/prerender-build.d.ts +4 -0
- package/dist/prerender.d.ts +0 -1
- package/dist/react-router-config.d.ts +8 -5
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/typegen.d.ts +14 -1
- package/dist/types.d.ts +15 -6
- package/package.json +11 -9
- package/src/build-manifest.ts +110 -73
- package/src/concurrency.ts +3 -22
- package/src/config-imports.ts +38 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +43 -53
- package/src/dev-runtime-artifacts.ts +50 -18
- package/src/dev-runtime-compilation.ts +80 -1
- package/src/dev-runtime-controller.ts +111 -31
- package/src/dev-runtime-session.ts +18 -11
- package/src/dev-server.ts +31 -1
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +82 -23
- package/src/index.ts +118 -153
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +12 -5
- package/src/manifest.ts +366 -255
- package/src/modify-browser-manifest.ts +2 -1
- package/src/parallel-route-transforms.ts +195 -69
- package/src/prerender-build.ts +125 -61
- package/src/prerender.ts +0 -18
- package/src/react-router-config.ts +98 -73
- package/src/route-artifacts.ts +2 -2
- package/src/route-export-resolution.ts +3 -3
- package/src/route-watch.ts +119 -84
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +7 -106
- package/src/ssr-externals.ts +1 -1
- package/src/typegen.ts +162 -33
- package/src/types.ts +16 -6
|
@@ -163,9 +163,10 @@ export function registerModifyBrowserManifestAssets(
|
|
|
163
163
|
const browserManifestAsset = assets[BROWSER_MANIFEST_ASSET];
|
|
164
164
|
if (browserManifestAsset) {
|
|
165
165
|
const originalSource = browserManifestAsset.source().toString();
|
|
166
|
+
const serializedManifest = jsesc(manifestForBrowser, { es6: true });
|
|
166
167
|
const newSource = originalSource.replace(
|
|
167
168
|
/["'`]PLACEHOLDER["'`]/,
|
|
168
|
-
|
|
169
|
+
() => serializedManifest
|
|
169
170
|
);
|
|
170
171
|
compilation.updateAsset(
|
|
171
172
|
BROWSER_MANIFEST_ASSET,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import { setBoundedCacheEntry } from './bounded-cache.js';
|
|
3
|
-
import { getDefaultConcurrency } from './concurrency.js';
|
|
3
|
+
import { getAvailableCpuCount, getDefaultConcurrency } from './concurrency.js';
|
|
4
|
+
import { normalizeEffectError } from './effect-runtime.js';
|
|
4
5
|
import {
|
|
5
6
|
executeRouteTransformTask,
|
|
6
7
|
type RouteTransformResult,
|
|
@@ -22,10 +23,12 @@ export type ParallelRouteTransformConfig =
|
|
|
22
23
|
export type RouteTransformExecutorOptions = RouteTransformTaskOptions & {
|
|
23
24
|
parallelRouteTransform?: PluginOptions['parallelRouteTransform'];
|
|
24
25
|
splitRouteModules?: boolean;
|
|
26
|
+
isBuild?: boolean;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export type RouteTransformExecutor = {
|
|
28
30
|
run: (task: RouteTransformTask) => Promise<RouteTransformResult>;
|
|
31
|
+
prewarm: () => void;
|
|
29
32
|
close: () => Promise<void>;
|
|
30
33
|
};
|
|
31
34
|
|
|
@@ -34,8 +37,18 @@ type PendingTask = {
|
|
|
34
37
|
reject: (error: Error) => void;
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
type RouteTransformWorker = {
|
|
41
|
+
on(event: 'message', handler: (response: WorkerResponse) => void): unknown;
|
|
42
|
+
on(event: 'error', handler: (error: Error) => void): unknown;
|
|
43
|
+
on(event: 'exit', handler: (code: number) => void): unknown;
|
|
44
|
+
postMessage(message: WorkerRequest): void;
|
|
45
|
+
terminate(): Promise<number> | number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type RouteTransformWorkerFactory = () => RouteTransformWorker;
|
|
49
|
+
|
|
37
50
|
type WorkerState = {
|
|
38
|
-
worker:
|
|
51
|
+
worker: RouteTransformWorker;
|
|
39
52
|
pending: Map<number, PendingTask>;
|
|
40
53
|
sourceCache: Map<string, string>;
|
|
41
54
|
};
|
|
@@ -48,19 +61,41 @@ class WorkerStartupError extends Error {
|
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
const MAX_WORKER_SOURCE_CACHE_ENTRIES = 2048;
|
|
64
|
+
const MAX_ROUTE_SOURCE_WORKER_ENTRIES = 4096;
|
|
51
65
|
const AUTO_PARALLEL_ROUTE_THRESHOLD = 256;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
const DEFAULT_WORKER_COUNT_LIMIT = 2;
|
|
67
|
+
// Benchmarked on a 4-core cpuset: in dev (large-355 fixture) a single worker
|
|
68
|
+
// recovered no wall time while worker startup and IPC cost ~0.5s of
|
|
69
|
+
// dev-server ready (9.09s -> 8.57s median when disabled), so small machines
|
|
70
|
+
// run dev transforms inline. Production builds are different: at 1024 routes
|
|
71
|
+
// the transform volume is large enough that two workers beat inline by ~8%
|
|
72
|
+
// (synthetic-1024-ssr-esm build, 4.46s inline vs 4.09s with workers), so
|
|
73
|
+
// builds keep the default worker count even on small machines.
|
|
74
|
+
const SMALL_MACHINE_CPU_COUNT = 4;
|
|
75
|
+
|
|
76
|
+
export const getDefaultWorkerCount = (
|
|
77
|
+
cpuCount?: number,
|
|
78
|
+
{ isBuild = false }: { isBuild?: boolean } = {}
|
|
79
|
+
): number => {
|
|
80
|
+
const resolvedCpuCount = cpuCount ?? getAvailableCpuCount();
|
|
81
|
+
if (!isBuild && resolvedCpuCount <= SMALL_MACHINE_CPU_COUNT) {
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
return Math.min(
|
|
85
|
+
getDefaultConcurrency(resolvedCpuCount),
|
|
86
|
+
DEFAULT_WORKER_COUNT_LIMIT
|
|
87
|
+
);
|
|
88
|
+
};
|
|
55
89
|
|
|
56
90
|
export const shouldParallelizeRouteTransforms = (routeCount: number): boolean =>
|
|
57
91
|
routeCount >= AUTO_PARALLEL_ROUTE_THRESHOLD;
|
|
58
92
|
|
|
59
93
|
const getConfiguredWorkerCount = (
|
|
60
|
-
parallelRouteTransform: ParallelRouteTransformConfig
|
|
94
|
+
parallelRouteTransform: ParallelRouteTransformConfig,
|
|
95
|
+
isBuild: boolean
|
|
61
96
|
): number => {
|
|
62
97
|
if (parallelRouteTransform === true) {
|
|
63
|
-
return getDefaultWorkerCount();
|
|
98
|
+
return getDefaultWorkerCount(undefined, { isBuild });
|
|
64
99
|
}
|
|
65
100
|
|
|
66
101
|
if (!Number.isInteger(parallelRouteTransform) || parallelRouteTransform < 1) {
|
|
@@ -91,37 +126,50 @@ const deserializeWorkerError = (error: WorkerErrorPayload): Error => {
|
|
|
91
126
|
const createWorkerUrl = (): URL =>
|
|
92
127
|
new URL('./parallel-route-transform-worker.js', import.meta.url);
|
|
93
128
|
|
|
129
|
+
const createDefaultWorker = (): RouteTransformWorker =>
|
|
130
|
+
new Worker(createWorkerUrl());
|
|
131
|
+
|
|
94
132
|
const isWorkerStartupError = (error: unknown): error is WorkerStartupError =>
|
|
95
133
|
error instanceof WorkerStartupError;
|
|
96
134
|
|
|
135
|
+
const createWorkerStartupError = (error: unknown): WorkerStartupError => {
|
|
136
|
+
const normalized = normalizeEffectError(error);
|
|
137
|
+
const startupError = new WorkerStartupError(normalized.message);
|
|
138
|
+
startupError.stack = normalized.stack;
|
|
139
|
+
return startupError;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const rejectPendingTasks = (state: WorkerState, error: Error): void => {
|
|
143
|
+
for (const pending of state.pending.values()) {
|
|
144
|
+
pending.reject(error);
|
|
145
|
+
}
|
|
146
|
+
state.pending.clear();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const createInlineRouteTransformExecutor = (
|
|
150
|
+
options: RouteTransformTaskOptions
|
|
151
|
+
): RouteTransformExecutor => ({
|
|
152
|
+
run: task => executeRouteTransformTask(task, options),
|
|
153
|
+
prewarm: () => {},
|
|
154
|
+
close: async () => {},
|
|
155
|
+
});
|
|
156
|
+
|
|
97
157
|
class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
98
158
|
#closed = false;
|
|
99
159
|
#closePromise: Promise<void> | undefined;
|
|
100
160
|
#workersDisabled = false;
|
|
101
161
|
#nextId = 1;
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
162
|
+
#nextRouteSourceWorkerIndex = 0;
|
|
163
|
+
#routeSourceWorkers = new Map<string, number>();
|
|
164
|
+
#workers: Array<WorkerState | undefined> | undefined;
|
|
165
|
+
#prewarmScheduled = false;
|
|
106
166
|
|
|
107
167
|
constructor(
|
|
108
|
-
workerCount: number,
|
|
168
|
+
private readonly workerCount: number,
|
|
109
169
|
private readonly options: RouteTransformTaskOptions,
|
|
110
|
-
private readonly balanceRouteModuleTransforms: boolean
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
for (let index = 0; index < workerCount; index += 1) {
|
|
115
|
-
this.#workers.push(this.#createWorkerState());
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
for (const state of this.#workers) {
|
|
119
|
-
void state.worker.terminate();
|
|
120
|
-
}
|
|
121
|
-
this.#workers = [];
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
170
|
+
private readonly balanceRouteModuleTransforms: boolean,
|
|
171
|
+
private readonly createWorker: RouteTransformWorkerFactory
|
|
172
|
+
) {}
|
|
125
173
|
|
|
126
174
|
async run(task: RouteTransformTask): Promise<RouteTransformResult> {
|
|
127
175
|
if (this.#closed) {
|
|
@@ -141,19 +189,44 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
141
189
|
}
|
|
142
190
|
}
|
|
143
191
|
|
|
192
|
+
prewarm(): void {
|
|
193
|
+
if (this.#prewarmScheduled || this.#closed || this.#workersDisabled) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
this.#prewarmScheduled = true;
|
|
197
|
+
void this.#prewarmWorkers();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async #prewarmWorkers(): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
for (let index = 0; index < this.workerCount; index += 1) {
|
|
203
|
+
if (this.#closed || this.#workersDisabled) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
this.#getWorker(index);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
this.#disableWorkers(createWorkerStartupError(error));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
this.#prewarmScheduled = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
144
218
|
close(): Promise<void> {
|
|
145
219
|
if (this.#closePromise) {
|
|
146
220
|
return this.#closePromise;
|
|
147
221
|
}
|
|
148
222
|
this.#closed = true;
|
|
149
|
-
const workers = this.#workers
|
|
223
|
+
const workers = (this.#workers ?? []).filter(
|
|
224
|
+
(state): state is WorkerState => Boolean(state)
|
|
225
|
+
);
|
|
150
226
|
this.#workers = [];
|
|
151
227
|
this.#closePromise = Promise.all(
|
|
152
228
|
workers.map(async state => {
|
|
153
|
-
|
|
154
|
-
pending.reject(new Error('Route transform worker closed.'));
|
|
155
|
-
}
|
|
156
|
-
state.pending.clear();
|
|
229
|
+
rejectPendingTasks(state, new Error('Route transform worker closed.'));
|
|
157
230
|
await state.worker.terminate();
|
|
158
231
|
})
|
|
159
232
|
).then(() => undefined);
|
|
@@ -165,19 +238,18 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
165
238
|
return;
|
|
166
239
|
}
|
|
167
240
|
this.#workersDisabled = true;
|
|
168
|
-
const workers = this.#workers
|
|
241
|
+
const workers = (this.#workers ?? []).filter(
|
|
242
|
+
(state): state is WorkerState => Boolean(state)
|
|
243
|
+
);
|
|
169
244
|
this.#workers = [];
|
|
170
245
|
for (const state of workers) {
|
|
171
|
-
|
|
172
|
-
pending.reject(error);
|
|
173
|
-
}
|
|
174
|
-
state.pending.clear();
|
|
246
|
+
rejectPendingTasks(state, error);
|
|
175
247
|
void state.worker.terminate();
|
|
176
248
|
}
|
|
177
249
|
}
|
|
178
250
|
|
|
179
251
|
#createWorkerState(): WorkerState {
|
|
180
|
-
const worker =
|
|
252
|
+
const worker = this.createWorker();
|
|
181
253
|
const state: WorkerState = {
|
|
182
254
|
worker,
|
|
183
255
|
pending: new Map(),
|
|
@@ -198,9 +270,7 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
198
270
|
});
|
|
199
271
|
|
|
200
272
|
worker.on('error', (error: Error) => {
|
|
201
|
-
|
|
202
|
-
startupError.stack = error.stack;
|
|
203
|
-
this.#disableWorkers(startupError);
|
|
273
|
+
this.#disableWorkers(createWorkerStartupError(error));
|
|
204
274
|
});
|
|
205
275
|
|
|
206
276
|
worker.on('exit', code => {
|
|
@@ -216,9 +286,24 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
216
286
|
return state;
|
|
217
287
|
}
|
|
218
288
|
|
|
289
|
+
#getWorker(index: number): WorkerState | undefined {
|
|
290
|
+
if (this.#closed || this.#workersDisabled) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
this.#workers ??= new Array(this.workerCount);
|
|
294
|
+
const existingWorker = this.#workers[index];
|
|
295
|
+
if (existingWorker) {
|
|
296
|
+
return existingWorker;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const worker = this.#createWorkerState();
|
|
300
|
+
this.#workers[index] = worker;
|
|
301
|
+
return worker;
|
|
302
|
+
}
|
|
303
|
+
|
|
219
304
|
#runInWorker(task: RouteTransformTask): Promise<RouteTransformResult> {
|
|
220
|
-
const workerIndex = this.#getWorkerIndex(task);
|
|
221
|
-
const state = this.#
|
|
305
|
+
const workerIndex = this.#getWorkerIndex(task, this.workerCount);
|
|
306
|
+
const state = this.#getWorker(workerIndex);
|
|
222
307
|
if (!state) {
|
|
223
308
|
return executeRouteTransformTask(task, this.options);
|
|
224
309
|
}
|
|
@@ -243,7 +328,7 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
243
328
|
// The worker may not have received the source update. Force the next
|
|
244
329
|
// request for this module to send its full source again.
|
|
245
330
|
state.sourceCache.delete(sourceCacheKey);
|
|
246
|
-
reject(
|
|
331
|
+
reject(normalizeEffectError(error));
|
|
247
332
|
}
|
|
248
333
|
});
|
|
249
334
|
}
|
|
@@ -268,35 +353,51 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor {
|
|
|
268
353
|
return task;
|
|
269
354
|
}
|
|
270
355
|
|
|
271
|
-
#getWorkerIndex(task: RouteTransformTask): number {
|
|
272
|
-
const
|
|
356
|
+
#getWorkerIndex(task: RouteTransformTask, workerCount: number): number {
|
|
357
|
+
const safeWorkerCount = Math.max(1, workerCount);
|
|
273
358
|
if (
|
|
274
359
|
this.balanceRouteModuleTransforms &&
|
|
275
360
|
(task.kind === 'routeClientEntry' ||
|
|
276
361
|
task.kind === 'routeChunk' ||
|
|
277
362
|
task.kind === 'splitRouteExports')
|
|
278
363
|
) {
|
|
279
|
-
|
|
280
|
-
task.resourcePath
|
|
364
|
+
return this.#getRouteSourceWorkerIndex(
|
|
365
|
+
task.resourcePath,
|
|
366
|
+
safeWorkerCount
|
|
281
367
|
);
|
|
282
|
-
if (existingWorkerIndex !== undefined) {
|
|
283
|
-
return existingWorkerIndex % workerCount;
|
|
284
|
-
}
|
|
285
|
-
const workerIndex = this.#nextSplitRouteAnalysisWorkerIndex % workerCount;
|
|
286
|
-
this.#nextSplitRouteAnalysisWorkerIndex += 1;
|
|
287
|
-
this.#splitRouteAnalysisWorkers.set(task.resourcePath, workerIndex);
|
|
288
|
-
return workerIndex;
|
|
289
368
|
}
|
|
290
369
|
if (
|
|
291
370
|
this.balanceRouteModuleTransforms &&
|
|
292
371
|
task.kind === 'routeModule' &&
|
|
293
372
|
!(task.environmentName === 'web' && !task.ssr && task.isSpaMode)
|
|
294
373
|
) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
374
|
+
return this.#getRouteSourceWorkerIndex(
|
|
375
|
+
task.resourcePath,
|
|
376
|
+
safeWorkerCount
|
|
377
|
+
);
|
|
298
378
|
}
|
|
299
|
-
return hashString(task.resourcePath) %
|
|
379
|
+
return hashString(task.resourcePath) % safeWorkerCount;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#getRouteSourceWorkerIndex(
|
|
383
|
+
resourcePath: string,
|
|
384
|
+
safeWorkerCount: number
|
|
385
|
+
): number {
|
|
386
|
+
const existingWorkerIndex = this.#routeSourceWorkers.get(resourcePath);
|
|
387
|
+
if (existingWorkerIndex !== undefined) {
|
|
388
|
+
this.#routeSourceWorkers.delete(resourcePath);
|
|
389
|
+
this.#routeSourceWorkers.set(resourcePath, existingWorkerIndex);
|
|
390
|
+
return existingWorkerIndex % safeWorkerCount;
|
|
391
|
+
}
|
|
392
|
+
const workerIndex = this.#nextRouteSourceWorkerIndex % safeWorkerCount;
|
|
393
|
+
this.#nextRouteSourceWorkerIndex += 1;
|
|
394
|
+
setBoundedCacheEntry(
|
|
395
|
+
this.#routeSourceWorkers,
|
|
396
|
+
resourcePath,
|
|
397
|
+
workerIndex,
|
|
398
|
+
MAX_ROUTE_SOURCE_WORKER_ENTRIES
|
|
399
|
+
);
|
|
400
|
+
return workerIndex;
|
|
300
401
|
}
|
|
301
402
|
}
|
|
302
403
|
|
|
@@ -304,29 +405,54 @@ export const createRouteTransformExecutor = ({
|
|
|
304
405
|
parallelRouteTransform,
|
|
305
406
|
routeChunkCache,
|
|
306
407
|
splitRouteModules,
|
|
408
|
+
isBuild,
|
|
307
409
|
}: RouteTransformExecutorOptions = {}): RouteTransformExecutor => {
|
|
410
|
+
return createRouteTransformExecutorWithWorkerFactory(
|
|
411
|
+
{
|
|
412
|
+
parallelRouteTransform,
|
|
413
|
+
routeChunkCache,
|
|
414
|
+
splitRouteModules,
|
|
415
|
+
isBuild,
|
|
416
|
+
},
|
|
417
|
+
createDefaultWorker
|
|
418
|
+
);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const createRouteTransformExecutorWithWorkerFactory = (
|
|
422
|
+
{
|
|
423
|
+
parallelRouteTransform,
|
|
424
|
+
routeChunkCache,
|
|
425
|
+
splitRouteModules,
|
|
426
|
+
isBuild,
|
|
427
|
+
}: RouteTransformExecutorOptions = {},
|
|
428
|
+
createWorker: RouteTransformWorkerFactory
|
|
429
|
+
): RouteTransformExecutor => {
|
|
308
430
|
const options = { routeChunkCache };
|
|
309
431
|
if (
|
|
310
432
|
parallelRouteTransform === undefined ||
|
|
311
433
|
parallelRouteTransform === false
|
|
312
434
|
) {
|
|
313
|
-
return
|
|
314
|
-
run: task => executeRouteTransformTask(task, options),
|
|
315
|
-
close: async () => {},
|
|
316
|
-
};
|
|
435
|
+
return createInlineRouteTransformExecutor(options);
|
|
317
436
|
}
|
|
318
437
|
|
|
319
|
-
const workerCount = getConfiguredWorkerCount(
|
|
438
|
+
const workerCount = getConfiguredWorkerCount(
|
|
439
|
+
parallelRouteTransform,
|
|
440
|
+
Boolean(isBuild)
|
|
441
|
+
);
|
|
320
442
|
if (workerCount < 1) {
|
|
321
|
-
return
|
|
322
|
-
run: task => executeRouteTransformTask(task, options),
|
|
323
|
-
close: async () => {},
|
|
324
|
-
};
|
|
443
|
+
return createInlineRouteTransformExecutor(options);
|
|
325
444
|
}
|
|
326
445
|
|
|
327
446
|
return new ParallelRouteTransformExecutor(
|
|
328
447
|
workerCount,
|
|
329
448
|
options,
|
|
330
|
-
Boolean(splitRouteModules)
|
|
449
|
+
Boolean(splitRouteModules),
|
|
450
|
+
createWorker
|
|
331
451
|
);
|
|
332
452
|
};
|
|
453
|
+
|
|
454
|
+
export const createRouteTransformExecutorForTesting = (
|
|
455
|
+
options: RouteTransformExecutorOptions,
|
|
456
|
+
createWorker: RouteTransformWorkerFactory
|
|
457
|
+
): RouteTransformExecutor =>
|
|
458
|
+
createRouteTransformExecutorWithWorkerFactory(options, createWorker);
|
package/src/prerender-build.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import fsExtra from 'fs-extra';
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
5
6
|
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
6
7
|
import {
|
|
7
8
|
createRequestHandler,
|
|
@@ -22,7 +23,6 @@ import {
|
|
|
22
23
|
getPrerenderConcurrency,
|
|
23
24
|
getSsrFalsePrerenderExportErrors,
|
|
24
25
|
normalizePrerenderMatchPath,
|
|
25
|
-
withBuildRequest,
|
|
26
26
|
} from './prerender.js';
|
|
27
27
|
import type {
|
|
28
28
|
Config,
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
} from './react-router-config.js';
|
|
31
31
|
import { resolveServerBuildModule } from './server-utils.js';
|
|
32
32
|
import type { PluginOptions, Route } from './types.js';
|
|
33
|
+
import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
|
|
33
34
|
|
|
34
35
|
type ReactRouterManifest = Awaited<
|
|
35
36
|
ReturnType<typeof getReactRouterManifestForDev>
|
|
@@ -131,6 +132,34 @@ const createDataRequestPath = (
|
|
|
131
132
|
: `${prerenderPath.replace(/\/$/, '')}.data`;
|
|
132
133
|
};
|
|
133
134
|
|
|
135
|
+
export const createBuildRequestEffect = <T>(
|
|
136
|
+
input: string | URL,
|
|
137
|
+
init: RequestInit | undefined,
|
|
138
|
+
handle: (request: Request) => Promise<T>
|
|
139
|
+
): Effect.Effect<T, Error, never> =>
|
|
140
|
+
Effect.acquireUseRelease(
|
|
141
|
+
Effect.sync(() => new AbortController()),
|
|
142
|
+
controller =>
|
|
143
|
+
tryPluginPromise(() =>
|
|
144
|
+
handle(
|
|
145
|
+
new Request(input, {
|
|
146
|
+
...init,
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
),
|
|
151
|
+
controller =>
|
|
152
|
+
Effect.sync(() => {
|
|
153
|
+
controller.abort();
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
export const withBuildRequest = <T>(
|
|
158
|
+
input: string | URL,
|
|
159
|
+
init: RequestInit | undefined,
|
|
160
|
+
handle: (request: Request) => Promise<T>
|
|
161
|
+
): Promise<T> => runPluginEffect(createBuildRequestEffect(input, init, handle));
|
|
162
|
+
|
|
134
163
|
const prerenderData = async ({
|
|
135
164
|
handler,
|
|
136
165
|
prerenderPath,
|
|
@@ -401,23 +430,30 @@ const validatePrerenderPathMatches = (
|
|
|
401
430
|
}
|
|
402
431
|
};
|
|
403
432
|
|
|
404
|
-
const
|
|
433
|
+
export const createBoundedPrerenderTasksEffect = (
|
|
434
|
+
prerenderPaths: string[],
|
|
435
|
+
concurrency: number,
|
|
436
|
+
renderPath: (path: string) => Effect.Effect<void, Error, never>
|
|
437
|
+
): Effect.Effect<void, Error, never> =>
|
|
438
|
+
Effect.forEach(prerenderPaths, renderPath, { concurrency, discard: true });
|
|
439
|
+
|
|
440
|
+
const createPrerenderPathEffect = ({
|
|
441
|
+
path,
|
|
405
442
|
build,
|
|
443
|
+
buildRoutes,
|
|
406
444
|
requestHandler,
|
|
407
445
|
clientBuildDir,
|
|
408
446
|
options,
|
|
409
447
|
}: {
|
|
448
|
+
path: string;
|
|
410
449
|
build: PrerenderServerBuild;
|
|
450
|
+
buildRoutes: ReturnType<typeof createPrerenderRoutes>;
|
|
411
451
|
requestHandler: (request: Request) => Promise<Response>;
|
|
412
452
|
clientBuildDir: string;
|
|
413
453
|
options: RunReactRouterPrerenderBuildOptions;
|
|
414
|
-
}):
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const concurrency = getPrerenderConcurrency(prerenderConfig);
|
|
418
|
-
const pending = new Set<Promise<void>>();
|
|
419
|
-
|
|
420
|
-
const enqueue = async (path: string) => {
|
|
454
|
+
}): Effect.Effect<void, Error, never> =>
|
|
455
|
+
Effect.gen(function* () {
|
|
456
|
+
const { api, basename, future } = options;
|
|
421
457
|
const matches = matchRoutes(buildRoutes, normalizePrerenderMatchPath(path));
|
|
422
458
|
if (!matches) {
|
|
423
459
|
return;
|
|
@@ -431,27 +467,33 @@ const runPrerenderPaths = async ({
|
|
|
431
467
|
|
|
432
468
|
if (isResourceRoute) {
|
|
433
469
|
if (manifestRoute.loader && routeId) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
470
|
+
yield* tryPluginPromise(() =>
|
|
471
|
+
prerenderData({
|
|
472
|
+
handler: requestHandler,
|
|
473
|
+
prerenderPath: path,
|
|
474
|
+
onlyRoutes: [routeId],
|
|
475
|
+
clientBuildDir,
|
|
476
|
+
basename,
|
|
477
|
+
trailingSlashAwareDataRequests:
|
|
478
|
+
future.unstable_trailingSlashAwareDataRequests,
|
|
479
|
+
api,
|
|
480
|
+
})
|
|
481
|
+
);
|
|
482
|
+
yield* tryPluginPromise(() =>
|
|
483
|
+
prerenderResourceRoute({
|
|
484
|
+
handler: requestHandler,
|
|
485
|
+
prerenderPath: path,
|
|
486
|
+
clientBuildDir,
|
|
487
|
+
basename,
|
|
488
|
+
api,
|
|
489
|
+
})
|
|
454
490
|
);
|
|
491
|
+
} else {
|
|
492
|
+
yield* Effect.sync(() => {
|
|
493
|
+
api.logger.warn(
|
|
494
|
+
`⚠️ Skipping prerendering for resource route without a loader: ${routeId}`
|
|
495
|
+
);
|
|
496
|
+
});
|
|
455
497
|
}
|
|
456
498
|
return;
|
|
457
499
|
}
|
|
@@ -463,44 +505,66 @@ const runPrerenderPaths = async ({
|
|
|
463
505
|
}
|
|
464
506
|
return build.assets?.routes?.[matchedRouteId]?.hasLoader;
|
|
465
507
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
508
|
+
const data = hasLoaders
|
|
509
|
+
? yield* tryPluginPromise(() =>
|
|
510
|
+
prerenderData({
|
|
511
|
+
handler: requestHandler,
|
|
512
|
+
prerenderPath: path,
|
|
513
|
+
onlyRoutes: null,
|
|
514
|
+
clientBuildDir,
|
|
515
|
+
basename,
|
|
516
|
+
trailingSlashAwareDataRequests:
|
|
517
|
+
future.unstable_trailingSlashAwareDataRequests,
|
|
518
|
+
api,
|
|
519
|
+
})
|
|
520
|
+
)
|
|
521
|
+
: undefined;
|
|
522
|
+
|
|
523
|
+
yield* tryPluginPromise(() =>
|
|
524
|
+
prerenderRoute({
|
|
469
525
|
handler: requestHandler,
|
|
470
526
|
prerenderPath: path,
|
|
471
|
-
onlyRoutes: null,
|
|
472
527
|
clientBuildDir,
|
|
473
528
|
basename,
|
|
474
|
-
trailingSlashAwareDataRequests:
|
|
475
|
-
future.unstable_trailingSlashAwareDataRequests,
|
|
476
529
|
api,
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
headers: {
|
|
488
|
-
'X-React-Router-Prerender-Data': encodeURI(data),
|
|
489
|
-
},
|
|
490
|
-
}
|
|
491
|
-
: undefined,
|
|
492
|
-
});
|
|
493
|
-
};
|
|
530
|
+
requestInit: data
|
|
531
|
+
? {
|
|
532
|
+
headers: {
|
|
533
|
+
'X-React-Router-Prerender-Data': encodeURI(data),
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
: undefined,
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
});
|
|
494
540
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
541
|
+
const runPrerenderPaths = async ({
|
|
542
|
+
build,
|
|
543
|
+
requestHandler,
|
|
544
|
+
clientBuildDir,
|
|
545
|
+
options,
|
|
546
|
+
}: {
|
|
547
|
+
build: PrerenderServerBuild;
|
|
548
|
+
requestHandler: (request: Request) => Promise<Response>;
|
|
549
|
+
clientBuildDir: string;
|
|
550
|
+
options: RunReactRouterPrerenderBuildOptions;
|
|
551
|
+
}): Promise<void> => {
|
|
552
|
+
const { prerenderConfig, prerenderPaths } = options;
|
|
553
|
+
const buildRoutes = createPrerenderRoutes(build.routes);
|
|
554
|
+
const concurrency = getPrerenderConcurrency(prerenderConfig);
|
|
555
|
+
|
|
556
|
+
await runPluginEffect(
|
|
557
|
+
createBoundedPrerenderTasksEffect(prerenderPaths, concurrency, path =>
|
|
558
|
+
createPrerenderPathEffect({
|
|
559
|
+
path,
|
|
560
|
+
build,
|
|
561
|
+
buildRoutes,
|
|
562
|
+
requestHandler,
|
|
563
|
+
clientBuildDir,
|
|
564
|
+
options,
|
|
565
|
+
})
|
|
566
|
+
)
|
|
567
|
+
);
|
|
504
568
|
};
|
|
505
569
|
|
|
506
570
|
export const runReactRouterPrerenderBuild = async (
|
package/src/prerender.ts
CHANGED
|
@@ -92,24 +92,6 @@ export const createPrerenderRoutes = (
|
|
|
92
92
|
export const normalizePrerenderMatchPath = (path: string): string =>
|
|
93
93
|
`/${path}/`.replace(/^\/\/+/, '/');
|
|
94
94
|
|
|
95
|
-
export const withBuildRequest = async <T>(
|
|
96
|
-
input: string | URL,
|
|
97
|
-
init: RequestInit | undefined,
|
|
98
|
-
handle: (request: Request) => Promise<T>
|
|
99
|
-
): Promise<T> => {
|
|
100
|
-
const controller = new AbortController();
|
|
101
|
-
try {
|
|
102
|
-
return await handle(
|
|
103
|
-
new Request(input, {
|
|
104
|
-
...init,
|
|
105
|
-
signal: controller.signal,
|
|
106
|
-
})
|
|
107
|
-
);
|
|
108
|
-
} finally {
|
|
109
|
-
controller.abort();
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
95
|
export const getSsrFalsePrerenderExportErrors = ({
|
|
114
96
|
routes,
|
|
115
97
|
manifestRoutes,
|