rsbuild-plugin-react-router 0.1.1 → 0.3.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 +185 -204
- package/dist/451.js +1087 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +1 -1
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/config-imports.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +39 -0
- package/dist/dev-runtime-compilation.d.ts +32 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +11 -2
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +3085 -1272
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2039 -1299
- package/dist/lazy-compilation.d.ts +5 -0
- package/dist/manifest.d.ts +32 -9
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +14 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +32 -0
- package/dist/prerender.d.ts +28 -2
- package/dist/react-router-config.d.ts +8 -1
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/templates/entry.client.js +3 -3
- package/dist/templates/entry.server.cjs +11 -8
- package/dist/templates/entry.server.js +5 -5
- package/dist/typegen.d.ts +2 -0
- package/dist/types.d.ts +30 -12
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +26 -22
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +168 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +34 -0
- package/src/config-imports.ts +45 -0
- package/src/constants.ts +91 -0
- package/src/dev-generation.ts +700 -0
- package/src/dev-runtime-artifacts.ts +207 -0
- package/src/dev-runtime-compilation.ts +92 -0
- package/src/dev-runtime-controller.ts +462 -0
- package/src/dev-runtime-session.ts +184 -0
- package/src/dev-server.ts +58 -0
- package/src/export-utils.ts +219 -0
- package/src/index.ts +1025 -0
- package/src/lazy-compilation.ts +94 -0
- package/src/manifest.ts +480 -0
- package/src/modify-browser-manifest.ts +245 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +332 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +623 -0
- package/src/prerender.ts +367 -0
- package/src/react-router-config.ts +220 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +322 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-utils.ts +210 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +49 -0
- package/src/types.ts +82 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/906.js +0 -1
- package/dist/946.js +0 -1
- package/dist/babel.d.ts +0 -8
- package/dist/rslib-runtime.js +0 -23
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import type { RsbuildDevServer, Rspack } from '@rsbuild/core';
|
|
2
|
+
import type { ServerBuild } from 'react-router';
|
|
3
|
+
import {
|
|
4
|
+
evaluateServerBuilds,
|
|
5
|
+
getEnvironmentStats,
|
|
6
|
+
isSafeOneSidedChange,
|
|
7
|
+
pinServerBuildsToManifests,
|
|
8
|
+
snapshotDependencies,
|
|
9
|
+
type DependencySnapshot,
|
|
10
|
+
type DevCompilationIdentity,
|
|
11
|
+
type DevGraphChanges,
|
|
12
|
+
type DevGraphIdentity,
|
|
13
|
+
type ReactRouterDevBuildPlan,
|
|
14
|
+
type ReactRouterDevManifestSet,
|
|
15
|
+
type ReactRouterServerBuilds,
|
|
16
|
+
type WebArtifact,
|
|
17
|
+
} from './dev-runtime-artifacts.js';
|
|
18
|
+
|
|
19
|
+
export { snapshotDevChangedFiles } from './dev-runtime-artifacts.js';
|
|
20
|
+
export type {
|
|
21
|
+
DevChangedFiles,
|
|
22
|
+
DevGraphChanges,
|
|
23
|
+
DevGraphIdentity,
|
|
24
|
+
ReactRouterDevBuildPlan,
|
|
25
|
+
ReactRouterDevManifest,
|
|
26
|
+
ReactRouterDevManifestSet,
|
|
27
|
+
} from './dev-runtime-artifacts.js';
|
|
28
|
+
|
|
29
|
+
type Deferred<T> = {
|
|
30
|
+
promise: Promise<T>;
|
|
31
|
+
resolve: (value: T) => void;
|
|
32
|
+
reject: (error: Error) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type CommittedGeneration = {
|
|
36
|
+
buildsByEntryName: ReactRouterServerBuilds;
|
|
37
|
+
webIdentity: DevCompilationIdentity;
|
|
38
|
+
nodeIdentity: DevCompilationIdentity;
|
|
39
|
+
web: WebArtifact;
|
|
40
|
+
nodeDependencies: DependencySnapshot;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type RuntimeState =
|
|
44
|
+
| {
|
|
45
|
+
kind: 'starting';
|
|
46
|
+
attemptId: number;
|
|
47
|
+
readiness: Deferred<CommittedGeneration>;
|
|
48
|
+
}
|
|
49
|
+
| { kind: 'failed'; attemptId: number; error: Error }
|
|
50
|
+
| {
|
|
51
|
+
kind: 'ready';
|
|
52
|
+
committed: CommittedGeneration;
|
|
53
|
+
pendingAttemptId: number | null;
|
|
54
|
+
}
|
|
55
|
+
| { kind: 'closed'; error: Error };
|
|
56
|
+
|
|
57
|
+
export type ReactRouterDevRuntime = {
|
|
58
|
+
beginAttempt: () => void;
|
|
59
|
+
captureWeb: (
|
|
60
|
+
compilation: Rspack.Compilation,
|
|
61
|
+
manifestsByEntryName: ReactRouterDevManifestSet
|
|
62
|
+
) => void;
|
|
63
|
+
finishAttempt: (
|
|
64
|
+
stats: Rspack.Stats | Rspack.MultiStats,
|
|
65
|
+
changes: DevGraphChanges,
|
|
66
|
+
identity: DevGraphIdentity
|
|
67
|
+
) => Promise<void>;
|
|
68
|
+
failAttempt: (error: Error) => void;
|
|
69
|
+
load: (entryName?: string) => Promise<ServerBuild>;
|
|
70
|
+
close: (error?: Error) => void;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type CreateReactRouterDevRuntimeOptions = {
|
|
74
|
+
server: RsbuildDevServer;
|
|
75
|
+
buildPlan: ReactRouterDevBuildPlan;
|
|
76
|
+
onEvaluationError: (error: Error) => void;
|
|
77
|
+
onCssAssetOwnershipChanged?: (change: 'removed' | 'restored') => void;
|
|
78
|
+
onRouteManifestChanged?: () => void;
|
|
79
|
+
onWarning?: (message: string) => void;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const collectManifestCssAssetOwnership = (
|
|
83
|
+
manifest: ReactRouterDevManifestSet[string]
|
|
84
|
+
): Set<string> => {
|
|
85
|
+
const ownership = new Set<string>();
|
|
86
|
+
for (const asset of manifest.entry?.css ?? []) {
|
|
87
|
+
ownership.add(`entry\0${asset}`);
|
|
88
|
+
}
|
|
89
|
+
for (const [routeId, route] of Object.entries(manifest.routes ?? {})) {
|
|
90
|
+
for (const asset of route.css ?? []) {
|
|
91
|
+
ownership.add(`route\0${routeId}\0${asset}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return ownership;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const hasRemovedCssAssetOwnership = (
|
|
98
|
+
previous: ReactRouterDevManifestSet,
|
|
99
|
+
next: ReactRouterDevManifestSet
|
|
100
|
+
): boolean => {
|
|
101
|
+
for (const [entryName, previousManifest] of Object.entries(previous)) {
|
|
102
|
+
const previousOwnership =
|
|
103
|
+
collectManifestCssAssetOwnership(previousManifest);
|
|
104
|
+
if (previousOwnership.size === 0) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const nextManifest = next[entryName];
|
|
108
|
+
if (!nextManifest) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const nextOwnership = collectManifestCssAssetOwnership(nextManifest);
|
|
112
|
+
for (const owner of previousOwnership) {
|
|
113
|
+
if (!nextOwnership.has(owner)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const hasAddedCssAssetOwnership = (
|
|
122
|
+
previous: ReactRouterDevManifestSet,
|
|
123
|
+
next: ReactRouterDevManifestSet
|
|
124
|
+
): boolean => hasRemovedCssAssetOwnership(next, previous);
|
|
125
|
+
|
|
126
|
+
const collectManifestCssAssets = (
|
|
127
|
+
manifest: ReactRouterDevManifestSet[string]
|
|
128
|
+
): Set<string> => {
|
|
129
|
+
const assets = new Set(manifest.entry?.css ?? []);
|
|
130
|
+
for (const route of Object.values(manifest.routes ?? {})) {
|
|
131
|
+
for (const asset of route.css ?? []) {
|
|
132
|
+
assets.add(asset);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return assets;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const normalizeManifestForCssOwnershipCheck = (
|
|
139
|
+
manifest: ReactRouterDevManifestSet[string]
|
|
140
|
+
) => {
|
|
141
|
+
const cssAssets = collectManifestCssAssets(manifest);
|
|
142
|
+
const nonCssImports = (imports: string[] = []) =>
|
|
143
|
+
imports.filter(importPath => !cssAssets.has(importPath));
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
entry: {
|
|
147
|
+
imports: nonCssImports(manifest.entry?.imports),
|
|
148
|
+
module: manifest.entry?.module,
|
|
149
|
+
},
|
|
150
|
+
routes: Object.fromEntries(
|
|
151
|
+
Object.entries(manifest.routes ?? {})
|
|
152
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
153
|
+
.map(([routeId, route]) => [
|
|
154
|
+
routeId,
|
|
155
|
+
{
|
|
156
|
+
caseSensitive: route.caseSensitive,
|
|
157
|
+
clientActionModule: route.clientActionModule,
|
|
158
|
+
clientLoaderModule: route.clientLoaderModule,
|
|
159
|
+
clientMiddlewareModule: route.clientMiddlewareModule,
|
|
160
|
+
errorBoundary: route.hasErrorBoundary,
|
|
161
|
+
hasAction: route.hasAction,
|
|
162
|
+
hasClientAction: route.hasClientAction,
|
|
163
|
+
hasClientLoader: route.hasClientLoader,
|
|
164
|
+
hasClientMiddleware: route.hasClientMiddleware,
|
|
165
|
+
hasDefaultExport: route.hasDefaultExport,
|
|
166
|
+
hasLoader: route.hasLoader,
|
|
167
|
+
hydrateFallbackModule: route.hydrateFallbackModule,
|
|
168
|
+
id: route.id,
|
|
169
|
+
imports: nonCssImports(route.imports),
|
|
170
|
+
index: route.index,
|
|
171
|
+
module: route.module,
|
|
172
|
+
parentId: route.parentId,
|
|
173
|
+
path: route.path,
|
|
174
|
+
},
|
|
175
|
+
])
|
|
176
|
+
),
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const hasOnlyCssAssetOwnershipChanges = (
|
|
181
|
+
previous: ReactRouterDevManifestSet,
|
|
182
|
+
next: ReactRouterDevManifestSet
|
|
183
|
+
): boolean => {
|
|
184
|
+
const previousEntryNames = Object.keys(previous).sort();
|
|
185
|
+
const nextEntryNames = Object.keys(next).sort();
|
|
186
|
+
if (previousEntryNames.join('\0') !== nextEntryNames.join('\0')) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return previousEntryNames.every(entryName => {
|
|
190
|
+
const previousManifest = normalizeManifestForCssOwnershipCheck(
|
|
191
|
+
previous[entryName]
|
|
192
|
+
);
|
|
193
|
+
const nextManifest = normalizeManifestForCssOwnershipCheck(next[entryName]);
|
|
194
|
+
return JSON.stringify(previousManifest) === JSON.stringify(nextManifest);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
type DevRouteManifestEntry = NonNullable<
|
|
199
|
+
ReactRouterDevManifestSet[string]['routes']
|
|
200
|
+
>[string];
|
|
201
|
+
|
|
202
|
+
const hasSameRouteMetadata = (
|
|
203
|
+
previous: DevRouteManifestEntry,
|
|
204
|
+
next: DevRouteManifestEntry
|
|
205
|
+
): boolean =>
|
|
206
|
+
previous.caseSensitive === next.caseSensitive &&
|
|
207
|
+
previous.clientActionModule === next.clientActionModule &&
|
|
208
|
+
previous.clientLoaderModule === next.clientLoaderModule &&
|
|
209
|
+
previous.clientMiddlewareModule === next.clientMiddlewareModule &&
|
|
210
|
+
previous.hasErrorBoundary === next.hasErrorBoundary &&
|
|
211
|
+
previous.hasAction === next.hasAction &&
|
|
212
|
+
previous.hasClientAction === next.hasClientAction &&
|
|
213
|
+
previous.hasClientLoader === next.hasClientLoader &&
|
|
214
|
+
previous.hasClientMiddleware === next.hasClientMiddleware &&
|
|
215
|
+
previous.hasDefaultExport === next.hasDefaultExport &&
|
|
216
|
+
previous.hasLoader === next.hasLoader &&
|
|
217
|
+
previous.hydrateFallbackModule === next.hydrateFallbackModule &&
|
|
218
|
+
previous.id === next.id &&
|
|
219
|
+
previous.index === next.index &&
|
|
220
|
+
previous.parentId === next.parentId &&
|
|
221
|
+
previous.path === next.path;
|
|
222
|
+
|
|
223
|
+
const hasRouteManifestMetadataChanges = (
|
|
224
|
+
previous: ReactRouterDevManifestSet,
|
|
225
|
+
next: ReactRouterDevManifestSet
|
|
226
|
+
): boolean => {
|
|
227
|
+
const previousEntryNames = Object.keys(previous);
|
|
228
|
+
if (previousEntryNames.length !== Object.keys(next).length) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
for (const entryName of previousEntryNames) {
|
|
232
|
+
const nextManifest = next[entryName];
|
|
233
|
+
if (!nextManifest) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
const previousRoutes = previous[entryName].routes ?? {};
|
|
237
|
+
const nextRoutes = nextManifest.routes ?? {};
|
|
238
|
+
const previousRouteIds = Object.keys(previousRoutes);
|
|
239
|
+
if (previousRouteIds.length !== Object.keys(nextRoutes).length) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
for (const routeId of previousRouteIds) {
|
|
243
|
+
const previousRoute = previousRoutes[routeId];
|
|
244
|
+
const nextRoute = nextRoutes[routeId];
|
|
245
|
+
if (!nextRoute || !hasSameRouteMetadata(previousRoute, nextRoute)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const createDeferred = <T>(): Deferred<T> => {
|
|
254
|
+
let resolve!: (value: T) => void;
|
|
255
|
+
let reject!: (error: Error) => void;
|
|
256
|
+
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
|
|
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
|
+
};
|
|
265
|
+
|
|
266
|
+
export const createReactRouterDevRuntime = ({
|
|
267
|
+
server,
|
|
268
|
+
buildPlan,
|
|
269
|
+
onEvaluationError,
|
|
270
|
+
onCssAssetOwnershipChanged = () => undefined,
|
|
271
|
+
onRouteManifestChanged = () => undefined,
|
|
272
|
+
onWarning = () => undefined,
|
|
273
|
+
}: CreateReactRouterDevRuntimeOptions): ReactRouterDevRuntime => {
|
|
274
|
+
let nextAttemptId = 1;
|
|
275
|
+
let reloadAfterCssRemoval = false;
|
|
276
|
+
let state: RuntimeState = {
|
|
277
|
+
kind: 'starting',
|
|
278
|
+
attemptId: 0,
|
|
279
|
+
readiness: createDeferred(),
|
|
280
|
+
};
|
|
281
|
+
const manifestsByCompilation = new WeakMap<
|
|
282
|
+
Rspack.Compilation,
|
|
283
|
+
ReactRouterDevManifestSet
|
|
284
|
+
>();
|
|
285
|
+
|
|
286
|
+
const notifyCssAssetOwnershipChanged = (
|
|
287
|
+
change: 'removed' | 'restored'
|
|
288
|
+
): void => {
|
|
289
|
+
try {
|
|
290
|
+
onCssAssetOwnershipChanged(change);
|
|
291
|
+
} catch (cause) {
|
|
292
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
293
|
+
onWarning(
|
|
294
|
+
`[rsbuild-plugin-react-router] Failed to notify the browser after CSS asset ownership changed: ${reason}`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const notifyRouteManifestChanged = (): void => {
|
|
300
|
+
try {
|
|
301
|
+
onRouteManifestChanged();
|
|
302
|
+
} catch (cause) {
|
|
303
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
304
|
+
onWarning(
|
|
305
|
+
`[rsbuild-plugin-react-router] Failed to notify the browser after route manifest metadata changed: ${reason}`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const uniqueEntryNames = new Set(buildPlan.entryNames);
|
|
311
|
+
if (
|
|
312
|
+
uniqueEntryNames.size !== buildPlan.entryNames.length ||
|
|
313
|
+
!uniqueEntryNames.has(buildPlan.defaultEntryName)
|
|
314
|
+
) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
'[rsbuild-plugin-react-router] The development server build plan must contain unique entries and include its default entry.'
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const selectBuild = (
|
|
321
|
+
generation: CommittedGeneration,
|
|
322
|
+
requestedEntryName?: string
|
|
323
|
+
): ServerBuild => {
|
|
324
|
+
const entryName = requestedEntryName ?? buildPlan.defaultEntryName;
|
|
325
|
+
const build = generation.buildsByEntryName[entryName];
|
|
326
|
+
if (!build) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`[rsbuild-plugin-react-router] Committed React Router server build ${JSON.stringify(entryName)} was not found.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return build;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const getCurrentAttemptId = (): number | null => {
|
|
335
|
+
if (state.kind === 'starting') {
|
|
336
|
+
return state.attemptId;
|
|
337
|
+
}
|
|
338
|
+
return state.kind === 'ready' ? state.pendingAttemptId : null;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const isCurrentAttempt = (attemptId: number): boolean =>
|
|
342
|
+
getCurrentAttemptId() === attemptId;
|
|
343
|
+
|
|
344
|
+
const rejectAttempt = (
|
|
345
|
+
attemptId: number,
|
|
346
|
+
error: Error,
|
|
347
|
+
report: boolean
|
|
348
|
+
): void => {
|
|
349
|
+
if (!isCurrentAttempt(attemptId)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (state.kind === 'starting') {
|
|
353
|
+
const { readiness } = state;
|
|
354
|
+
state = { kind: 'failed', attemptId, error };
|
|
355
|
+
readiness.reject(error);
|
|
356
|
+
} else if (state.kind === 'ready') {
|
|
357
|
+
state = { ...state, pendingAttemptId: null };
|
|
358
|
+
}
|
|
359
|
+
if (report) {
|
|
360
|
+
onEvaluationError(error);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const commit = (
|
|
365
|
+
attemptId: number,
|
|
366
|
+
committed: CommittedGeneration
|
|
367
|
+
): boolean => {
|
|
368
|
+
if (!isCurrentAttempt(attemptId)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
if (state.kind === 'starting') {
|
|
372
|
+
const { readiness } = state;
|
|
373
|
+
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
374
|
+
readiness.resolve(committed);
|
|
375
|
+
} else if (state.kind === 'ready') {
|
|
376
|
+
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const discardUnsafeOneSidedResult = (
|
|
382
|
+
attemptId: number,
|
|
383
|
+
previous: CommittedGeneration,
|
|
384
|
+
webChanged: boolean,
|
|
385
|
+
changes: DevGraphChanges
|
|
386
|
+
): boolean => {
|
|
387
|
+
const side = webChanged ? 'web-only' : 'node-only';
|
|
388
|
+
const changedFiles = webChanged ? changes.web : changes.node;
|
|
389
|
+
const unchangedDependencies = webChanged
|
|
390
|
+
? previous.nodeDependencies
|
|
391
|
+
: previous.web.dependencies;
|
|
392
|
+
if (isSafeOneSidedChange(changedFiles, unchangedDependencies)) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
onWarning(
|
|
396
|
+
`[rsbuild-plugin-react-router] Discarded an incomplete ${side} development result and kept the last-good build.`
|
|
397
|
+
);
|
|
398
|
+
rejectAttempt(
|
|
399
|
+
attemptId,
|
|
400
|
+
new Error(`Incomplete ${side} development result.`),
|
|
401
|
+
false
|
|
402
|
+
);
|
|
403
|
+
return true;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
beginAttempt(): void {
|
|
408
|
+
if (state.kind === 'closed') {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const attemptId = nextAttemptId++;
|
|
412
|
+
if (state.kind === 'failed') {
|
|
413
|
+
state = {
|
|
414
|
+
kind: 'starting',
|
|
415
|
+
attemptId,
|
|
416
|
+
readiness: createDeferred(),
|
|
417
|
+
};
|
|
418
|
+
} else if (state.kind === 'starting') {
|
|
419
|
+
state = { ...state, attemptId };
|
|
420
|
+
} else {
|
|
421
|
+
state = { ...state, pendingAttemptId: attemptId };
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
captureWeb(compilation, manifestsByEntryName): void {
|
|
426
|
+
if (state.kind !== 'closed') {
|
|
427
|
+
manifestsByCompilation.set(
|
|
428
|
+
compilation,
|
|
429
|
+
structuredClone(manifestsByEntryName)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
async finishAttempt(stats, changes, identity): Promise<void> {
|
|
435
|
+
const attemptId = getCurrentAttemptId();
|
|
436
|
+
if (attemptId === null) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const webStats = getEnvironmentStats(stats, 'web');
|
|
440
|
+
const nodeStats = getEnvironmentStats(stats, 'node');
|
|
441
|
+
if (!webStats || !nodeStats) {
|
|
442
|
+
rejectAttempt(
|
|
443
|
+
attemptId,
|
|
444
|
+
new Error(
|
|
445
|
+
'[rsbuild-plugin-react-router] Development compilation did not provide both web and node results.'
|
|
446
|
+
),
|
|
447
|
+
true
|
|
448
|
+
);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (
|
|
452
|
+
webStats.compilation.needAdditionalPass ||
|
|
453
|
+
nodeStats.compilation.needAdditionalPass
|
|
454
|
+
) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (webStats.hasErrors() || nodeStats.hasErrors()) {
|
|
458
|
+
rejectAttempt(
|
|
459
|
+
attemptId,
|
|
460
|
+
new Error(
|
|
461
|
+
'[rsbuild-plugin-react-router] The React Router development compilation failed.'
|
|
462
|
+
),
|
|
463
|
+
false
|
|
464
|
+
);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const webCompilation = webStats.compilation;
|
|
469
|
+
const nodeCompilation = nodeStats.compilation;
|
|
470
|
+
const webIdentity = identity.web;
|
|
471
|
+
const nodeIdentity = identity.node;
|
|
472
|
+
if (!webIdentity || !nodeIdentity) {
|
|
473
|
+
rejectAttempt(
|
|
474
|
+
attemptId,
|
|
475
|
+
new Error(
|
|
476
|
+
'[rsbuild-plugin-react-router] Development compilation identity was unavailable.'
|
|
477
|
+
),
|
|
478
|
+
true
|
|
479
|
+
);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const previous = state.kind === 'ready' ? state.committed : undefined;
|
|
483
|
+
const webChanged = !previous || previous.webIdentity !== webIdentity;
|
|
484
|
+
const nodeChanged = !previous || previous.nodeIdentity !== nodeIdentity;
|
|
485
|
+
|
|
486
|
+
if (!webChanged && !nodeChanged) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const manifestsByEntryName = webChanged
|
|
491
|
+
? manifestsByCompilation.get(webCompilation)
|
|
492
|
+
: previous?.web.manifestsByEntryName;
|
|
493
|
+
if (!manifestsByEntryName) {
|
|
494
|
+
rejectAttempt(
|
|
495
|
+
attemptId,
|
|
496
|
+
new Error(
|
|
497
|
+
'[rsbuild-plugin-react-router] The web compilation completed without a matching React Router manifest. Keeping the last-good development build.'
|
|
498
|
+
),
|
|
499
|
+
true
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const cssAssetsRemoved =
|
|
504
|
+
!!previous &&
|
|
505
|
+
webChanged &&
|
|
506
|
+
hasRemovedCssAssetOwnership(
|
|
507
|
+
previous.web.manifestsByEntryName,
|
|
508
|
+
manifestsByEntryName
|
|
509
|
+
);
|
|
510
|
+
const cssAssetsAdded =
|
|
511
|
+
!!previous &&
|
|
512
|
+
webChanged &&
|
|
513
|
+
hasAddedCssAssetOwnership(
|
|
514
|
+
previous.web.manifestsByEntryName,
|
|
515
|
+
manifestsByEntryName
|
|
516
|
+
);
|
|
517
|
+
const cssOnlyWebManifestChange =
|
|
518
|
+
(cssAssetsRemoved || cssAssetsAdded) &&
|
|
519
|
+
hasOnlyCssAssetOwnershipChanges(
|
|
520
|
+
previous.web.manifestsByEntryName,
|
|
521
|
+
manifestsByEntryName
|
|
522
|
+
);
|
|
523
|
+
const routeManifestMetadataChanged =
|
|
524
|
+
!!previous &&
|
|
525
|
+
webChanged &&
|
|
526
|
+
hasRouteManifestMetadataChanges(
|
|
527
|
+
previous.web.manifestsByEntryName,
|
|
528
|
+
manifestsByEntryName
|
|
529
|
+
);
|
|
530
|
+
const reusePreviousNodeBuild = !!previous && cssOnlyWebManifestChange;
|
|
531
|
+
|
|
532
|
+
if (
|
|
533
|
+
nodeChanged &&
|
|
534
|
+
identity.nodeWeb !== webIdentity &&
|
|
535
|
+
!reusePreviousNodeBuild
|
|
536
|
+
) {
|
|
537
|
+
const message =
|
|
538
|
+
'[rsbuild-plugin-react-router] Discarded web and node results from different compiler cycles and kept the last-good build.';
|
|
539
|
+
if (!previous) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
onWarning(message);
|
|
543
|
+
rejectAttempt(attemptId, new Error(message), false);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const shouldEvaluateNode = nodeChanged && !reusePreviousNodeBuild;
|
|
548
|
+
if (
|
|
549
|
+
previous &&
|
|
550
|
+
webChanged !== shouldEvaluateNode &&
|
|
551
|
+
!cssOnlyWebManifestChange &&
|
|
552
|
+
discardUnsafeOneSidedResult(attemptId, previous, webChanged, changes)
|
|
553
|
+
) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
const buildsByEntryName = shouldEvaluateNode
|
|
559
|
+
? await evaluateServerBuilds(server, buildPlan.entryNames)
|
|
560
|
+
: previous!.buildsByEntryName;
|
|
561
|
+
if (!isCurrentAttempt(attemptId)) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const web = webChanged
|
|
565
|
+
? {
|
|
566
|
+
manifestsByEntryName,
|
|
567
|
+
dependencies: snapshotDependencies(webCompilation),
|
|
568
|
+
}
|
|
569
|
+
: previous!.web;
|
|
570
|
+
const committed = commit(attemptId, {
|
|
571
|
+
buildsByEntryName: pinServerBuildsToManifests(
|
|
572
|
+
buildsByEntryName,
|
|
573
|
+
buildPlan.entryNames,
|
|
574
|
+
web.manifestsByEntryName
|
|
575
|
+
),
|
|
576
|
+
webIdentity,
|
|
577
|
+
nodeIdentity: shouldEvaluateNode
|
|
578
|
+
? nodeIdentity
|
|
579
|
+
: previous!.nodeIdentity,
|
|
580
|
+
web,
|
|
581
|
+
nodeDependencies: shouldEvaluateNode
|
|
582
|
+
? snapshotDependencies(nodeCompilation)
|
|
583
|
+
: previous!.nodeDependencies,
|
|
584
|
+
});
|
|
585
|
+
if (!committed) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (cssAssetsRemoved) {
|
|
589
|
+
reloadAfterCssRemoval = !cssAssetsAdded;
|
|
590
|
+
notifyCssAssetOwnershipChanged('removed');
|
|
591
|
+
} else if (cssAssetsAdded) {
|
|
592
|
+
if (reloadAfterCssRemoval) {
|
|
593
|
+
notifyCssAssetOwnershipChanged('restored');
|
|
594
|
+
}
|
|
595
|
+
reloadAfterCssRemoval = false;
|
|
596
|
+
}
|
|
597
|
+
if (routeManifestMetadataChanged) {
|
|
598
|
+
notifyRouteManifestChanged();
|
|
599
|
+
}
|
|
600
|
+
} catch (cause) {
|
|
601
|
+
rejectAttempt(
|
|
602
|
+
attemptId,
|
|
603
|
+
cause instanceof Error ? cause : new Error(String(cause)),
|
|
604
|
+
true
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
failAttempt(error): void {
|
|
610
|
+
const attemptId = getCurrentAttemptId();
|
|
611
|
+
if (attemptId !== null) {
|
|
612
|
+
rejectAttempt(attemptId, error, false);
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
load(entryName?: string): Promise<ServerBuild> {
|
|
617
|
+
if (entryName && !uniqueEntryNames.has(entryName)) {
|
|
618
|
+
return Promise.reject(
|
|
619
|
+
new Error(
|
|
620
|
+
`[rsbuild-plugin-react-router] React Router server build ${JSON.stringify(entryName)} is not part of this development server build plan.`
|
|
621
|
+
)
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (state.kind === 'ready') {
|
|
625
|
+
return Promise.resolve(selectBuild(state.committed, entryName));
|
|
626
|
+
}
|
|
627
|
+
if (state.kind === 'starting') {
|
|
628
|
+
const selected = state.readiness.promise.then(generation =>
|
|
629
|
+
selectBuild(generation, entryName)
|
|
630
|
+
);
|
|
631
|
+
// Compilation may fail before the request awaiting this selection has
|
|
632
|
+
// a chance to attach its own rejection handler.
|
|
633
|
+
void selected.catch(() => undefined);
|
|
634
|
+
return selected;
|
|
635
|
+
}
|
|
636
|
+
return Promise.reject(state.error);
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
close(error?: Error): void {
|
|
640
|
+
if (state.kind === 'closed') {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const closeError =
|
|
644
|
+
error ??
|
|
645
|
+
new Error(
|
|
646
|
+
'[rsbuild-plugin-react-router] The development server closed before a React Router build was ready.'
|
|
647
|
+
);
|
|
648
|
+
if (state.kind === 'starting') {
|
|
649
|
+
state.readiness.reject(closeError);
|
|
650
|
+
}
|
|
651
|
+
state = { kind: 'closed', error: closeError };
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const DEV_RUNTIME_KEY = Symbol.for(
|
|
657
|
+
'rsbuild-plugin-react-router.dev-runtime.v1'
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
const getRegisteredRuntime = (
|
|
661
|
+
server: RsbuildDevServer
|
|
662
|
+
): ReactRouterDevRuntime | undefined =>
|
|
663
|
+
Reflect.get(server, DEV_RUNTIME_KEY) as ReactRouterDevRuntime | undefined;
|
|
664
|
+
|
|
665
|
+
export const registerReactRouterDevRuntime = (
|
|
666
|
+
server: RsbuildDevServer,
|
|
667
|
+
runtime: ReactRouterDevRuntime
|
|
668
|
+
): void => {
|
|
669
|
+
// Symbol.for keeps registration shared when the plugin and public helper are
|
|
670
|
+
// loaded through different ESM and CommonJS package entrypoints.
|
|
671
|
+
Object.defineProperty(server, DEV_RUNTIME_KEY, {
|
|
672
|
+
configurable: true,
|
|
673
|
+
enumerable: false,
|
|
674
|
+
value: runtime,
|
|
675
|
+
});
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
export const unregisterReactRouterDevRuntime = (
|
|
679
|
+
server: RsbuildDevServer,
|
|
680
|
+
runtime: ReactRouterDevRuntime
|
|
681
|
+
): void => {
|
|
682
|
+
if (getRegisteredRuntime(server) === runtime) {
|
|
683
|
+
Reflect.deleteProperty(server, DEV_RUNTIME_KEY);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
export const loadReactRouterServerBuild = (
|
|
688
|
+
server: RsbuildDevServer,
|
|
689
|
+
entryName?: string
|
|
690
|
+
): Promise<ServerBuild> => {
|
|
691
|
+
const runtime = getRegisteredRuntime(server);
|
|
692
|
+
if (!runtime) {
|
|
693
|
+
return Promise.reject(
|
|
694
|
+
new Error(
|
|
695
|
+
'[rsbuild-plugin-react-router] This Rsbuild development server is not registered with the React Router plugin. Add pluginReactRouter() before calling loadReactRouterServerBuild().'
|
|
696
|
+
)
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
return runtime.load(entryName);
|
|
700
|
+
};
|