rsbuild-plugin-react-router 0.5.0 → 0.6.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 (110) hide show
  1. package/README.md +58 -208
  2. package/dist/511.js +370 -331
  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 +3957 -2208
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +3470 -1793
  19. package/dist/lazy-compilation-prewarm.d.ts +8 -5
  20. package/dist/manifest.d.ts +16 -4
  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 +2 -1
  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 +253 -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 +157 -82
  77. package/src/mode-plan.ts +367 -0
  78. package/src/modify-browser-manifest.ts +130 -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 +167 -51
  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 =
@@ -130,117 +149,104 @@ export function registerModifyBrowserManifestAssets(
130
149
  routes,
131
150
  routeChunkOptions?.splitRouteModules
132
151
  );
152
+ const isBuild = Boolean(routeChunkOptions?.isBuild);
133
153
  const finalizeSri = Boolean(
134
- routeChunkOptions?.isBuild &&
154
+ isBuild &&
135
155
  (options?.subResourceIntegrity ??
136
156
  options?.future?.unstable_subResourceIntegrity)
137
157
  );
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
158
+
159
+ // Build the React Router manifest from the compilation's current asset names,
160
+ // emit the build-mode `manifest-<version>.js` asset (or replace the dev
161
+ // browser-manifest placeholder), and fire the `onManifest` callback.
162
+ const buildAndEmitManifest = async (
163
+ { assets, sources, compilation }: ManifestProcessAssetsContext,
164
+ { withSri }: { withSri: boolean }
165
+ ): Promise<void> => {
166
+ const currentAssetPrefix = getAssetPrefix();
167
+ const stats = createReactRouterManifestStats(
168
+ compilation,
169
+ manifestChunkNames
170
+ );
171
+ const { manifest, moduleExportsByRouteId } =
172
+ await generateReactRouterManifestForDev(
173
+ routes,
174
+ pluginOptions,
175
+ stats,
176
+ appDirectory,
177
+ currentAssetPrefix,
178
+ createReactRouterManifestOptions({
179
+ routeChunks: routeChunkOptions,
180
+ routeModuleAnalysis: options?.routeModuleAnalysis,
181
+ })
149
182
  );
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
- }
183
+ const browserManifest = { ...manifest };
184
+ delete browserManifest.sri;
185
+
186
+ const browserManifestAsset = assets[BROWSER_MANIFEST_ASSET];
187
+ if (browserManifestAsset) {
188
+ const originalSource = browserManifestAsset.source().toString();
189
+ const serializedManifest = jsesc(browserManifest, { es6: true });
190
+ const newSource = originalSource.replace(
191
+ /["'`]PLACEHOLDER["'`]/,
192
+ () => serializedManifest
193
+ );
194
+ compilation.updateAsset(
195
+ BROWSER_MANIFEST_ASSET,
196
+ new sources.RawSource(newSource)
197
+ );
198
+ }
176
199
 
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
- }
200
+ if (isBuild) {
201
+ const entryAssets = stats?.assetsByChunkName?.['entry.client'];
202
+ const entryJsAssets =
203
+ entryAssets?.filter(asset => asset.endsWith('.js')) || [];
204
+ const manifestPath = getReactRouterManifestPath({
205
+ version: manifest.version,
206
+ isBuild: true,
207
+ entryModulePath: entryJsAssets[0],
208
+ });
209
+ const manifestSource = `window.__reactRouterManifest=${jsesc(
210
+ browserManifest,
211
+ { es6: true }
212
+ )};`;
213
+ const source = new sources.RawSource(manifestSource);
214
+ if (compilation.getAsset(manifestPath)) {
215
+ compilation.updateAsset(manifestPath, source);
216
+ } else {
217
+ compilation.emitAsset(manifestPath, source);
196
218
  }
219
+ }
197
220
 
198
- if (generatedManifests) {
199
- generatedManifests.set(compilation, {
200
- manifest,
201
- moduleExportsByRouteId,
202
- manifestStats: stats,
203
- assetPrefix: currentAssetPrefix,
204
- });
205
- return;
206
- }
221
+ // Rspack's SRI stats are finalized by a later report-stage hook. Hash the
222
+ // finalized JavaScript sources here as a fallback, matching React Router's
223
+ // Vite integration. Browser manifests intentionally omit `sri`; hydration
224
+ // receives it from the server-rendered import map.
225
+ const sri = withSri
226
+ ? (collectSubresourceIntegrity(
227
+ compilation.getStats().toJson({
228
+ all: false,
229
+ assets: true,
230
+ }) as StatsWithIntegrity,
231
+ compilation,
232
+ currentAssetPrefix
233
+ ) ?? true)
234
+ : undefined;
235
+
236
+ options?.onManifest?.(manifest, sri, moduleExportsByRouteId, {
237
+ compilation,
238
+ manifestStats: stats,
239
+ });
240
+ };
207
241
 
208
- options?.onManifest?.(manifest, undefined, moduleExportsByRouteId, {
209
- compilation,
210
- manifestStats: stats,
211
- });
212
- }
242
+ // In production, Rspack renames content-hashed assets during
243
+ // `realContentHash` (PROCESS_ASSETS_STAGE_OPTIMIZE_HASH = 2500). Reading
244
+ // asset names before that stage yields pre-rename hashes that never appear
245
+ // in the output, so CSS/JS URLs in the manifest 404. In dev, CSS extraction
246
+ // can also attach entry CSS after `additions`. Defer manifest generation and
247
+ // emission to the `report` stage (PROCESS_ASSETS_STAGE_REPORT = 5000), which
248
+ // runs after the rename, late CSS attachment, and SRI integrity finalization.
249
+ api.processAssets({ stage: 'report', environments: ['web'] }, context =>
250
+ buildAndEmitManifest(context, { withSri: finalizeSri })
213
251
  );
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
252
  }