rsbuild-plugin-react-router 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +14 -2
  2. package/dist/468.js +48 -0
  3. package/dist/511.js +60 -86
  4. package/dist/819.js +64 -0
  5. package/dist/build-output-transforms.d.ts +3 -1
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/dev-hdr-channel.d.ts +9 -0
  8. package/dist/dev-hmr.d.ts +1 -22
  9. package/dist/dev-runtime-controller.d.ts +1 -6
  10. package/dist/dev-server.d.ts +3 -1
  11. package/dist/index.cjs +5417 -5080
  12. package/dist/index.js +1701 -1441
  13. package/dist/manifest-assets.d.ts +34 -0
  14. package/dist/manifest-snapshot.d.ts +17 -0
  15. package/dist/manifest-state.d.ts +12 -0
  16. package/dist/manifest.d.ts +2 -22
  17. package/dist/modify-browser-manifest.d.ts +2 -0
  18. package/dist/node-only-manifest.d.ts +8 -0
  19. package/dist/plugin-utils.d.ts +1 -1
  20. package/dist/rsc-prerender.d.ts +3 -2
  21. package/dist/server-build-worker-client.d.ts +20 -0
  22. package/dist/server-build-worker-protocol.d.ts +84 -0
  23. package/dist/server-build-worker.d.ts +1 -0
  24. package/dist/server-build-worker.js +123 -0
  25. package/dist/server-utils.d.ts +1 -2
  26. package/dist/types.d.ts +6 -0
  27. package/package.json +4 -4
  28. package/src/build-output-transforms.ts +22 -1
  29. package/src/classic-mode.ts +0 -1
  30. package/src/constants.ts +3 -0
  31. package/src/dev-hdr-channel.ts +38 -0
  32. package/src/dev-hmr.ts +57 -100
  33. package/src/dev-runtime-controller.ts +23 -18
  34. package/src/dev-server.ts +24 -4
  35. package/src/index.ts +229 -144
  36. package/src/lazy-compilation.ts +7 -2
  37. package/src/manifest-assets.ts +228 -0
  38. package/src/manifest-snapshot.ts +80 -0
  39. package/src/manifest-state.ts +71 -0
  40. package/src/manifest.ts +23 -161
  41. package/src/mode-plan.ts +12 -4
  42. package/src/modify-browser-manifest.ts +47 -18
  43. package/src/node-only-manifest.ts +52 -0
  44. package/src/plugin-utils.ts +6 -2
  45. package/src/prerender-build.ts +76 -86
  46. package/src/route-chunks.ts +152 -63
  47. package/src/rsc-prerender.ts +7 -35
  48. package/src/server-build-resolution.ts +1 -2
  49. package/src/server-build-worker-client.ts +221 -0
  50. package/src/server-build-worker-protocol.ts +69 -0
  51. package/src/server-build-worker.ts +192 -0
  52. package/src/server-utils.ts +0 -2
  53. package/src/types.ts +7 -0
@@ -0,0 +1,228 @@
1
+ import { DEFAULT_JS_DIST_PATH } from './constants.js';
2
+
3
+ export type ReactRouterManifestStats = {
4
+ assetsByChunkName?: Record<string, string[]>;
5
+ entrypointFilesByName?: Record<string, string[]>;
6
+ assetTypesByName?: Record<string, string>;
7
+ };
8
+
9
+ type ManifestStatsChunk = {
10
+ files?: Iterable<string>;
11
+ };
12
+ type ManifestStatsEntrypoint = {
13
+ getFiles?: () => Iterable<string>;
14
+ };
15
+ type ManifestStatsLookup<T> = Iterable<[string, T | null | undefined]> & {
16
+ get?: (name: string) => T | null | undefined;
17
+ };
18
+ type ManifestStatsCompilation = {
19
+ namedChunks: ManifestStatsLookup<ManifestStatsChunk>;
20
+ entrypoints?: ManifestStatsLookup<ManifestStatsEntrypoint>;
21
+ getAsset?: (name: string) => {
22
+ name?: string;
23
+ info?: { assetType?: string; javascriptModule?: boolean };
24
+ } | void;
25
+ };
26
+
27
+ export const stripAssetQuery = (name: string): string =>
28
+ name.replace(/[?#].*$/, '');
29
+
30
+ export const getManifestAssetType = (
31
+ name: string,
32
+ assetTypesByName?: Readonly<Record<string, string>>
33
+ ): string | undefined => {
34
+ if (/\.hot-update\.[cm]?js(?:[?#]|$)/.test(name)) return undefined;
35
+ const assetType = assetTypesByName?.[name];
36
+ if (assetType !== undefined) {
37
+ return assetType === 'extract-css' ? 'css' : assetType;
38
+ }
39
+
40
+ // Synthetic stats and older bundlers may not supply asset metadata.
41
+ const path = stripAssetQuery(name);
42
+ if (/\.[cm]?js$/.test(path)) {
43
+ return 'javascript';
44
+ }
45
+ return path.endsWith('.css') ? 'css' : undefined;
46
+ };
47
+
48
+ const orderChunkFiles = (
49
+ chunkName: string,
50
+ files: string[],
51
+ assetTypesByName: Record<string, string>
52
+ ): string[] => {
53
+ const ownFileIndex = files.findIndex(
54
+ file =>
55
+ getManifestAssetType(file, assetTypesByName) === 'javascript' &&
56
+ stripAssetQuery(file)
57
+ .replace(/\.[^.]+$/, '')
58
+ .endsWith(chunkName)
59
+ );
60
+ if (ownFileIndex <= 0) {
61
+ return files;
62
+ }
63
+ return [
64
+ files[ownFileIndex],
65
+ ...files.slice(0, ownFileIndex),
66
+ ...files.slice(ownFileIndex + 1),
67
+ ];
68
+ };
69
+
70
+ const collectManifestFilesByName = <T>(
71
+ items: ManifestStatsLookup<T>,
72
+ names: ReadonlySet<string> | undefined,
73
+ getFiles: (item: T) => string[]
74
+ ): Record<string, string[]> => {
75
+ const filesByName: Record<string, string[]> = {};
76
+ if (!names) {
77
+ for (const [name, item] of items) {
78
+ if (item == null) continue;
79
+ filesByName[name] = getFiles(item);
80
+ }
81
+ return filesByName;
82
+ }
83
+
84
+ const missingNames = new Set(names);
85
+ if (typeof items.get === 'function') {
86
+ for (const name of names) {
87
+ const item = items.get(name);
88
+ if (!item) {
89
+ continue;
90
+ }
91
+ if (item == null) continue;
92
+ filesByName[name] = getFiles(item);
93
+ missingNames.delete(name);
94
+ }
95
+ }
96
+ if (missingNames.size === 0) {
97
+ return filesByName;
98
+ }
99
+
100
+ for (const [name, item] of items) {
101
+ if (!missingNames.has(name)) {
102
+ continue;
103
+ }
104
+ if (item == null) continue;
105
+ filesByName[name] = getFiles(item);
106
+ missingNames.delete(name);
107
+ if (missingNames.size === 0) {
108
+ break;
109
+ }
110
+ }
111
+ return filesByName;
112
+ };
113
+
114
+ export const createReactRouterManifestStats = (
115
+ compilation: ManifestStatsCompilation | undefined,
116
+ chunkNames?: ReadonlySet<string>
117
+ ): ReactRouterManifestStats | undefined => {
118
+ if (!compilation) {
119
+ return undefined;
120
+ }
121
+
122
+ const assetsByChunkName = collectManifestFilesByName(
123
+ compilation.namedChunks,
124
+ chunkNames,
125
+ chunk => Array.from(chunk.files ?? [])
126
+ );
127
+ const entrypointFilesByName = compilation.entrypoints
128
+ ? collectManifestFilesByName(
129
+ compilation.entrypoints,
130
+ chunkNames,
131
+ entrypoint => Array.from(entrypoint.getFiles?.() ?? [])
132
+ )
133
+ : {};
134
+ const assetTypesByName: Record<string, string> = {};
135
+ if (compilation.getAsset) {
136
+ const files = new Set([
137
+ ...Object.values(assetsByChunkName).flat(),
138
+ ...Object.values(entrypointFilesByName).flat(),
139
+ ]);
140
+ for (const file of files) {
141
+ const info = compilation.getAsset(file)?.info;
142
+ const assetType =
143
+ info?.assetType ??
144
+ (typeof info?.javascriptModule === 'boolean'
145
+ ? 'javascript'
146
+ : undefined);
147
+ if (assetType !== undefined) {
148
+ assetTypesByName[file] = assetType;
149
+ }
150
+ }
151
+ }
152
+ for (const [chunkName, files] of Object.entries(assetsByChunkName)) {
153
+ assetsByChunkName[chunkName] = orderChunkFiles(
154
+ chunkName,
155
+ files,
156
+ assetTypesByName
157
+ );
158
+ }
159
+
160
+ return {
161
+ assetsByChunkName,
162
+ ...(Object.keys(entrypointFilesByName).length > 0
163
+ ? { entrypointFilesByName }
164
+ : {}),
165
+ ...(Object.keys(assetTypesByName).length > 0 ? { assetTypesByName } : {}),
166
+ };
167
+ };
168
+
169
+ type ChunkAssets = { js: string[]; css: string[] };
170
+
171
+ export const createChunkAssetResolver = (
172
+ clientStats: ReactRouterManifestStats | undefined,
173
+ includeEntrypointJavaScript: boolean
174
+ ): ((chunkName: string) => ChunkAssets) => {
175
+ const chunkAssetsByName = new Map<string, ChunkAssets>();
176
+ const getAssetType = (name: string) =>
177
+ getManifestAssetType(name, clientStats?.assetTypesByName);
178
+
179
+ return (chunkName: string): ChunkAssets => {
180
+ const cached = chunkAssetsByName.get(chunkName);
181
+ if (cached) {
182
+ return cached;
183
+ }
184
+ const assets = clientStats?.assetsByChunkName?.[chunkName];
185
+ if (!assets) {
186
+ const result = {
187
+ js: [`${DEFAULT_JS_DIST_PATH}/${chunkName}.js`],
188
+ css: [],
189
+ };
190
+ chunkAssetsByName.set(chunkName, result);
191
+ return result;
192
+ }
193
+
194
+ const cssAssets = new Set<string>();
195
+ const jsAssets: string[] = [];
196
+ for (const asset of assets) {
197
+ const assetType = getAssetType(asset);
198
+ if (assetType === 'css') {
199
+ cssAssets.add(asset);
200
+ } else if (assetType === 'javascript') {
201
+ jsAssets.push(asset);
202
+ }
203
+ }
204
+
205
+ if (jsAssets.length === 0) {
206
+ throw new Error(
207
+ `[react-router] Chunk "${chunkName}" emitted no JavaScript asset the browser manifest can reference (files: ${assets.join(', ') || 'none'}). Check the web \`output.filename.js\` scheme.`
208
+ );
209
+ }
210
+ // Entrypoint files contain the synchronous chunk group, not async children.
211
+ // Keep the route's own module first even when the runtime is listed first.
212
+ for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
213
+ const assetType = getAssetType(asset);
214
+ if (assetType === 'css') {
215
+ cssAssets.add(asset);
216
+ } else if (includeEntrypointJavaScript && assetType === 'javascript') {
217
+ jsAssets.push(asset);
218
+ }
219
+ }
220
+
221
+ const result = {
222
+ js: [...new Set(jsAssets)],
223
+ css: [...cssAssets],
224
+ };
225
+ chunkAssetsByName.set(chunkName, result);
226
+ return result;
227
+ };
228
+ };
@@ -0,0 +1,80 @@
1
+ import type {
2
+ ReactRouterManifestForDev,
3
+ RouteManifestModuleExports,
4
+ } from './manifest.js';
5
+ import type { ReactRouterServerBuildPlan } from './server-build-plan.js';
6
+ import type { Route } from './types.js';
7
+
8
+ export type ReactRouterManifestSnapshot = {
9
+ browser: ReactRouterManifestForDev;
10
+ moduleExportsByRouteId: RouteManifestModuleExports;
11
+ server: ReactRouterManifestForDev;
12
+ serverByBundleId: Readonly<Record<string, ReactRouterManifestForDev>>;
13
+ serverByEntryName: Readonly<Record<string, ReactRouterManifestForDev>>;
14
+ };
15
+
16
+ export const createReactRouterManifestSnapshot = ({
17
+ manifest,
18
+ sri,
19
+ moduleExportsByRouteId,
20
+ serverBuildPlan,
21
+ routesByServerBundleId,
22
+ }: {
23
+ manifest: ReactRouterManifestForDev;
24
+ sri: ReactRouterManifestForDev['sri'];
25
+ moduleExportsByRouteId: RouteManifestModuleExports;
26
+ serverBuildPlan: Pick<
27
+ ReactRouterServerBuildPlan,
28
+ 'defaultEntryName' | 'serverBundleEntries'
29
+ >;
30
+ routesByServerBundleId: Readonly<
31
+ Record<string, Readonly<Record<string, Route>>>
32
+ >;
33
+ }): ReactRouterManifestSnapshot => {
34
+ // Detach caller-owned data once so every derived view belongs to this snapshot.
35
+ const { server, moduleExportsByRouteId: detachedModuleExports } =
36
+ structuredClone({
37
+ server: { ...manifest, sri },
38
+ moduleExportsByRouteId,
39
+ });
40
+ const browser: ReactRouterManifestForDev = {
41
+ ...server,
42
+ sri: undefined,
43
+ };
44
+ const bundles = serverBuildPlan.serverBundleEntries.flatMap(
45
+ ({ bundleId, entryName }) => {
46
+ const bundleRoutes = routesByServerBundleId[bundleId];
47
+ if (!bundleRoutes) {
48
+ return [];
49
+ }
50
+
51
+ return [
52
+ {
53
+ bundleId,
54
+ entryName,
55
+ manifest: {
56
+ ...server,
57
+ routes: Object.fromEntries(
58
+ Object.entries(server.routes).filter(([routeId]) =>
59
+ Object.hasOwn(bundleRoutes, routeId)
60
+ )
61
+ ),
62
+ },
63
+ },
64
+ ];
65
+ }
66
+ );
67
+
68
+ return {
69
+ browser,
70
+ moduleExportsByRouteId: detachedModuleExports,
71
+ server,
72
+ serverByBundleId: Object.fromEntries(
73
+ bundles.map(({ bundleId, manifest }) => [bundleId, manifest])
74
+ ),
75
+ serverByEntryName: Object.fromEntries([
76
+ [serverBuildPlan.defaultEntryName, server],
77
+ ...bundles.map(({ entryName, manifest }) => [entryName, manifest]),
78
+ ]),
79
+ };
80
+ };
@@ -0,0 +1,71 @@
1
+ import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
2
+ import type { ReactRouterManifestSnapshot } from './manifest-snapshot.js';
3
+
4
+ type ReactRouterManifestState = {
5
+ stage(
6
+ compilation: Rspack.Compilation,
7
+ snapshot: ReactRouterManifestSnapshot
8
+ ): void;
9
+ read(): ReactRouterManifestSnapshot | null;
10
+ };
11
+
12
+ export const createReactRouterManifestState = ({
13
+ api,
14
+ isBuild,
15
+ onPublish,
16
+ }: {
17
+ api: Pick<
18
+ RsbuildPluginAPI,
19
+ 'onBeforeEnvironmentCompile' | 'onAfterEnvironmentCompile'
20
+ >;
21
+ isBuild: boolean;
22
+ onPublish: (
23
+ compilation: Rspack.Compilation,
24
+ snapshot: ReactRouterManifestSnapshot
25
+ ) => void;
26
+ }): ReactRouterManifestState => {
27
+ let latest: ReactRouterManifestSnapshot | null = null;
28
+ const pending = new WeakMap<
29
+ Rspack.Compilation,
30
+ ReactRouterManifestSnapshot
31
+ >();
32
+ const publish = (
33
+ compilation: Rspack.Compilation,
34
+ snapshot: ReactRouterManifestSnapshot
35
+ ) => {
36
+ latest = snapshot;
37
+ onPublish(compilation, snapshot);
38
+ };
39
+
40
+ if (isBuild) {
41
+ api.onBeforeEnvironmentCompile(({ environment }) => {
42
+ if (environment.name === 'web') {
43
+ // A failed web rebuild must not expose the previous build's manifest.
44
+ // A node-only rebuild may continue using the last successful snapshot.
45
+ latest = null;
46
+ }
47
+ });
48
+ }
49
+ api.onAfterEnvironmentCompile(({ environment, stats }) => {
50
+ if (environment.name !== 'web') {
51
+ return;
52
+ }
53
+ const snapshot =
54
+ stats && !stats.hasErrors() ? pending.get(stats.compilation) : undefined;
55
+ if (stats) {
56
+ pending.delete(stats.compilation);
57
+ }
58
+ if (snapshot && stats) {
59
+ publish(stats.compilation, snapshot);
60
+ } else if (isBuild) {
61
+ latest = null;
62
+ }
63
+ });
64
+
65
+ return {
66
+ stage(compilation, snapshot) {
67
+ pending.set(compilation, snapshot);
68
+ },
69
+ read: () => latest,
70
+ };
71
+ };
package/src/manifest.ts CHANGED
@@ -1,3 +1,14 @@
1
+ import {
2
+ createChunkAssetResolver,
3
+ type ReactRouterManifestStats,
4
+ stripAssetQuery,
5
+ getManifestAssetType,
6
+ } from './manifest-assets.js';
7
+ export {
8
+ createReactRouterManifestStats,
9
+ type ReactRouterManifestStats,
10
+ } from './manifest-assets.js';
11
+ import { BROWSER_MANIFEST_ENTRY_NAME } from './constants.js';
1
12
  import { createHash } from 'node:crypto';
2
13
  import { dirname, isAbsolute, relative, resolve } from 'pathe';
3
14
  import * as Effect from 'effect/Effect';
@@ -143,30 +154,6 @@ export type ReactRouterManifestForDev = {
143
154
  routes: Record<string, RouteManifestItem>;
144
155
  };
145
156
 
146
- export type ReactRouterManifestStats = {
147
- assetsByChunkName?: Record<string, string[]>;
148
- entrypointFilesByName?: Record<string, string[]>;
149
- };
150
-
151
- type ReactRouterManifestStatsChunk = {
152
- files?: Iterable<string>;
153
- };
154
-
155
- type ReactRouterManifestStatsEntrypoint = {
156
- getFiles?: () => Iterable<string>;
157
- };
158
-
159
- type ReactRouterManifestStatsLookup<T> = Iterable<
160
- [string, T | null | undefined]
161
- > & {
162
- get?: (name: string) => T | null | undefined;
163
- };
164
-
165
- type ReactRouterManifestStatsCompilation = {
166
- namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
167
- entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
168
- };
169
-
170
157
  // Emitted asset names may carry a query (`output.filename.js:
171
158
  // '[name].js?v=[contenthash:8]'`); classify on the pathname but keep the full
172
159
  // reference, since the query is part of the URL the browser must request.
@@ -252,81 +239,6 @@ export const collectUnsupportedRscScriptAssets = (
252
239
  return [...unsupported];
253
240
  };
254
241
 
255
- const collectManifestFilesByName = <T>(
256
- items: ReactRouterManifestStatsLookup<T>,
257
- names: ReadonlySet<string> | undefined,
258
- getFiles: (name: string, item: T) => string[]
259
- ): Record<string, string[]> => {
260
- const filesByName: Record<string, string[]> = {};
261
- if (!names) {
262
- for (const [name, item] of items) {
263
- if (item == null) {
264
- continue;
265
- }
266
- filesByName[name] = getFiles(name, item);
267
- }
268
- return filesByName;
269
- }
270
-
271
- const missingNames = new Set(names);
272
- if (typeof items.get === 'function') {
273
- for (const name of names) {
274
- const item = items.get(name);
275
- if (item == null) {
276
- continue;
277
- }
278
- filesByName[name] = getFiles(name, item);
279
- missingNames.delete(name);
280
- }
281
- }
282
-
283
- if (missingNames.size === 0) {
284
- return filesByName;
285
- }
286
-
287
- for (const [name, item] of items) {
288
- if (!missingNames.has(name)) {
289
- continue;
290
- }
291
- if (item == null) {
292
- continue;
293
- }
294
- filesByName[name] = getFiles(name, item);
295
- missingNames.delete(name);
296
- if (missingNames.size === 0) {
297
- break;
298
- }
299
- }
300
-
301
- return filesByName;
302
- };
303
-
304
- export const createReactRouterManifestStats = (
305
- compilation: ReactRouterManifestStatsCompilation | undefined,
306
- chunkNames?: ReadonlySet<string>
307
- ): ReactRouterManifestStats | undefined => {
308
- if (!compilation) {
309
- return undefined;
310
- }
311
-
312
- const assetsByChunkName = collectManifestFilesByName(
313
- compilation.namedChunks,
314
- chunkNames,
315
- (_chunkName, chunk) => Array.from(chunk.files ?? [])
316
- );
317
- const entrypointFilesByName = compilation.entrypoints
318
- ? collectManifestFilesByName(
319
- compilation.entrypoints,
320
- chunkNames,
321
- (_name, entrypoint) => Array.from(entrypoint.getFiles?.() ?? [])
322
- )
323
- : {};
324
-
325
- return Object.keys(entrypointFilesByName).length > 0
326
- ? { assetsByChunkName, entrypointFilesByName }
327
- : { assetsByChunkName };
328
- };
329
-
330
242
  export type RouteManifestModuleExports = Record<string, readonly string[]>;
331
243
 
332
244
  export type ReactRouterManifestGenerationResult = {
@@ -334,11 +246,6 @@ export type ReactRouterManifestGenerationResult = {
334
246
  moduleExportsByRouteId: RouteManifestModuleExports;
335
247
  };
336
248
 
337
- type ChunkAssets = {
338
- js: string[];
339
- css: string[];
340
- };
341
-
342
249
  type RouteManifestAnalysis = {
343
250
  cssAssets: string[];
344
251
  exports: Set<string>;
@@ -351,61 +258,6 @@ type RouteManifestAnalysis = {
351
258
  const DEFAULT_MANIFEST_DIR = DEFAULT_JS_DIST_PATH;
352
259
  const CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/;
353
260
 
354
- const createChunkAssetResolver = (
355
- clientStats: ReactRouterManifestStats | undefined,
356
- includeEntrypointJs: boolean
357
- ): ((chunkName: string) => ChunkAssets) => {
358
- const chunkAssetsByName = new Map<string, ChunkAssets>();
359
-
360
- return (chunkName: string): ChunkAssets => {
361
- const cached = chunkAssetsByName.get(chunkName);
362
- if (cached) {
363
- return cached;
364
- }
365
-
366
- const assets = clientStats?.assetsByChunkName?.[chunkName];
367
- if (!assets) {
368
- const fallback = `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`;
369
- const result = { js: [fallback], css: [] };
370
- chunkAssetsByName.set(chunkName, result);
371
- return result;
372
- }
373
-
374
- const cssAssets = new Set<string>();
375
- const jsAssets = new Set<string>();
376
- // The chunk's own files come first so `js[0]` is the route module itself;
377
- // entrypoint files (runtime, shared vendor chunks) follow as `imports`.
378
- for (const asset of assets) {
379
- if (isManifestCssAsset(asset)) {
380
- cssAssets.add(asset);
381
- } else if (isManifestJsAsset(asset)) {
382
- jsAssets.add(asset);
383
- }
384
- }
385
- for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
386
- if (isManifestCssAsset(asset)) {
387
- cssAssets.add(asset);
388
- } else if (includeEntrypointJs && isManifestJsAsset(asset)) {
389
- jsAssets.add(asset);
390
- }
391
- }
392
- if (jsAssets.size === 0) {
393
- // Compilation metadata exists for this chunk but names no module script.
394
- // Guessing `<dir>/<chunk>.js` here would turn an identifiable build
395
- // problem into a browser 404, so surface it at build time instead.
396
- throw new Error(
397
- `[react-router] Chunk "${chunkName}" emitted no JavaScript asset the browser manifest can reference (files: ${
398
- assets.join(', ') || 'none'
399
- }). Check the web \`output.filename.js\` scheme.`
400
- );
401
- }
402
-
403
- const result = { js: [...jsAssets], css: [...cssAssets] };
404
- chunkAssetsByName.set(chunkName, result);
405
- return result;
406
- };
407
- };
408
-
409
261
  const analyzeRouteForManifestEffect = ({
410
262
  discoveredCssAssets,
411
263
  isBuild,
@@ -696,14 +548,24 @@ function generateReactRouterManifestForDevEffect(
696
548
  const manifestPath = getReactRouterManifestPath({
697
549
  version,
698
550
  isBuild,
699
- entryModulePath: entryJsAssets[0],
551
+ entryModulePath: stripAssetQuery(entryJsAssets[0] ?? ''),
700
552
  });
701
553
 
554
+ const browserManifestPath =
555
+ clientStats?.assetsByChunkName?.[BROWSER_MANIFEST_ENTRY_NAME]?.find(
556
+ name =>
557
+ getManifestAssetType(name, clientStats.assetTypesByName) ===
558
+ 'javascript'
559
+ ) ?? manifestPath;
560
+ // Report-stage serialization happens after hashing, so retain the version
561
+ // query even when the virtual manifest has a content-hashed filename.
702
562
  const manifest = {
703
563
  version,
704
564
  url: combineURLs(
705
565
  assetPrefix,
706
- isBuild ? manifestPath : `${manifestPath}?v=${version}`
566
+ isBuild
567
+ ? manifestPath
568
+ : `${browserManifestPath}${browserManifestPath.includes('?') ? '&' : '?'}v=${version}`
707
569
  ),
708
570
  hmr: undefined,
709
571
  entry: fingerprintedValues.entry,
package/src/mode-plan.ts CHANGED
@@ -113,9 +113,14 @@ type CreateReactRouterModePlanOptions =
113
113
 
114
114
  const RSC_LAYERS = rspack.experiments.rsc.Layers;
115
115
 
116
- const createReactRouterPackageAliases = (): Record<string, string> => {
117
- const reactRouterPath = resolveAppPackagePath('react-router');
118
- const reactRouterDomPath = resolveAppPackagePath('react-router/dom');
116
+ const createReactRouterPackageAliases = (
117
+ rootPath: string
118
+ ): Record<string, string> => {
119
+ const reactRouterPath = resolveAppPackagePath('react-router', rootPath);
120
+ const reactRouterDomPath = resolveAppPackagePath(
121
+ 'react-router/dom',
122
+ rootPath
123
+ );
119
124
  return {
120
125
  ...(reactRouterPath ? { 'react-router$': reactRouterPath } : {}),
121
126
  ...(reactRouterDomPath ? { 'react-router/dom$': reactRouterDomPath } : {}),
@@ -284,7 +289,9 @@ const createClassicModePlan = async ({
284
289
  ssr,
285
290
  devHmr,
286
291
  });
287
- const reactRouterAliases = createReactRouterPackageAliases();
292
+ const reactRouterAliases = createReactRouterPackageAliases(
293
+ api.context.rootPath
294
+ );
288
295
  return {
289
296
  kind: 'classic',
290
297
  artifacts,
@@ -334,6 +341,7 @@ const createClassicModePlan = async ({
334
341
  setup: [
335
342
  createReactRouterDevServerSetup({
336
343
  loadBuild: artifacts.devRuntime.createBuildLoader(),
344
+ rootPath: api.context.rootPath,
337
345
  }),
338
346
  ],
339
347
  },