rsbuild-plugin-react-router 0.5.0 → 0.6.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.
Files changed (110) hide show
  1. package/README.md +58 -208
  2. package/dist/511.js +371 -332
  3. package/dist/build-output-transforms.d.ts +30 -6
  4. package/dist/classic-mode.d.ts +56 -0
  5. package/dist/config-imports.d.ts +8 -2
  6. package/dist/constants.d.ts +3 -0
  7. package/dist/dev-background-resources.d.ts +3 -1
  8. package/dist/dev-generation.d.ts +2 -3
  9. package/dist/dev-hmr.d.ts +8 -2
  10. package/dist/dev-runtime-controller.d.ts +6 -1
  11. package/dist/dev-source-maps.d.ts +4 -0
  12. package/dist/effect-runtime.d.ts +17 -5
  13. package/dist/entry-paths.d.ts +16 -0
  14. package/dist/environment-output.d.ts +6 -0
  15. package/dist/export-utils.d.ts +2 -1
  16. package/dist/index.cjs +3964 -2217
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +3474 -1799
  19. package/dist/lazy-compilation-prewarm.d.ts +8 -5
  20. package/dist/manifest.d.ts +17 -5
  21. package/dist/mode-plan.d.ts +82 -0
  22. package/dist/modify-browser-manifest.d.ts +8 -4
  23. package/dist/plugin-utils.d.ts +27 -1
  24. package/dist/prerender-build.d.ts +5 -5
  25. package/dist/prerender.d.ts +1 -5
  26. package/dist/react-router-config.d.ts +11 -3
  27. package/dist/route-artifacts.d.ts +4 -2
  28. package/dist/route-chunks.d.ts +8 -2
  29. package/dist/route-component-transform.d.ts +0 -2
  30. package/dist/route-export-pruning.d.ts +4 -1
  31. package/dist/route-imports.d.ts +18 -0
  32. package/dist/route-transform-tasks.d.ts +5 -0
  33. package/dist/route-watch.d.ts +11 -6
  34. package/dist/rsc-dev-server.d.ts +26 -0
  35. package/dist/rsc-prerender.d.ts +54 -0
  36. package/dist/rsc-route-config.d.ts +6 -0
  37. package/dist/rsc-route-exports.d.ts +15 -0
  38. package/dist/rsc-route-transform-loader.cjs +43 -0
  39. package/dist/rsc-route-transform-loader.d.ts +30 -0
  40. package/dist/rsc-route-transform-loader.js +16 -0
  41. package/dist/rsc-route-transform-registration.d.ts +12 -0
  42. package/dist/rsc-route-transforms.d.ts +21 -0
  43. package/dist/rsc-support.d.ts +23 -0
  44. package/dist/rsc-virtual-modules.d.ts +19 -0
  45. package/dist/server-build-plan.d.ts +2 -1
  46. package/dist/server-build-resolution.d.ts +1 -2
  47. package/dist/server-utils.d.ts +4 -5
  48. package/dist/ssr-asset-relocation.d.ts +98 -0
  49. package/dist/templates/entry.rsc.client.d.ts +1 -0
  50. package/dist/templates/entry.rsc.client.js +61 -0
  51. package/dist/templates/entry.rsc.d.ts +9 -0
  52. package/dist/templates/entry.rsc.js +38 -0
  53. package/dist/templates/entry.rsc.ssr.d.ts +4 -0
  54. package/dist/templates/entry.rsc.ssr.js +24 -0
  55. package/dist/typegen.d.ts +4 -2
  56. package/dist/types.d.ts +30 -1
  57. package/package.json +69 -14
  58. package/src/build-output-transforms.ts +155 -21
  59. package/src/classic-mode.ts +258 -0
  60. package/src/config-imports.ts +153 -6
  61. package/src/constants.ts +6 -2
  62. package/src/dev-background-resources.ts +46 -85
  63. package/src/dev-generation.ts +52 -33
  64. package/src/dev-hmr.ts +112 -80
  65. package/src/dev-runtime-artifacts.ts +11 -12
  66. package/src/dev-runtime-controller.ts +75 -85
  67. package/src/dev-runtime-session.ts +14 -18
  68. package/src/dev-server.ts +2 -0
  69. package/src/dev-source-maps.ts +257 -0
  70. package/src/effect-runtime.ts +105 -57
  71. package/src/entry-paths.ts +80 -0
  72. package/src/environment-output.ts +55 -0
  73. package/src/export-utils.ts +15 -11
  74. package/src/index.ts +661 -496
  75. package/src/lazy-compilation-prewarm.ts +20 -4
  76. package/src/manifest.ts +186 -100
  77. package/src/mode-plan.ts +367 -0
  78. package/src/modify-browser-manifest.ts +131 -124
  79. package/src/plugin-utils.ts +103 -39
  80. package/src/prerender-build.ts +82 -104
  81. package/src/prerender.ts +23 -24
  82. package/src/react-router-config.ts +72 -26
  83. package/src/route-artifacts.ts +96 -66
  84. package/src/route-chunks.ts +196 -54
  85. package/src/route-component-transform.ts +14 -21
  86. package/src/route-export-pruning.ts +4 -3
  87. package/src/route-imports.ts +100 -0
  88. package/src/route-transform-tasks.ts +39 -25
  89. package/src/route-watch.ts +166 -188
  90. package/src/rsc-dev-server.ts +112 -0
  91. package/src/rsc-prerender.ts +362 -0
  92. package/src/rsc-route-config.ts +175 -0
  93. package/src/rsc-route-exports.ts +67 -0
  94. package/src/rsc-route-transform-loader.ts +65 -0
  95. package/src/rsc-route-transform-registration.ts +145 -0
  96. package/src/rsc-route-transforms.ts +995 -0
  97. package/src/rsc-runtime.d.ts +143 -0
  98. package/src/rsc-support.ts +116 -0
  99. package/src/rsc-virtual-modules.ts +113 -0
  100. package/src/server-build-plan.ts +14 -3
  101. package/src/server-build-resolution.ts +37 -47
  102. package/src/server-utils.ts +21 -35
  103. package/src/ssr-asset-relocation.ts +183 -0
  104. package/src/ssr-externals.ts +8 -26
  105. package/src/templates/entry.rsc.client.tsx +168 -0
  106. package/src/templates/entry.rsc.ssr.tsx +45 -0
  107. package/src/templates/entry.rsc.tsx +80 -0
  108. package/src/typegen.ts +40 -23
  109. package/src/types.ts +39 -1
  110. package/src/warnings/warn-on-client-source-maps.ts +6 -10
@@ -0,0 +1,367 @@
1
+ import type {
2
+ RsbuildConfig,
3
+ RsbuildEntryDescription,
4
+ RsbuildPluginAPI,
5
+ Rspack,
6
+ } from '@rsbuild/core';
7
+ import { rspack } from '@rsbuild/core';
8
+ import type { RouteConfigEntry } from '@react-router/dev/routes';
9
+ import { resolve } from 'pathe';
10
+ import type {
11
+ Config,
12
+ ResolvedReactRouterConfig,
13
+ } from './react-router-config.js';
14
+ import type { RouteChunkCache, RouteChunkConfig } from './route-chunks.js';
15
+ import type { DevHmrPlanOptions } from './dev-hmr.js';
16
+ import type { Route } from './types.js';
17
+ import { createReactRouterDevServerSetup } from './dev-server.js';
18
+ import { resolveAppPackagePath } from './plugin-utils.js';
19
+ import { resolvePrerenderPaths } from './prerender.js';
20
+ import { createReactRouterNodeEntries } from './server-build-plan.js';
21
+ import { getSsrExternals } from './ssr-externals.js';
22
+ import {
23
+ createClassicBuildArtifacts,
24
+ createClassicVirtualModules,
25
+ createClassicWebRouteEntries,
26
+ type ClassicBuildArtifacts,
27
+ } from './classic-mode.js';
28
+ import {
29
+ createReactRouterRscDevServerSetup,
30
+ createReactRouterRscResolveAliases,
31
+ createReactRouterRscVirtualModules,
32
+ } from './rsc-support.js';
33
+
34
+ type CommonModePlan = {
35
+ routeChunkConfig: RouteChunkConfig;
36
+ manifestChunkNames: Set<string>;
37
+ webEntries: Record<string, string | RsbuildEntryDescription>;
38
+ nodeEntries: Record<string, string | RsbuildEntryDescription>;
39
+ createVirtualModules(
40
+ publicPath: string,
41
+ jsDistPath: string
42
+ ): Record<string, string>;
43
+ createResolveConfig(rootPath: string): Rspack.Configuration['resolve'];
44
+ server: RsbuildConfig['server'] | undefined;
45
+ webExternalsType: 'module' | undefined;
46
+ webOutput: NonNullable<Rspack.Configuration['output']>;
47
+ webOptimization: NonNullable<Rspack.Configuration['optimization']>;
48
+ nodeExternals: string[] | undefined;
49
+ nodeDependencies: { dependencies?: string[] };
50
+ };
51
+
52
+ export type ClassicModePlan = CommonModePlan & {
53
+ kind: 'classic';
54
+ artifacts: ClassicBuildArtifacts;
55
+ routeChunkOptions: {
56
+ splitRouteModules: Config['splitRouteModules'];
57
+ rootRouteFile: string;
58
+ isBuild: boolean;
59
+ cache: RouteChunkCache;
60
+ };
61
+ };
62
+
63
+ export type RscModePlan = CommonModePlan & {
64
+ kind: 'rsc';
65
+ prerenderPaths: string[];
66
+ };
67
+
68
+ export type ReactRouterModePlan = ClassicModePlan | RscModePlan;
69
+
70
+ // Fields both mode planners need. Mode-specific inputs live on the
71
+ // per-mode option types below so each planner owns only what it uses.
72
+ type ModePlanContext = {
73
+ allowedActionOriginsForBuild: string[] | undefined;
74
+ api: RsbuildPluginAPI;
75
+ appDirectory: string;
76
+ basename: string;
77
+ customServer: boolean;
78
+ splitRouteModules: Config['splitRouteModules'];
79
+ isBuild: boolean;
80
+ isSpaMode: boolean;
81
+ prerenderConfig: Config['prerender'];
82
+ routeConfig: RouteConfigEntry[];
83
+ routeDiscovery: Config['routeDiscovery'];
84
+ routes: Record<string, Route>;
85
+ rootRouteFile: string;
86
+ ssr: boolean;
87
+ };
88
+
89
+ type CreateClassicModePlanOptions = ModePlanContext & {
90
+ assetsBuildDirectory: string;
91
+ devHmr?: DevHmrPlanOptions;
92
+ defaultEntryName: string;
93
+ entryServerPath: string;
94
+ finalEntryClientPath: string;
95
+ future: Config['future'];
96
+ hasServerApp: boolean;
97
+ reactRouterConfig: ResolvedReactRouterConfig;
98
+ routeChunkCache: RouteChunkCache;
99
+ serverAppPath: string;
100
+ shouldDependOnWebCompiler: boolean;
101
+ };
102
+
103
+ type CreateRscModePlanOptions = ModePlanContext & {
104
+ buildDirectory: string;
105
+ finalEntryRscClientPath: string;
106
+ finalEntryRscPath: string;
107
+ outputClientPath: string;
108
+ pluginName: string;
109
+ serverBuildFile: string | undefined;
110
+ };
111
+
112
+ type CreateReactRouterModePlanOptions =
113
+ | ({ isRscMode: true } & CreateRscModePlanOptions)
114
+ | ({ isRscMode: false } & CreateClassicModePlanOptions);
115
+
116
+ const RSC_LAYERS = rspack.experiments.rsc.Layers;
117
+
118
+ const createReactRouterPackageAliases = (): Record<string, string> => {
119
+ const reactRouterPath = resolveAppPackagePath('react-router');
120
+ const reactRouterDomPath = resolveAppPackagePath('react-router/dom');
121
+ return {
122
+ ...(reactRouterPath ? { 'react-router$': reactRouterPath } : {}),
123
+ ...(reactRouterDomPath ? { 'react-router/dom$': reactRouterDomPath } : {}),
124
+ };
125
+ };
126
+
127
+ const createRscModePlan = async ({
128
+ allowedActionOriginsForBuild,
129
+ api,
130
+ appDirectory,
131
+ basename,
132
+ buildDirectory,
133
+ customServer,
134
+ finalEntryRscClientPath,
135
+ finalEntryRscPath,
136
+ isBuild,
137
+ outputClientPath,
138
+ pluginName,
139
+ prerenderConfig,
140
+ routeConfig,
141
+ routeDiscovery,
142
+ routes,
143
+ rootRouteFile,
144
+ serverBuildFile,
145
+ splitRouteModules,
146
+ ssr,
147
+ }: CreateRscModePlanOptions): Promise<RscModePlan> => {
148
+ const rscServerEntryName = (serverBuildFile || 'index.js').replace(
149
+ /\.js$/,
150
+ ''
151
+ );
152
+ const prerenderPaths = await resolvePrerenderPaths(
153
+ prerenderConfig,
154
+ ssr,
155
+ routeConfig,
156
+ {
157
+ logWarning: true,
158
+ warn: message => api.logger.warn(message),
159
+ }
160
+ );
161
+ return {
162
+ kind: 'rsc',
163
+ prerenderPaths,
164
+ // RSC route chunking is content-detected; the config value only gates
165
+ // 'enforce' validation, matching upstream's RSC Vite plugin.
166
+ routeChunkConfig: {
167
+ splitRouteModules,
168
+ appDirectory,
169
+ rootRouteFile,
170
+ },
171
+ manifestChunkNames: new Set<string>(['index']),
172
+ webEntries: {
173
+ index: {
174
+ import: finalEntryRscClientPath,
175
+ html: false,
176
+ },
177
+ },
178
+ nodeEntries: {
179
+ [rscServerEntryName]: {
180
+ import: finalEntryRscPath,
181
+ layer: RSC_LAYERS.rsc,
182
+ },
183
+ },
184
+ createVirtualModules: (publicPath: string, jsDistPath: string) =>
185
+ createReactRouterRscVirtualModules({
186
+ allowedActionOrigins: allowedActionOriginsForBuild,
187
+ appDirectory,
188
+ basename,
189
+ buildDirectory,
190
+ isBuild,
191
+ jsDistPath,
192
+ outputClientPath,
193
+ publicPath,
194
+ routeDiscovery,
195
+ routes,
196
+ ssr,
197
+ }),
198
+ createResolveConfig: (rootPath: string) => ({
199
+ modules: [resolve(rootPath, 'node_modules'), 'node_modules'],
200
+ alias: createReactRouterRscResolveAliases(rootPath),
201
+ }),
202
+ server: !customServer
203
+ ? {
204
+ setup: createReactRouterRscDevServerSetup({
205
+ entryName: rscServerEntryName,
206
+ pluginName,
207
+ }),
208
+ }
209
+ : undefined,
210
+ webExternalsType: undefined,
211
+ webOutput: {
212
+ chunkFormat: 'array-push',
213
+ chunkLoading: 'jsonp',
214
+ workerChunkLoading: 'import-scripts',
215
+ wasmLoading: 'fetch',
216
+ module: false,
217
+ },
218
+ webOptimization: {
219
+ splitChunks: false,
220
+ // Flight resolves client references dynamically by export name. Preserve
221
+ // every export and its public name in production so the manifest can
222
+ // resolve route components, data exports, and React Router boundaries.
223
+ ...(isBuild ? { mangleExports: false, usedExports: false } : {}),
224
+ runtimeChunk: false,
225
+ },
226
+ nodeExternals: undefined,
227
+ nodeDependencies: {},
228
+ };
229
+ };
230
+
231
+ const createClassicModePlan = async ({
232
+ api,
233
+ allowedActionOriginsForBuild,
234
+ appDirectory,
235
+ assetsBuildDirectory,
236
+ basename,
237
+ customServer,
238
+ defaultEntryName,
239
+ devHmr,
240
+ entryServerPath,
241
+ finalEntryClientPath,
242
+ future,
243
+ hasServerApp,
244
+ isBuild,
245
+ isSpaMode,
246
+ prerenderConfig,
247
+ reactRouterConfig,
248
+ routeChunkCache,
249
+ routeConfig,
250
+ routeDiscovery,
251
+ routes,
252
+ rootRouteFile,
253
+ serverAppPath,
254
+ shouldDependOnWebCompiler,
255
+ splitRouteModules,
256
+ ssr,
257
+ }: CreateClassicModePlanOptions): Promise<ClassicModePlan> => {
258
+ const routeChunkConfig: RouteChunkConfig = {
259
+ splitRouteModules,
260
+ appDirectory,
261
+ rootRouteFile,
262
+ };
263
+ const routeChunkOptions = {
264
+ splitRouteModules,
265
+ rootRouteFile,
266
+ isBuild,
267
+ cache: routeChunkCache,
268
+ };
269
+ const { manifestChunkNames, webRouteEntries } = createClassicWebRouteEntries({
270
+ appDirectory,
271
+ isBuild,
272
+ routes,
273
+ splitRouteModules: Boolean(splitRouteModules),
274
+ });
275
+ const artifacts = await createClassicBuildArtifacts({
276
+ api,
277
+ defaultEntryName,
278
+ isBuild,
279
+ prerenderConfig,
280
+ reactRouterConfig,
281
+ routeConfig,
282
+ routes,
283
+ rootDirectory: process.cwd(),
284
+ ssr,
285
+ devHmr,
286
+ });
287
+ const reactRouterAliases = createReactRouterPackageAliases();
288
+ return {
289
+ kind: 'classic',
290
+ artifacts,
291
+ routeChunkConfig,
292
+ routeChunkOptions,
293
+ manifestChunkNames,
294
+ webEntries: {
295
+ // `html: false` prevents rsbuild from emitting a stray entry.client.html
296
+ // into build/client; React Router renders HTML itself.
297
+ 'entry.client': { import: finalEntryClientPath, html: false },
298
+ 'virtual/react-router/browser-manifest': {
299
+ import: 'virtual/react-router/browser-manifest',
300
+ html: false,
301
+ },
302
+ ...webRouteEntries,
303
+ },
304
+ nodeEntries: createReactRouterNodeEntries({
305
+ hasServerApp,
306
+ isBuild,
307
+ serverAppPath,
308
+ entryServerPath,
309
+ serverBuildFile: reactRouterConfig.serverBuildFile,
310
+ defaultEntryName,
311
+ serverBundleEntries: artifacts.serverBundleEntries,
312
+ }),
313
+ createVirtualModules: (publicPath: string, _jsDistPath: string) =>
314
+ createClassicVirtualModules({
315
+ allowedActionOrigins: allowedActionOriginsForBuild,
316
+ appDirectory,
317
+ assetsBuildDirectory,
318
+ basename,
319
+ devHmrRuntimeModule: devHmr?.runtimeModule,
320
+ entryServerPath,
321
+ future,
322
+ isSpaMode,
323
+ prerenderPaths: artifacts.prerenderPaths,
324
+ publicPath,
325
+ routeDiscovery,
326
+ routes,
327
+ routesByServerBundleId: artifacts.routesByServerBundleId,
328
+ ssr,
329
+ }),
330
+ createResolveConfig: () => ({ alias: reactRouterAliases }),
331
+ server: customServer
332
+ ? undefined
333
+ : {
334
+ setup: [
335
+ createReactRouterDevServerSetup({
336
+ loadBuild: artifacts.devRuntime.createBuildLoader(),
337
+ }),
338
+ ],
339
+ },
340
+ webExternalsType: 'module',
341
+ webOutput: {
342
+ chunkFormat: 'module',
343
+ chunkLoading: 'import',
344
+ workerChunkLoading: 'import',
345
+ wasmLoading: 'fetch',
346
+ library: { type: 'module' },
347
+ module: true,
348
+ },
349
+ webOptimization: {
350
+ avoidEntryIife: true,
351
+ runtimeChunk: 'single',
352
+ },
353
+ nodeExternals: Array.from(
354
+ new Set(['express', ...getSsrExternals(process.cwd())])
355
+ ),
356
+ nodeDependencies: shouldDependOnWebCompiler
357
+ ? { dependencies: ['web'] }
358
+ : {},
359
+ };
360
+ };
361
+
362
+ export const createReactRouterModePlan = (
363
+ options: CreateReactRouterModePlanOptions
364
+ ): Promise<ReactRouterModePlan> =>
365
+ options.isRscMode
366
+ ? createRscModePlan(options)
367
+ : createClassicModePlan(options);
@@ -1,11 +1,16 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import type { Route, PluginOptions } from './types.js';
2
3
  import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
3
4
  import {
5
+ createReactRouterManifestOptions,
4
6
  createReactRouterManifestStats,
5
7
  generateReactRouterManifestForDev,
6
8
  getReactRouterManifestChunkNames,
7
- getReactRouterManifestForDev,
8
9
  getReactRouterManifestPath,
10
+ type ReactRouterManifestForDev as ReactRouterManifest,
11
+ type RouteChunkManifestOptions,
12
+ type RouteManifestModuleExports,
13
+ type RouteModuleAnalysisProvider,
9
14
  } from './manifest.js';
10
15
  import { combineURLs } from './plugin-utils.js';
11
16
  import jsesc from 'jsesc';
@@ -21,6 +26,7 @@ type StatsWithIntegrity = {
21
26
 
22
27
  type CompilationAssetWithIntegrity = {
23
28
  name: string;
29
+ source?: { source(): string | Buffer };
24
30
  info?: {
25
31
  integrity?: unknown;
26
32
  };
@@ -33,15 +39,14 @@ type CompilationWithIntegrityAssets =
33
39
  | Pick<Rspack.Compilation, 'getAssets'>;
34
40
 
35
41
  type ModifyBrowserManifestOptions = {
36
- future?: { unstable_subResourceIntegrity?: boolean };
37
42
  subResourceIntegrity?: boolean;
43
+ future?: { unstable_subResourceIntegrity?: boolean };
38
44
  manifestChunkNames?: ReadonlySet<string>;
45
+ routeModuleAnalysis?: RouteModuleAnalysisProvider;
39
46
  onManifest?: (
40
- manifest: Awaited<ReturnType<typeof getReactRouterManifestForDev>>,
47
+ manifest: ReactRouterManifest,
41
48
  sri: Record<string, string> | true | undefined,
42
- moduleExportsByRouteId: Awaited<
43
- ReturnType<typeof generateReactRouterManifestForDev>
44
- >['moduleExportsByRouteId'],
49
+ moduleExportsByRouteId: RouteManifestModuleExports,
45
50
  context: {
46
51
  compilation: Rspack.Compilation;
47
52
  manifestStats: ReturnType<typeof createReactRouterManifestStats>;
@@ -51,18 +56,9 @@ type ModifyBrowserManifestOptions = {
51
56
 
52
57
  type ProcessAssetsApi = Pick<RsbuildPluginAPI, 'processAssets'>;
53
58
  type AssetPrefixInput = string | (() => string);
54
- type ReactRouterManifest = Awaited<
55
- ReturnType<typeof getReactRouterManifestForDev>
56
- >;
57
- type RouteManifestModuleExports = Awaited<
58
- ReturnType<typeof generateReactRouterManifestForDev>
59
- >['moduleExportsByRouteId'];
60
- type GeneratedManifest = {
61
- manifest: ReactRouterManifest;
62
- moduleExportsByRouteId: RouteManifestModuleExports;
63
- manifestStats: ReturnType<typeof createReactRouterManifestStats>;
64
- assetPrefix: string;
65
- };
59
+ type ManifestProcessAssetsContext = Parameters<
60
+ Parameters<RsbuildPluginAPI['processAssets']>[1]
61
+ >[0];
66
62
 
67
63
  const BROWSER_MANIFEST_ASSET =
68
64
  'static/js/virtual/react-router/browser-manifest.js';
@@ -85,12 +81,27 @@ const addIntegrity = (
85
81
  assetName: unknown,
86
82
  integrity: unknown
87
83
  ) => {
88
- if (typeof assetName !== 'string' || typeof integrity !== 'string') {
84
+ if (
85
+ typeof assetName !== 'string' ||
86
+ !assetName.endsWith('.js') ||
87
+ typeof integrity !== 'string'
88
+ ) {
89
89
  return;
90
90
  }
91
91
  sri[toManifestAssetUrl(assetPrefix, assetName)] = integrity;
92
92
  };
93
93
 
94
+ const computeSubresourceIntegrity = (
95
+ source: CompilationAssetWithIntegrity['source']
96
+ ): string | undefined => {
97
+ if (!source) {
98
+ return undefined;
99
+ }
100
+ return `sha384-${createHash('sha384')
101
+ .update(source.source())
102
+ .digest('base64')}`;
103
+ };
104
+
94
105
  export const collectSubresourceIntegrity = (
95
106
  stats: StatsWithIntegrity | undefined,
96
107
  compilation: CompilationWithIntegrityAssets | undefined,
@@ -106,7 +117,15 @@ export const collectSubresourceIntegrity = (
106
117
  const assets =
107
118
  compilation.getAssets() as readonly CompilationAssetWithIntegrity[];
108
119
  for (const asset of assets) {
109
- addIntegrity(sri, assetPrefix, asset.name, asset.info?.integrity);
120
+ if (!asset.name.endsWith('.js')) {
121
+ continue;
122
+ }
123
+ addIntegrity(
124
+ sri,
125
+ assetPrefix,
126
+ asset.name,
127
+ computeSubresourceIntegrity(asset.source) ?? asset.info?.integrity
128
+ );
110
129
  }
111
130
  }
112
131
 
@@ -119,7 +138,7 @@ export function registerModifyBrowserManifestAssets(
119
138
  pluginOptions: PluginOptions,
120
139
  appDirectory: string,
121
140
  assetPrefix: AssetPrefixInput = '/',
122
- routeChunkOptions?: Parameters<typeof getReactRouterManifestForDev>[5],
141
+ routeChunkOptions?: RouteChunkManifestOptions,
123
142
  options?: ModifyBrowserManifestOptions
124
143
  ): void {
125
144
  const getAssetPrefix =
@@ -128,119 +147,107 @@ export function registerModifyBrowserManifestAssets(
128
147
  options?.manifestChunkNames ??
129
148
  getReactRouterManifestChunkNames(
130
149
  routes,
150
+ appDirectory,
131
151
  routeChunkOptions?.splitRouteModules
132
152
  );
153
+ const isBuild = Boolean(routeChunkOptions?.isBuild);
133
154
  const finalizeSri = Boolean(
134
- routeChunkOptions?.isBuild &&
155
+ isBuild &&
135
156
  (options?.subResourceIntegrity ??
136
157
  options?.future?.unstable_subResourceIntegrity)
137
158
  );
138
- const generatedManifests = finalizeSri
139
- ? new WeakMap<Rspack.Compilation, GeneratedManifest>()
140
- : undefined;
141
-
142
- api.processAssets(
143
- { stage: 'additions', environments: ['web'] },
144
- async ({ assets, sources, compilation }) => {
145
- const currentAssetPrefix = getAssetPrefix();
146
- const stats = createReactRouterManifestStats(
147
- compilation,
148
- manifestChunkNames
159
+
160
+ // Build the React Router manifest from the compilation's current asset names,
161
+ // emit the build-mode `manifest-<version>.js` asset (or replace the dev
162
+ // browser-manifest placeholder), and fire the `onManifest` callback.
163
+ const buildAndEmitManifest = async (
164
+ { assets, sources, compilation }: ManifestProcessAssetsContext,
165
+ { withSri }: { withSri: boolean }
166
+ ): Promise<void> => {
167
+ const currentAssetPrefix = getAssetPrefix();
168
+ const stats = createReactRouterManifestStats(
169
+ compilation,
170
+ manifestChunkNames
171
+ );
172
+ const { manifest, moduleExportsByRouteId } =
173
+ await generateReactRouterManifestForDev(
174
+ routes,
175
+ pluginOptions,
176
+ stats,
177
+ appDirectory,
178
+ currentAssetPrefix,
179
+ createReactRouterManifestOptions({
180
+ routeChunks: routeChunkOptions,
181
+ routeModuleAnalysis: options?.routeModuleAnalysis,
182
+ })
149
183
  );
150
- const { manifest, moduleExportsByRouteId } =
151
- await generateReactRouterManifestForDev(
152
- routes,
153
- pluginOptions,
154
- stats,
155
- appDirectory,
156
- currentAssetPrefix,
157
- routeChunkOptions
158
- );
159
- const manifestForBrowser = finalizeSri
160
- ? { ...manifest, sri: true as const }
161
- : manifest;
162
-
163
- const browserManifestAsset = assets[BROWSER_MANIFEST_ASSET];
164
- if (browserManifestAsset) {
165
- const originalSource = browserManifestAsset.source().toString();
166
- const serializedManifest = jsesc(manifestForBrowser, { es6: true });
167
- const newSource = originalSource.replace(
168
- /["'`]PLACEHOLDER["'`]/,
169
- () => serializedManifest
170
- );
171
- compilation.updateAsset(
172
- BROWSER_MANIFEST_ASSET,
173
- new sources.RawSource(newSource)
174
- );
175
- }
184
+ const browserManifest = { ...manifest };
185
+ delete browserManifest.sri;
186
+
187
+ const browserManifestAsset = assets[BROWSER_MANIFEST_ASSET];
188
+ if (browserManifestAsset) {
189
+ const originalSource = browserManifestAsset.source().toString();
190
+ const serializedManifest = jsesc(browserManifest, { es6: true });
191
+ const newSource = originalSource.replace(
192
+ /["'`]PLACEHOLDER["'`]/,
193
+ () => serializedManifest
194
+ );
195
+ compilation.updateAsset(
196
+ BROWSER_MANIFEST_ASSET,
197
+ new sources.RawSource(newSource)
198
+ );
199
+ }
176
200
 
177
- if (routeChunkOptions?.isBuild) {
178
- const entryAssets = stats?.assetsByChunkName?.['entry.client'];
179
- const entryJsAssets =
180
- entryAssets?.filter(asset => asset.endsWith('.js')) || [];
181
- const manifestPath = getReactRouterManifestPath({
182
- version: manifest.version,
183
- isBuild: true,
184
- entryModulePath: entryJsAssets[0],
185
- });
186
- const manifestSource = `window.__reactRouterManifest=${jsesc(
187
- manifestForBrowser,
188
- { es6: true }
189
- )};`;
190
- const source = new sources.RawSource(manifestSource);
191
- if (compilation.getAsset(manifestPath)) {
192
- compilation.updateAsset(manifestPath, source);
193
- } else {
194
- compilation.emitAsset(manifestPath, source);
195
- }
201
+ if (isBuild) {
202
+ const entryAssets = stats?.assetsByChunkName?.['entry.client'];
203
+ const entryJsAssets =
204
+ entryAssets?.filter(asset => asset.endsWith('.js')) || [];
205
+ const manifestPath = getReactRouterManifestPath({
206
+ version: manifest.version,
207
+ isBuild: true,
208
+ entryModulePath: entryJsAssets[0],
209
+ });
210
+ const manifestSource = `window.__reactRouterManifest=${jsesc(
211
+ browserManifest,
212
+ { es6: true }
213
+ )};`;
214
+ const source = new sources.RawSource(manifestSource);
215
+ if (compilation.getAsset(manifestPath)) {
216
+ compilation.updateAsset(manifestPath, source);
217
+ } else {
218
+ compilation.emitAsset(manifestPath, source);
196
219
  }
220
+ }
197
221
 
198
- if (generatedManifests) {
199
- generatedManifests.set(compilation, {
200
- manifest,
201
- moduleExportsByRouteId,
202
- manifestStats: stats,
203
- assetPrefix: currentAssetPrefix,
204
- });
205
- return;
206
- }
222
+ // Rspack's SRI stats are finalized by a later report-stage hook. Hash the
223
+ // finalized JavaScript sources here as a fallback, matching React Router's
224
+ // Vite integration. Browser manifests intentionally omit `sri`; hydration
225
+ // receives it from the server-rendered import map.
226
+ const sri = withSri
227
+ ? (collectSubresourceIntegrity(
228
+ compilation.getStats().toJson({
229
+ all: false,
230
+ assets: true,
231
+ }) as StatsWithIntegrity,
232
+ compilation,
233
+ currentAssetPrefix
234
+ ) ?? true)
235
+ : undefined;
236
+
237
+ options?.onManifest?.(manifest, sri, moduleExportsByRouteId, {
238
+ compilation,
239
+ manifestStats: stats,
240
+ });
241
+ };
207
242
 
208
- options?.onManifest?.(manifest, undefined, moduleExportsByRouteId, {
209
- compilation,
210
- manifestStats: stats,
211
- });
212
- }
243
+ // In production, Rspack renames content-hashed assets during
244
+ // `realContentHash` (PROCESS_ASSETS_STAGE_OPTIMIZE_HASH = 2500). Reading
245
+ // asset names before that stage yields pre-rename hashes that never appear
246
+ // in the output, so CSS/JS URLs in the manifest 404. In dev, CSS extraction
247
+ // can also attach entry CSS after `additions`. Defer manifest generation and
248
+ // emission to the `report` stage (PROCESS_ASSETS_STAGE_REPORT = 5000), which
249
+ // runs after the rename, late CSS attachment, and SRI integrity finalization.
250
+ api.processAssets({ stage: 'report', environments: ['web'] }, context =>
251
+ buildAndEmitManifest(context, { withSri: finalizeSri })
213
252
  );
214
-
215
- if (generatedManifests) {
216
- api.processAssets(
217
- { stage: 'report', environments: ['web'] },
218
- ({ compilation }) => {
219
- const generatedManifest = generatedManifests.get(compilation);
220
- if (!generatedManifest) {
221
- return;
222
- }
223
-
224
- generatedManifests.delete(compilation);
225
- const sri =
226
- collectSubresourceIntegrity(
227
- undefined,
228
- compilation,
229
- generatedManifest.assetPrefix
230
- ) ?? true;
231
- options?.onManifest?.(
232
- {
233
- ...generatedManifest.manifest,
234
- sri,
235
- },
236
- sri,
237
- generatedManifest.moduleExportsByRouteId,
238
- {
239
- compilation,
240
- manifestStats: generatedManifest.manifestStats,
241
- }
242
- );
243
- }
244
- );
245
- }
246
253
  }