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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
2
|
+
import type { RouteConfigEntry } from '@react-router/dev/routes';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import { relative } from 'pathe';
|
|
5
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
6
|
+
import {
|
|
7
|
+
createDelayedPluginTask,
|
|
8
|
+
DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
9
|
+
normalizeEffectError,
|
|
10
|
+
runPluginEffect,
|
|
11
|
+
tryPluginPromise,
|
|
12
|
+
} from './effect-runtime.js';
|
|
13
|
+
import {
|
|
14
|
+
createLazyCompilationPrewarmController,
|
|
15
|
+
normalizeLazyCompilationPrewarmOptions,
|
|
16
|
+
} from './lazy-compilation-prewarm.js';
|
|
17
|
+
import {
|
|
18
|
+
configRoutesToRouteManifestEntries,
|
|
19
|
+
type ReactRouterManifestForDev,
|
|
20
|
+
} from './manifest.js';
|
|
21
|
+
import type { RouteTransformExecutor } from './parallel-route-transforms.js';
|
|
22
|
+
import {
|
|
23
|
+
createRouteManifestSnapshot,
|
|
24
|
+
createRouteTopologyWatcher,
|
|
25
|
+
ensureDevRestartMarker,
|
|
26
|
+
type WatchFileConfig,
|
|
27
|
+
} from './route-watch.js';
|
|
28
|
+
import type { PluginOptions } from './types.js';
|
|
29
|
+
|
|
30
|
+
type RegisterReactRouterDevBackgroundResourcesOptions = {
|
|
31
|
+
api: RsbuildPluginAPI;
|
|
32
|
+
isBuild: boolean;
|
|
33
|
+
lazyCompilationPrewarm: PluginOptions['unstableLazyCompilationPrewarm'];
|
|
34
|
+
routeTransformExecutor: RouteTransformExecutor;
|
|
35
|
+
routeRestartMarkerPath: string;
|
|
36
|
+
watchDirectory: string;
|
|
37
|
+
getRouteTopology: () => Promise<Set<string>>;
|
|
38
|
+
initialRouteTopology: Set<string>;
|
|
39
|
+
onRouteTopologyChange: PluginOptions['onRouteTopologyChange'];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ReactRouterDevBackgroundResources = {
|
|
43
|
+
setManifest(manifest: ReactRouterManifestForDev): void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const createReactRouterRouteTopology = ({
|
|
47
|
+
appDirectory,
|
|
48
|
+
rootRouteFile,
|
|
49
|
+
routeConfig,
|
|
50
|
+
loadRouteConfig,
|
|
51
|
+
getRootRoutePath,
|
|
52
|
+
}: {
|
|
53
|
+
appDirectory: string;
|
|
54
|
+
rootRouteFile: string;
|
|
55
|
+
routeConfig: RouteConfigEntry[];
|
|
56
|
+
loadRouteConfig: () => Promise<RouteConfigEntry[]>;
|
|
57
|
+
getRootRoutePath: () => string;
|
|
58
|
+
}): {
|
|
59
|
+
initialRouteTopology: Set<string>;
|
|
60
|
+
getRouteTopology: () => Promise<Set<string>>;
|
|
61
|
+
} => {
|
|
62
|
+
const createSnapshot = (
|
|
63
|
+
routeFile: string,
|
|
64
|
+
routeConfigEntries: RouteConfigEntry[]
|
|
65
|
+
): Set<string> =>
|
|
66
|
+
createRouteManifestSnapshot([
|
|
67
|
+
['root', { path: '', id: 'root', file: routeFile }],
|
|
68
|
+
...configRoutesToRouteManifestEntries(appDirectory, routeConfigEntries),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
initialRouteTopology: createSnapshot(rootRouteFile, routeConfig),
|
|
73
|
+
async getRouteTopology() {
|
|
74
|
+
const latestRouteConfig = await loadRouteConfig();
|
|
75
|
+
const latestRootRouteFile = relative(appDirectory, getRootRoutePath());
|
|
76
|
+
return createSnapshot(latestRootRouteFile, latestRouteConfig);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const createReactRouterRouteWatchFiles = ({
|
|
82
|
+
configWatchPaths,
|
|
83
|
+
routeConfigWatchPaths,
|
|
84
|
+
routeRestartMarkerPath,
|
|
85
|
+
onRouteTopologyChange,
|
|
86
|
+
}: {
|
|
87
|
+
configWatchPaths: string | string[];
|
|
88
|
+
routeConfigWatchPaths: string | string[];
|
|
89
|
+
routeRestartMarkerPath: string;
|
|
90
|
+
onRouteTopologyChange: PluginOptions['onRouteTopologyChange'];
|
|
91
|
+
}): WatchFileConfig[] => {
|
|
92
|
+
const watchFiles: WatchFileConfig[] = [
|
|
93
|
+
{
|
|
94
|
+
paths: configWatchPaths,
|
|
95
|
+
type: 'reload-server',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (!onRouteTopologyChange) {
|
|
100
|
+
watchFiles.push(
|
|
101
|
+
{
|
|
102
|
+
paths: routeConfigWatchPaths,
|
|
103
|
+
type: 'reload-server',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
paths: routeRestartMarkerPath,
|
|
107
|
+
type: 'reload-server',
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return watchFiles;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const closeAll = async (
|
|
116
|
+
message: string,
|
|
117
|
+
closers: Array<() => Promise<void>>
|
|
118
|
+
): Promise<void> => {
|
|
119
|
+
const results = await Promise.allSettled(closers.map(closer => closer()));
|
|
120
|
+
const errors = results
|
|
121
|
+
.filter(
|
|
122
|
+
(result): result is PromiseRejectedResult => result.status === 'rejected'
|
|
123
|
+
)
|
|
124
|
+
.map(result => normalizeEffectError(result.reason));
|
|
125
|
+
|
|
126
|
+
if (errors.length === 1) {
|
|
127
|
+
throw errors[0];
|
|
128
|
+
}
|
|
129
|
+
if (errors.length > 1) {
|
|
130
|
+
throw new AggregateError(errors, message);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const registerReactRouterDevBackgroundResources = ({
|
|
135
|
+
api,
|
|
136
|
+
isBuild,
|
|
137
|
+
lazyCompilationPrewarm,
|
|
138
|
+
routeTransformExecutor,
|
|
139
|
+
routeRestartMarkerPath,
|
|
140
|
+
watchDirectory,
|
|
141
|
+
getRouteTopology,
|
|
142
|
+
initialRouteTopology,
|
|
143
|
+
onRouteTopologyChange,
|
|
144
|
+
}: RegisterReactRouterDevBackgroundResourcesOptions): ReactRouterDevBackgroundResources => {
|
|
145
|
+
let closeActiveRouteTopologyWatcher: (() => Promise<void>) | undefined;
|
|
146
|
+
let routeTopologyWatcherClosed = false;
|
|
147
|
+
|
|
148
|
+
const reportRouteTopologyWatcherError = (error: unknown): void => {
|
|
149
|
+
api.logger.warn(
|
|
150
|
+
`[${PLUGIN_NAME}] Failed to watch route topology changes: ${String(normalizeEffectError(error))}`
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const routeTopologyWatcherTask = createDelayedPluginTask({
|
|
155
|
+
delayMs: DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
156
|
+
run: () =>
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
yield* tryPluginPromise(() =>
|
|
159
|
+
ensureDevRestartMarker(routeRestartMarkerPath)
|
|
160
|
+
);
|
|
161
|
+
const closeWatcher = yield* tryPluginPromise(() =>
|
|
162
|
+
createRouteTopologyWatcher({
|
|
163
|
+
watchDirectory,
|
|
164
|
+
getRouteTopology,
|
|
165
|
+
initialRouteTopology,
|
|
166
|
+
restartMarkerPath: routeRestartMarkerPath,
|
|
167
|
+
onRouteTopologyChange,
|
|
168
|
+
onError: reportRouteTopologyWatcherError,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
if (routeTopologyWatcherClosed) {
|
|
172
|
+
yield* tryPluginPromise(() => closeWatcher());
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
closeActiveRouteTopologyWatcher = closeWatcher;
|
|
176
|
+
}),
|
|
177
|
+
onError: reportRouteTopologyWatcherError,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const scheduleRouteTopologyWatcher = (): void => {
|
|
181
|
+
if (routeTopologyWatcherClosed || closeActiveRouteTopologyWatcher) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
routeTopologyWatcherTask.schedule();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const lazyCompilationPrewarmConfig = normalizeLazyCompilationPrewarmOptions(
|
|
188
|
+
lazyCompilationPrewarm
|
|
189
|
+
);
|
|
190
|
+
const lazyCompilationPrewarmController = lazyCompilationPrewarmConfig
|
|
191
|
+
? createLazyCompilationPrewarmController({
|
|
192
|
+
config: lazyCompilationPrewarmConfig,
|
|
193
|
+
onError: error =>
|
|
194
|
+
api.logger.warn(
|
|
195
|
+
`[${PLUGIN_NAME}] Lazy compilation prewarm skipped: ${error.message}`
|
|
196
|
+
),
|
|
197
|
+
})
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
if (!isBuild) {
|
|
201
|
+
api.onBeforeStartDevServer(() => {
|
|
202
|
+
routeTopologyWatcherClosed = false;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
api.onAfterStartDevServer(({ port }) => {
|
|
206
|
+
lazyCompilationPrewarmController?.setServerOrigin(
|
|
207
|
+
`http://localhost:${port}`
|
|
208
|
+
);
|
|
209
|
+
lazyCompilationPrewarmController?.schedule();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
api.onAfterDevCompile(() => {
|
|
213
|
+
scheduleRouteTopologyWatcher();
|
|
214
|
+
lazyCompilationPrewarmController?.schedule();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Spawn transform workers now so thread startup overlaps Rsbuild's own
|
|
218
|
+
// compiler creation instead of delaying the first route transform.
|
|
219
|
+
routeTransformExecutor.prewarm();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const closeRouteTopologyWatcher = async (): Promise<void> => {
|
|
223
|
+
routeTopologyWatcherClosed = true;
|
|
224
|
+
await runPluginEffect(routeTopologyWatcherTask.cancelEffect());
|
|
225
|
+
await closeActiveRouteTopologyWatcher?.();
|
|
226
|
+
closeActiveRouteTopologyWatcher = undefined;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const closeLazyCompilationPrewarm = async (): Promise<void> => {
|
|
230
|
+
await runPluginEffect(
|
|
231
|
+
lazyCompilationPrewarmController?.cancelEffect() ?? Effect.void
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const closeRouteTransformExecutor = (): Promise<void> =>
|
|
236
|
+
routeTransformExecutor.close();
|
|
237
|
+
|
|
238
|
+
api.onCloseDevServer(() =>
|
|
239
|
+
closeAll(
|
|
240
|
+
'[rsbuild-plugin-react-router] Failed to close dev server resources.',
|
|
241
|
+
[
|
|
242
|
+
closeRouteTopologyWatcher,
|
|
243
|
+
closeLazyCompilationPrewarm,
|
|
244
|
+
closeRouteTransformExecutor,
|
|
245
|
+
]
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
api.onCloseBuild(closeRouteTransformExecutor);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
setManifest(manifest) {
|
|
252
|
+
lazyCompilationPrewarmController?.setManifest(manifest);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
};
|
package/src/dev-generation.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { RsbuildDevServer, Rspack } from '@rsbuild/core';
|
|
2
|
+
import * as EffectDeferred from 'effect/Deferred';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
2
4
|
import type { ServerBuild } from 'react-router';
|
|
3
5
|
import {
|
|
4
6
|
evaluateServerBuilds,
|
|
@@ -10,11 +12,13 @@ import {
|
|
|
10
12
|
type DevCompilationIdentity,
|
|
11
13
|
type DevGraphChanges,
|
|
12
14
|
type DevGraphIdentity,
|
|
15
|
+
type DevRuntimeStats,
|
|
13
16
|
type ReactRouterDevBuildPlan,
|
|
14
17
|
type ReactRouterDevManifestSet,
|
|
15
18
|
type ReactRouterServerBuilds,
|
|
16
19
|
type WebArtifact,
|
|
17
20
|
} from './dev-runtime-artifacts.js';
|
|
21
|
+
import { normalizeEffectError, runPluginEffect } from './effect-runtime.js';
|
|
18
22
|
|
|
19
23
|
export { snapshotDevChangedFiles } from './dev-runtime-artifacts.js';
|
|
20
24
|
export type {
|
|
@@ -26,12 +30,6 @@ export type {
|
|
|
26
30
|
ReactRouterDevManifestSet,
|
|
27
31
|
} from './dev-runtime-artifacts.js';
|
|
28
32
|
|
|
29
|
-
type Deferred<T> = {
|
|
30
|
-
promise: Promise<T>;
|
|
31
|
-
resolve: (value: T) => void;
|
|
32
|
-
reject: (error: Error) => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
33
|
type CommittedGeneration = {
|
|
36
34
|
buildsByEntryName: ReactRouterServerBuilds;
|
|
37
35
|
webIdentity: DevCompilationIdentity;
|
|
@@ -44,7 +42,7 @@ type RuntimeState =
|
|
|
44
42
|
| {
|
|
45
43
|
kind: 'starting';
|
|
46
44
|
attemptId: number;
|
|
47
|
-
readiness: Deferred<CommittedGeneration>;
|
|
45
|
+
readiness: EffectDeferred.Deferred<CommittedGeneration, Error>;
|
|
48
46
|
}
|
|
49
47
|
| { kind: 'failed'; attemptId: number; error: Error }
|
|
50
48
|
| {
|
|
@@ -61,10 +59,10 @@ export type ReactRouterDevRuntime = {
|
|
|
61
59
|
manifestsByEntryName: ReactRouterDevManifestSet
|
|
62
60
|
) => void;
|
|
63
61
|
finishAttempt: (
|
|
64
|
-
stats:
|
|
62
|
+
stats: DevRuntimeStats,
|
|
65
63
|
changes: DevGraphChanges,
|
|
66
64
|
identity: DevGraphIdentity
|
|
67
|
-
) => Promise<
|
|
65
|
+
) => Promise<'committed' | 'ignored' | 'retry-node'>;
|
|
68
66
|
failAttempt: (error: Error) => void;
|
|
69
67
|
load: (entryName?: string) => Promise<ServerBuild>;
|
|
70
68
|
close: (error?: Error) => void;
|
|
@@ -250,18 +248,10 @@ const hasRouteManifestMetadataChanges = (
|
|
|
250
248
|
return false;
|
|
251
249
|
};
|
|
252
250
|
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
resolve = resolvePromise;
|
|
258
|
-
reject = rejectPromise;
|
|
259
|
-
});
|
|
260
|
-
// Compilation can fail before a request asks for the build. Observe the
|
|
261
|
-
// rejection now while returning the same promise to future callers.
|
|
262
|
-
void promise.catch(() => undefined);
|
|
263
|
-
return { promise, resolve, reject };
|
|
264
|
-
};
|
|
251
|
+
const createReadinessDeferred = (): EffectDeferred.Deferred<
|
|
252
|
+
CommittedGeneration,
|
|
253
|
+
Error
|
|
254
|
+
> => Effect.runSync(EffectDeferred.make<CommittedGeneration, Error>());
|
|
265
255
|
|
|
266
256
|
export const createReactRouterDevRuntime = ({
|
|
267
257
|
server,
|
|
@@ -276,7 +266,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
276
266
|
let state: RuntimeState = {
|
|
277
267
|
kind: 'starting',
|
|
278
268
|
attemptId: 0,
|
|
279
|
-
readiness:
|
|
269
|
+
readiness: createReadinessDeferred(),
|
|
280
270
|
};
|
|
281
271
|
const manifestsByCompilation = new WeakMap<
|
|
282
272
|
Rspack.Compilation,
|
|
@@ -289,9 +279,8 @@ export const createReactRouterDevRuntime = ({
|
|
|
289
279
|
try {
|
|
290
280
|
onCssAssetOwnershipChanged(change);
|
|
291
281
|
} catch (cause) {
|
|
292
|
-
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
293
282
|
onWarning(
|
|
294
|
-
`[rsbuild-plugin-react-router] Failed to notify the browser after CSS asset ownership changed: ${
|
|
283
|
+
`[rsbuild-plugin-react-router] Failed to notify the browser after CSS asset ownership changed: ${normalizeEffectError(cause).message}`
|
|
295
284
|
);
|
|
296
285
|
}
|
|
297
286
|
};
|
|
@@ -300,9 +289,8 @@ export const createReactRouterDevRuntime = ({
|
|
|
300
289
|
try {
|
|
301
290
|
onRouteManifestChanged();
|
|
302
291
|
} catch (cause) {
|
|
303
|
-
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
304
292
|
onWarning(
|
|
305
|
-
`[rsbuild-plugin-react-router] Failed to notify the browser after route manifest metadata changed: ${
|
|
293
|
+
`[rsbuild-plugin-react-router] Failed to notify the browser after route manifest metadata changed: ${normalizeEffectError(cause).message}`
|
|
306
294
|
);
|
|
307
295
|
}
|
|
308
296
|
};
|
|
@@ -352,7 +340,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
352
340
|
if (state.kind === 'starting') {
|
|
353
341
|
const { readiness } = state;
|
|
354
342
|
state = { kind: 'failed', attemptId, error };
|
|
355
|
-
|
|
343
|
+
Effect.runSync(EffectDeferred.fail(readiness, error));
|
|
356
344
|
} else if (state.kind === 'ready') {
|
|
357
345
|
state = { ...state, pendingAttemptId: null };
|
|
358
346
|
}
|
|
@@ -371,7 +359,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
371
359
|
if (state.kind === 'starting') {
|
|
372
360
|
const { readiness } = state;
|
|
373
361
|
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
374
|
-
|
|
362
|
+
Effect.runSync(EffectDeferred.succeed(readiness, committed));
|
|
375
363
|
} else if (state.kind === 'ready') {
|
|
376
364
|
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
377
365
|
}
|
|
@@ -413,7 +401,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
413
401
|
state = {
|
|
414
402
|
kind: 'starting',
|
|
415
403
|
attemptId,
|
|
416
|
-
readiness:
|
|
404
|
+
readiness: createReadinessDeferred(),
|
|
417
405
|
};
|
|
418
406
|
} else if (state.kind === 'starting') {
|
|
419
407
|
state = { ...state, attemptId };
|
|
@@ -431,10 +419,10 @@ export const createReactRouterDevRuntime = ({
|
|
|
431
419
|
}
|
|
432
420
|
},
|
|
433
421
|
|
|
434
|
-
async finishAttempt(stats, changes, identity)
|
|
422
|
+
async finishAttempt(stats, changes, identity) {
|
|
435
423
|
const attemptId = getCurrentAttemptId();
|
|
436
424
|
if (attemptId === null) {
|
|
437
|
-
return;
|
|
425
|
+
return 'ignored';
|
|
438
426
|
}
|
|
439
427
|
const webStats = getEnvironmentStats(stats, 'web');
|
|
440
428
|
const nodeStats = getEnvironmentStats(stats, 'node');
|
|
@@ -446,13 +434,13 @@ export const createReactRouterDevRuntime = ({
|
|
|
446
434
|
),
|
|
447
435
|
true
|
|
448
436
|
);
|
|
449
|
-
return;
|
|
437
|
+
return 'ignored';
|
|
450
438
|
}
|
|
451
439
|
if (
|
|
452
440
|
webStats.compilation.needAdditionalPass ||
|
|
453
441
|
nodeStats.compilation.needAdditionalPass
|
|
454
442
|
) {
|
|
455
|
-
return;
|
|
443
|
+
return 'ignored';
|
|
456
444
|
}
|
|
457
445
|
if (webStats.hasErrors() || nodeStats.hasErrors()) {
|
|
458
446
|
rejectAttempt(
|
|
@@ -462,7 +450,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
462
450
|
),
|
|
463
451
|
false
|
|
464
452
|
);
|
|
465
|
-
return;
|
|
453
|
+
return 'ignored';
|
|
466
454
|
}
|
|
467
455
|
|
|
468
456
|
const webCompilation = webStats.compilation;
|
|
@@ -477,14 +465,14 @@ export const createReactRouterDevRuntime = ({
|
|
|
477
465
|
),
|
|
478
466
|
true
|
|
479
467
|
);
|
|
480
|
-
return;
|
|
468
|
+
return 'ignored';
|
|
481
469
|
}
|
|
482
470
|
const previous = state.kind === 'ready' ? state.committed : undefined;
|
|
483
471
|
const webChanged = !previous || previous.webIdentity !== webIdentity;
|
|
484
472
|
const nodeChanged = !previous || previous.nodeIdentity !== nodeIdentity;
|
|
485
473
|
|
|
486
474
|
if (!webChanged && !nodeChanged) {
|
|
487
|
-
return;
|
|
475
|
+
return 'ignored';
|
|
488
476
|
}
|
|
489
477
|
|
|
490
478
|
const manifestsByEntryName = webChanged
|
|
@@ -498,7 +486,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
498
486
|
),
|
|
499
487
|
true
|
|
500
488
|
);
|
|
501
|
-
return;
|
|
489
|
+
return 'ignored';
|
|
502
490
|
}
|
|
503
491
|
const cssAssetsRemoved =
|
|
504
492
|
!!previous &&
|
|
@@ -527,21 +515,25 @@ export const createReactRouterDevRuntime = ({
|
|
|
527
515
|
previous.web.manifestsByEntryName,
|
|
528
516
|
manifestsByEntryName
|
|
529
517
|
);
|
|
530
|
-
const reusePreviousNodeBuild =
|
|
518
|
+
const reusePreviousNodeBuild =
|
|
519
|
+
!!previous &&
|
|
520
|
+
cssOnlyWebManifestChange &&
|
|
521
|
+
(!nodeChanged || identity.nodeWeb !== webIdentity);
|
|
531
522
|
|
|
532
523
|
if (
|
|
533
524
|
nodeChanged &&
|
|
534
525
|
identity.nodeWeb !== webIdentity &&
|
|
526
|
+
!identity.attempt &&
|
|
535
527
|
!reusePreviousNodeBuild
|
|
536
528
|
) {
|
|
537
529
|
const message =
|
|
538
530
|
'[rsbuild-plugin-react-router] Discarded web and node results from different compiler cycles and kept the last-good build.';
|
|
539
531
|
if (!previous) {
|
|
540
|
-
return;
|
|
532
|
+
return 'retry-node';
|
|
541
533
|
}
|
|
542
534
|
onWarning(message);
|
|
543
535
|
rejectAttempt(attemptId, new Error(message), false);
|
|
544
|
-
return;
|
|
536
|
+
return 'retry-node';
|
|
545
537
|
}
|
|
546
538
|
|
|
547
539
|
const shouldEvaluateNode = nodeChanged && !reusePreviousNodeBuild;
|
|
@@ -551,7 +543,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
551
543
|
!cssOnlyWebManifestChange &&
|
|
552
544
|
discardUnsafeOneSidedResult(attemptId, previous, webChanged, changes)
|
|
553
545
|
) {
|
|
554
|
-
return;
|
|
546
|
+
return 'ignored';
|
|
555
547
|
}
|
|
556
548
|
|
|
557
549
|
try {
|
|
@@ -559,7 +551,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
559
551
|
? await evaluateServerBuilds(server, buildPlan.entryNames)
|
|
560
552
|
: previous!.buildsByEntryName;
|
|
561
553
|
if (!isCurrentAttempt(attemptId)) {
|
|
562
|
-
return;
|
|
554
|
+
return 'ignored';
|
|
563
555
|
}
|
|
564
556
|
const web = webChanged
|
|
565
557
|
? {
|
|
@@ -583,7 +575,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
583
575
|
: previous!.nodeDependencies,
|
|
584
576
|
});
|
|
585
577
|
if (!committed) {
|
|
586
|
-
return;
|
|
578
|
+
return 'ignored';
|
|
587
579
|
}
|
|
588
580
|
if (cssAssetsRemoved) {
|
|
589
581
|
reloadAfterCssRemoval = !cssAssetsAdded;
|
|
@@ -597,12 +589,10 @@ export const createReactRouterDevRuntime = ({
|
|
|
597
589
|
if (routeManifestMetadataChanged) {
|
|
598
590
|
notifyRouteManifestChanged();
|
|
599
591
|
}
|
|
592
|
+
return 'committed';
|
|
600
593
|
} catch (cause) {
|
|
601
|
-
rejectAttempt(
|
|
602
|
-
|
|
603
|
-
cause instanceof Error ? cause : new Error(String(cause)),
|
|
604
|
-
true
|
|
605
|
-
);
|
|
594
|
+
rejectAttempt(attemptId, normalizeEffectError(cause), true);
|
|
595
|
+
return 'ignored';
|
|
606
596
|
}
|
|
607
597
|
},
|
|
608
598
|
|
|
@@ -625,11 +615,11 @@ export const createReactRouterDevRuntime = ({
|
|
|
625
615
|
return Promise.resolve(selectBuild(state.committed, entryName));
|
|
626
616
|
}
|
|
627
617
|
if (state.kind === 'starting') {
|
|
628
|
-
const selected =
|
|
629
|
-
|
|
618
|
+
const selected = runPluginEffect(
|
|
619
|
+
EffectDeferred.await(state.readiness).pipe(
|
|
620
|
+
Effect.map(generation => selectBuild(generation, entryName))
|
|
621
|
+
)
|
|
630
622
|
);
|
|
631
|
-
// Compilation may fail before the request awaiting this selection has
|
|
632
|
-
// a chance to attach its own rejection handler.
|
|
633
623
|
void selected.catch(() => undefined);
|
|
634
624
|
return selected;
|
|
635
625
|
}
|
|
@@ -646,7 +636,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
646
636
|
'[rsbuild-plugin-react-router] The development server closed before a React Router build was ready.'
|
|
647
637
|
);
|
|
648
638
|
if (state.kind === 'starting') {
|
|
649
|
-
state.readiness
|
|
639
|
+
Effect.runSync(EffectDeferred.fail(state.readiness, closeError));
|
|
650
640
|
}
|
|
651
641
|
state = { kind: 'closed', error: closeError };
|
|
652
642
|
},
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { isAbsolute, relative } from 'node:path';
|
|
2
2
|
import type { RsbuildDevServer, Rspack } from '@rsbuild/core';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
3
4
|
import type { ServerBuild } from 'react-router';
|
|
4
5
|
import type { ReactRouterManifestForDev } from './manifest.js';
|
|
5
|
-
import {
|
|
6
|
+
import { getCappedPluginConcurrency } from './concurrency.js';
|
|
7
|
+
import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
|
|
8
|
+
import { resolveServerBuildModuleEffect } from './server-build-resolution.js';
|
|
6
9
|
|
|
7
10
|
export type ReactRouterDevManifest = ReactRouterManifestForDev;
|
|
8
11
|
|
|
@@ -17,6 +20,13 @@ export type ReactRouterDevManifestSet = Readonly<
|
|
|
17
20
|
|
|
18
21
|
export type ReactRouterServerBuilds = Readonly<Record<string, ServerBuild>>;
|
|
19
22
|
|
|
23
|
+
export type PairedDevStats = {
|
|
24
|
+
web: Rspack.Stats;
|
|
25
|
+
node: Rspack.Stats;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type DevRuntimeStats = Rspack.Stats | Rspack.MultiStats | PairedDevStats;
|
|
29
|
+
|
|
20
30
|
export type DependencySnapshot = {
|
|
21
31
|
files: ReadonlySet<string>;
|
|
22
32
|
contexts: ReadonlySet<string>;
|
|
@@ -34,11 +44,13 @@ export type DevGraphChanges = {
|
|
|
34
44
|
};
|
|
35
45
|
|
|
36
46
|
export type DevCompilationIdentity = symbol;
|
|
47
|
+
export type DevCompileAttemptIdentity = symbol;
|
|
37
48
|
|
|
38
49
|
export type DevGraphIdentity = {
|
|
39
50
|
web: DevCompilationIdentity | undefined;
|
|
40
51
|
node: DevCompilationIdentity | undefined;
|
|
41
52
|
nodeWeb: DevCompilationIdentity | undefined;
|
|
53
|
+
attempt: DevCompileAttemptIdentity | undefined;
|
|
42
54
|
};
|
|
43
55
|
|
|
44
56
|
export type WebArtifact = {
|
|
@@ -102,10 +114,17 @@ export const isSafeOneSidedChange = (
|
|
|
102
114
|
return true;
|
|
103
115
|
};
|
|
104
116
|
|
|
117
|
+
export const isPairedDevStats = (
|
|
118
|
+
stats: DevRuntimeStats
|
|
119
|
+
): stats is PairedDevStats => 'web' in stats && 'node' in stats;
|
|
120
|
+
|
|
105
121
|
export const getEnvironmentStats = (
|
|
106
|
-
stats:
|
|
122
|
+
stats: DevRuntimeStats,
|
|
107
123
|
name: 'web' | 'node'
|
|
108
124
|
): Rspack.Stats | undefined => {
|
|
125
|
+
if (isPairedDevStats(stats)) {
|
|
126
|
+
return stats[name];
|
|
127
|
+
}
|
|
109
128
|
const children = Array.isArray((stats as Rspack.MultiStats).stats)
|
|
110
129
|
? (stats as Rspack.MultiStats).stats
|
|
111
130
|
: [stats as Rspack.Stats];
|
|
@@ -115,29 +134,42 @@ export const getEnvironmentStats = (
|
|
|
115
134
|
});
|
|
116
135
|
};
|
|
117
136
|
|
|
118
|
-
const
|
|
137
|
+
const startServerBuildEvaluationEffect = (
|
|
119
138
|
server: RsbuildDevServer,
|
|
120
139
|
entryName: string
|
|
121
|
-
):
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
): Effect.Effect<ServerBuild, Error, never> =>
|
|
141
|
+
tryPluginPromise(() => server.environments.node.loadBundle(entryName)).pipe(
|
|
142
|
+
Effect.flatMap(buildModule =>
|
|
143
|
+
resolveServerBuildModuleEffect(
|
|
144
|
+
buildModule,
|
|
145
|
+
`Server entry ${JSON.stringify(entryName)}`
|
|
146
|
+
)
|
|
147
|
+
)
|
|
126
148
|
);
|
|
127
|
-
};
|
|
128
149
|
|
|
129
|
-
|
|
150
|
+
const evaluateServerBuildsEffect = (
|
|
130
151
|
server: RsbuildDevServer,
|
|
131
152
|
entryNames: readonly string[]
|
|
132
|
-
):
|
|
133
|
-
|
|
134
|
-
entryNames.map(
|
|
135
|
-
entryName
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
): Effect.Effect<ReactRouterServerBuilds, Error, never> =>
|
|
154
|
+
Effect.forEach(
|
|
155
|
+
entryNames.map(entryName =>
|
|
156
|
+
startServerBuildEvaluationEffect(server, entryName).pipe(
|
|
157
|
+
Effect.map(build => [entryName, build] as const)
|
|
158
|
+
)
|
|
159
|
+
),
|
|
160
|
+
evaluation => evaluation,
|
|
161
|
+
{ concurrency: getCappedPluginConcurrency() }
|
|
162
|
+
).pipe(
|
|
163
|
+
Effect.map(
|
|
164
|
+
evaluated => Object.fromEntries(evaluated) as Record<string, ServerBuild>
|
|
165
|
+
)
|
|
138
166
|
);
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
|
|
168
|
+
export const evaluateServerBuilds = (
|
|
169
|
+
server: RsbuildDevServer,
|
|
170
|
+
entryNames: readonly string[]
|
|
171
|
+
): Promise<ReactRouterServerBuilds> =>
|
|
172
|
+
runPluginEffect(evaluateServerBuildsEffect(server, entryNames));
|
|
141
173
|
|
|
142
174
|
const assertBuildMatchesManifest = (
|
|
143
175
|
entryName: string,
|