rsbuild-plugin-react-router 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +55 -24
  2. package/dist/451.js +36 -20
  3. package/dist/build-manifest.d.ts +6 -3
  4. package/dist/concurrency.d.ts +2 -1
  5. package/dist/config-imports.d.ts +7 -0
  6. package/dist/dev-background-resources.d.ts +38 -0
  7. package/dist/dev-generation.d.ts +2 -2
  8. package/dist/dev-runtime-artifacts.d.ts +9 -1
  9. package/dist/dev-runtime-compilation.d.ts +17 -2
  10. package/dist/dev-server.d.ts +5 -0
  11. package/dist/effect-runtime.d.ts +18 -0
  12. package/dist/export-utils.d.ts +2 -2
  13. package/dist/index.cjs +11448 -891
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +7950 -758
  16. package/dist/lazy-compilation-prewarm.d.ts +25 -0
  17. package/dist/lazy-compilation.d.ts +2 -1
  18. package/dist/manifest.d.ts +19 -4
  19. package/dist/parallel-route-transforms.d.ts +17 -2
  20. package/dist/prerender-build.d.ts +4 -0
  21. package/dist/prerender.d.ts +0 -1
  22. package/dist/react-router-config.d.ts +8 -5
  23. package/dist/server-build-resolution.d.ts +3 -0
  24. package/dist/ssr-externals.d.ts +1 -0
  25. package/dist/typegen.d.ts +14 -1
  26. package/dist/types.d.ts +15 -6
  27. package/package.json +11 -9
  28. package/src/build-manifest.ts +110 -73
  29. package/src/concurrency.ts +3 -22
  30. package/src/config-imports.ts +38 -0
  31. package/src/dev-background-resources.ts +255 -0
  32. package/src/dev-generation.ts +43 -53
  33. package/src/dev-runtime-artifacts.ts +50 -18
  34. package/src/dev-runtime-compilation.ts +80 -1
  35. package/src/dev-runtime-controller.ts +111 -31
  36. package/src/dev-runtime-session.ts +18 -11
  37. package/src/dev-server.ts +31 -1
  38. package/src/effect-runtime.ts +130 -0
  39. package/src/export-utils.ts +82 -23
  40. package/src/index.ts +118 -153
  41. package/src/lazy-compilation-prewarm.ts +279 -0
  42. package/src/lazy-compilation.ts +12 -5
  43. package/src/manifest.ts +366 -255
  44. package/src/modify-browser-manifest.ts +2 -1
  45. package/src/parallel-route-transforms.ts +195 -69
  46. package/src/prerender-build.ts +125 -61
  47. package/src/prerender.ts +0 -18
  48. package/src/react-router-config.ts +98 -73
  49. package/src/route-artifacts.ts +2 -2
  50. package/src/route-export-resolution.ts +3 -3
  51. package/src/route-watch.ts +119 -84
  52. package/src/server-build-resolution.ts +131 -0
  53. package/src/server-utils.ts +7 -106
  54. package/src/ssr-externals.ts +1 -1
  55. package/src/typegen.ts +162 -33
  56. package/src/types.ts +16 -6
package/src/manifest.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { dirname, isAbsolute, relative, resolve } from 'pathe';
3
+ import * as Effect from 'effect/Effect';
3
4
  import type { Route, PluginOptions, RouteManifestItem } from './types.js';
4
- import type { RouteConfigEntry } from '@react-router/dev/routes';
5
5
  import { combineURLs, createRouteId } from './plugin-utils.js';
6
6
  import { SERVER_EXPORTS, CLIENT_EXPORTS } from './constants.js';
7
7
  import {
@@ -15,12 +15,24 @@ import {
15
15
  type RouteChunkConfig,
16
16
  } from './route-chunks.js';
17
17
  import { getRouteModuleAnalysis } from './export-utils.js';
18
- import { getDefaultConcurrency, mapWithConcurrency } from './concurrency.js';
19
-
20
- const ROUTE_ANALYSIS_CONCURRENCY = Math.max(
21
- 1,
22
- Math.min(16, getDefaultConcurrency() || 1)
23
- );
18
+ import { getCappedPluginConcurrency } from './concurrency.js';
19
+ import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
20
+
21
+ const ROUTE_ANALYSIS_CONCURRENCY = getCappedPluginConcurrency();
22
+
23
+ /**
24
+ * Structural equivalent of `RouteConfigEntry` from `@react-router/dev/routes`,
25
+ * inlined so the public declaration files do not reference a devDependency
26
+ * (which would fail to resolve for consumers with `skipLibCheck: false`).
27
+ */
28
+ export interface RouteConfigEntry {
29
+ id?: string;
30
+ path?: string;
31
+ index?: boolean;
32
+ caseSensitive?: boolean;
33
+ file: string;
34
+ children?: RouteConfigEntry[];
35
+ }
24
36
 
25
37
  export function configRoutesToRouteManifest(
26
38
  appDirectory: string,
@@ -110,22 +122,13 @@ type ReactRouterManifestStatsEntrypoint = {
110
122
  getFiles?: () => Iterable<string>;
111
123
  };
112
124
 
113
- type ReactRouterManifestStatsCompilation = {
114
- namedChunks: Iterable<[string, ReactRouterManifestStatsChunk]>;
115
- entrypoints?: Iterable<[string, ReactRouterManifestStatsEntrypoint]>;
125
+ type ReactRouterManifestStatsLookup<T> = Iterable<[string, T]> & {
126
+ get?: (name: string) => T | undefined;
116
127
  };
117
128
 
118
- type ReactRouterManifestStatsNamedChunks =
119
- ReactRouterManifestStatsCompilation['namedChunks'] & {
120
- get?: (chunkName: string) => ReactRouterManifestStatsChunk | undefined;
121
- };
122
-
123
- type ReactRouterManifestStatsEntrypoints = NonNullable<
124
- ReactRouterManifestStatsCompilation['entrypoints']
125
- > & {
126
- get?: (
127
- entrypointName: string
128
- ) => ReactRouterManifestStatsEntrypoint | undefined;
129
+ type ReactRouterManifestStatsCompilation = {
130
+ namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
131
+ entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
129
132
  };
130
133
 
131
134
  const orderChunkFiles = (chunkName: string, files: string[]): string[] => {
@@ -142,64 +145,71 @@ const orderChunkFiles = (chunkName: string, files: string[]): string[] => {
142
145
  ];
143
146
  };
144
147
 
145
- export const createReactRouterManifestStats = (
146
- compilation: ReactRouterManifestStatsCompilation | undefined,
147
- chunkNames?: ReadonlySet<string>
148
- ): ReactRouterManifestStats | undefined => {
149
- if (!compilation) {
150
- return undefined;
148
+ const collectManifestFilesByName = <T>(
149
+ items: ReactRouterManifestStatsLookup<T>,
150
+ names: ReadonlySet<string> | undefined,
151
+ getFiles: (name: string, item: T) => string[]
152
+ ): Record<string, string[]> => {
153
+ const filesByName: Record<string, string[]> = {};
154
+ if (!names) {
155
+ for (const [name, item] of items) {
156
+ filesByName[name] = getFiles(name, item);
157
+ }
158
+ return filesByName;
151
159
  }
152
160
 
153
- const assetsByChunkName: Record<string, string[]> = {};
154
- const entrypointFilesByName: Record<string, string[]> = {};
155
- const namedChunks =
156
- compilation.namedChunks as ReactRouterManifestStatsNamedChunks;
157
- const entrypoints = compilation.entrypoints as
158
- | ReactRouterManifestStatsEntrypoints
159
- | undefined;
160
-
161
- if (chunkNames && typeof namedChunks.get === 'function') {
162
- for (const chunkName of chunkNames) {
163
- const chunk = namedChunks.get(chunkName);
164
- if (!chunk) {
161
+ const missingNames = new Set(names);
162
+ if (typeof items.get === 'function') {
163
+ for (const name of names) {
164
+ const item = items.get(name);
165
+ if (!item) {
165
166
  continue;
166
167
  }
167
- const files = Array.from(chunk.files ?? []);
168
- assetsByChunkName[chunkName] = orderChunkFiles(chunkName, files);
169
- }
170
- } else {
171
- for (const [chunkName, chunk] of namedChunks) {
172
- if (chunkNames && !chunkNames.has(chunkName)) {
173
- continue;
174
- }
175
- const files = Array.from(chunk.files ?? []);
176
- assetsByChunkName[chunkName] = orderChunkFiles(chunkName, files);
168
+ filesByName[name] = getFiles(name, item);
169
+ missingNames.delete(name);
177
170
  }
178
171
  }
179
172
 
180
- if (entrypoints) {
181
- if (chunkNames && typeof entrypoints.get === 'function') {
182
- for (const entrypointName of chunkNames) {
183
- const entrypoint = entrypoints.get(entrypointName);
184
- if (!entrypoint) {
185
- continue;
186
- }
187
- entrypointFilesByName[entrypointName] = Array.from(
188
- entrypoint.getFiles?.() ?? []
189
- );
190
- }
191
- } else {
192
- for (const [entrypointName, entrypoint] of entrypoints) {
193
- if (chunkNames && !chunkNames.has(entrypointName)) {
194
- continue;
195
- }
196
- entrypointFilesByName[entrypointName] = Array.from(
197
- entrypoint.getFiles?.() ?? []
198
- );
199
- }
173
+ if (missingNames.size === 0) {
174
+ return filesByName;
175
+ }
176
+
177
+ for (const [name, item] of items) {
178
+ if (!missingNames.has(name)) {
179
+ continue;
180
+ }
181
+ filesByName[name] = getFiles(name, item);
182
+ missingNames.delete(name);
183
+ if (missingNames.size === 0) {
184
+ break;
200
185
  }
201
186
  }
202
187
 
188
+ return filesByName;
189
+ };
190
+
191
+ export const createReactRouterManifestStats = (
192
+ compilation: ReactRouterManifestStatsCompilation | undefined,
193
+ chunkNames?: ReadonlySet<string>
194
+ ): ReactRouterManifestStats | undefined => {
195
+ if (!compilation) {
196
+ return undefined;
197
+ }
198
+
199
+ const assetsByChunkName = collectManifestFilesByName(
200
+ compilation.namedChunks,
201
+ chunkNames,
202
+ (chunkName, chunk) =>
203
+ orderChunkFiles(chunkName, Array.from(chunk.files ?? []))
204
+ );
205
+ const entrypointFilesByName = compilation.entrypoints
206
+ ? collectManifestFilesByName(
207
+ compilation.entrypoints,
208
+ chunkNames,
209
+ (_name, entrypoint) => Array.from(entrypoint.getFiles?.() ?? [])
210
+ )
211
+ : {};
212
+
203
213
  return Object.keys(entrypointFilesByName).length > 0
204
214
  ? { assetsByChunkName, entrypointFilesByName }
205
215
  : { assetsByChunkName };
@@ -212,7 +222,122 @@ export type ReactRouterManifestGenerationResult = {
212
222
  moduleExportsByRouteId: RouteManifestModuleExports;
213
223
  };
214
224
 
225
+ type ChunkAssets = {
226
+ js: string[];
227
+ css: string[];
228
+ };
229
+
230
+ type RouteManifestAnalysis = {
231
+ cssAssets: string[];
232
+ exports: Set<string>;
233
+ routeModuleExports: readonly string[];
234
+ hasRouteChunkByExportName: ReturnType<
235
+ typeof createEmptyRouteChunkByExportName
236
+ > | null;
237
+ };
238
+
215
239
  const DEFAULT_MANIFEST_DIR = 'static/js';
240
+ const CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/;
241
+
242
+ const createChunkAssetResolver = (
243
+ clientStats: ReactRouterManifestStats | undefined
244
+ ): ((chunkName: string) => ChunkAssets) => {
245
+ const chunkAssetsByName = new Map<string, ChunkAssets>();
246
+
247
+ return (chunkName: string): ChunkAssets => {
248
+ const cached = chunkAssetsByName.get(chunkName);
249
+ if (cached) {
250
+ return cached;
251
+ }
252
+
253
+ const assets = clientStats?.assetsByChunkName?.[chunkName];
254
+ if (!assets) {
255
+ const fallback = `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`;
256
+ const result = { js: [fallback], css: [] };
257
+ chunkAssetsByName.set(chunkName, result);
258
+ return result;
259
+ }
260
+
261
+ const cssAssets = new Set<string>();
262
+ const jsAssets: string[] = [];
263
+ for (const asset of assets) {
264
+ if (asset.endsWith('.css')) {
265
+ cssAssets.add(asset);
266
+ } else if (asset.endsWith('.js')) {
267
+ jsAssets.push(asset);
268
+ }
269
+ }
270
+ for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
271
+ if (asset.endsWith('.css')) {
272
+ cssAssets.add(asset);
273
+ }
274
+ }
275
+ if (jsAssets.length === 0) {
276
+ jsAssets.push(`${DEFAULT_MANIFEST_DIR}/${chunkName}.js`);
277
+ }
278
+
279
+ const result = { js: jsAssets, css: [...cssAssets] };
280
+ chunkAssetsByName.set(chunkName, result);
281
+ return result;
282
+ };
283
+ };
284
+
285
+ const analyzeRouteForManifestEffect = ({
286
+ discoveredCssAssets,
287
+ isBuild,
288
+ routeChunkCache,
289
+ routeChunkConfig,
290
+ routeEntryName,
291
+ routeFilePath,
292
+ }: {
293
+ discoveredCssAssets: string[];
294
+ isBuild: boolean;
295
+ routeChunkCache: RouteChunkCache | undefined;
296
+ routeChunkConfig: RouteChunkConfig | null;
297
+ routeEntryName: string;
298
+ routeFilePath: string;
299
+ }): Effect.Effect<RouteManifestAnalysis, Error, never> =>
300
+ tryPluginPromise(async () => {
301
+ const { code, exports: exportNames } =
302
+ await getRouteModuleAnalysis(routeFilePath);
303
+ const cssAssets =
304
+ !isBuild && discoveredCssAssets.length === 0 && CSS_IMPORT_RE.test(code)
305
+ ? [
306
+ `${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`,
307
+ ]
308
+ : discoveredCssAssets;
309
+ const chunkInfo =
310
+ isBuild && routeChunkConfig
311
+ ? await detectRouteChunksIfEnabled(
312
+ routeChunkCache,
313
+ routeChunkConfig,
314
+ routeFilePath,
315
+ code
316
+ )
317
+ : null;
318
+
319
+ return {
320
+ cssAssets,
321
+ exports: new Set(exportNames),
322
+ routeModuleExports: exportNames,
323
+ hasRouteChunkByExportName: chunkInfo?.hasRouteChunkByExportName ?? null,
324
+ };
325
+ }).pipe(
326
+ Effect.catchAll(error => {
327
+ if (isBuild) {
328
+ return Effect.fail(error);
329
+ }
330
+ return Effect.sync(() => {
331
+ console.error(`Failed to analyze route file ${routeFilePath}:`, error);
332
+ return {
333
+ cssAssets: discoveredCssAssets,
334
+ exports: new Set<string>(),
335
+ routeModuleExports: [],
336
+ hasRouteChunkByExportName: null,
337
+ };
338
+ });
339
+ })
340
+ );
216
341
 
217
342
  const getManifestDirFromEntryAsset = (entryModulePath?: string): string => {
218
343
  if (!entryModulePath) {
@@ -273,204 +398,190 @@ export const getReactRouterManifestChunkNames = (
273
398
  return chunkNames;
274
399
  };
275
400
 
276
- export async function generateReactRouterManifestForDev(
401
+ function generateReactRouterManifestForDevEffect(
277
402
  routes: Record<string, Route>,
278
403
  _options: PluginOptions,
279
404
  clientStats: ReactRouterManifestStats | undefined,
280
405
  context: string,
281
- assetPrefix = '/',
406
+ assetPrefix: string,
282
407
  routeChunkOptions?: RouteChunkManifestOptions
283
- ): Promise<ReactRouterManifestGenerationResult> {
284
- const result: Record<string, RouteManifestItem> = {};
285
- const splitRouteModules = routeChunkOptions?.splitRouteModules ?? false;
286
- const enforceSplitRouteModules = splitRouteModules === 'enforce';
287
- const isBuild = routeChunkOptions?.isBuild ?? false;
288
- const routeChunkConfig: RouteChunkConfig | null =
289
- splitRouteModules && routeChunkOptions?.rootRouteFile
290
- ? {
291
- splitRouteModules,
292
- appDirectory: context,
293
- rootRouteFile: routeChunkOptions.rootRouteFile,
294
- }
295
- : null;
296
-
297
- const getAssetsForChunk = (chunkName: string): string[] => {
298
- const assets = clientStats?.assetsByChunkName?.[chunkName];
299
- if (!assets) {
300
- return [`${DEFAULT_MANIFEST_DIR}/${chunkName}.js`];
301
- }
302
- const entrypointCssAssets =
303
- clientStats?.entrypointFilesByName?.[chunkName]?.filter(asset =>
304
- asset.endsWith('.css')
305
- ) ?? [];
306
- const cssAssets = [
307
- ...assets.filter(asset => asset.endsWith('.css')),
308
- ...entrypointCssAssets,
309
- ].filter((asset, index, all) => all.indexOf(asset) === index);
310
- const nonCssAssets = assets.filter(asset => !asset.endsWith('.css'));
311
-
312
- if (!nonCssAssets.some(asset => asset.endsWith('.js'))) {
313
- return [
314
- `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`,
315
- ...nonCssAssets,
316
- ...cssAssets,
317
- ];
318
- }
319
- return [...nonCssAssets, ...cssAssets];
320
- };
321
-
322
- const getModulePathForChunk = (chunkName: string): string | undefined => {
323
- const assets = getAssetsForChunk(chunkName);
324
- const jsAssets = assets.filter(asset => asset.endsWith('.js'));
325
- return jsAssets[0] ? combineURLs(assetPrefix, jsAssets[0]) : undefined;
326
- };
327
-
328
- const manifestEntries = await mapWithConcurrency(
329
- Object.entries(routes),
330
- ROUTE_ANALYSIS_CONCURRENCY,
331
- async ([key, route]) => {
332
- const routeEntryName = getRouteEntryName(route);
333
- const assets = getAssetsForChunk(routeEntryName);
334
- const jsAssets = assets.filter(asset => asset.endsWith('.js'));
335
- let cssAssets = assets.filter(asset => asset.endsWith('.css'));
336
- const routeFilePath = resolve(context, route.file);
337
- let exports = new Set<string>();
338
- let routeModuleExports: readonly string[] = [];
339
- let hasRouteChunkByExportName: ReturnType<
340
- typeof createEmptyRouteChunkByExportName
341
- > | null = null;
342
-
343
- try {
344
- const { code, exports: exportNames } =
345
- await getRouteModuleAnalysis(routeFilePath);
346
- if (
347
- !isBuild &&
348
- cssAssets.length === 0 &&
349
- /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/.test(code)
350
- ) {
351
- cssAssets = [
352
- `${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`,
353
- ];
354
- }
355
- routeModuleExports = exportNames;
356
- exports = new Set(exportNames);
357
-
358
- if (isBuild && routeChunkConfig) {
359
- const { hasRouteChunkByExportName: chunkInfo } =
360
- await detectRouteChunksIfEnabled(
361
- routeChunkOptions?.cache,
362
- routeChunkConfig,
363
- routeFilePath,
364
- code
365
- );
366
- hasRouteChunkByExportName = chunkInfo;
367
- }
368
- } catch (error) {
369
- if (isBuild) {
370
- throw error;
371
- }
372
- console.error(`Failed to analyze route file ${routeFilePath}:`, error);
373
- }
408
+ ): Effect.Effect<ReactRouterManifestGenerationResult, Error, never> {
409
+ return Effect.gen(function* () {
410
+ const result: Record<string, RouteManifestItem> = {};
411
+ const splitRouteModules = routeChunkOptions?.splitRouteModules ?? false;
412
+ const enforceSplitRouteModules = splitRouteModules === 'enforce';
413
+ const isBuild = routeChunkOptions?.isBuild ?? false;
414
+ const routeChunkConfig: RouteChunkConfig | null =
415
+ splitRouteModules && routeChunkOptions?.rootRouteFile
416
+ ? {
417
+ splitRouteModules,
418
+ appDirectory: context,
419
+ rootRouteFile: routeChunkOptions.rootRouteFile,
420
+ }
421
+ : null;
422
+
423
+ const getAssetsForChunk = createChunkAssetResolver(clientStats);
424
+ const getModulePathForChunk = (chunkName: string): string | undefined => {
425
+ const { js: jsAssets } = getAssetsForChunk(chunkName);
426
+ return jsAssets[0] ? combineURLs(assetPrefix, jsAssets[0]) : undefined;
427
+ };
374
428
 
375
- const hasClientAction = exports.has(CLIENT_EXPORTS.clientAction);
376
- const hasClientLoader = exports.has(CLIENT_EXPORTS.clientLoader);
377
- const hasClientMiddleware = exports.has(CLIENT_EXPORTS.clientMiddleware);
378
- const hasDefaultExport = exports.has('default');
379
- const routeChunkMap = hasRouteChunkByExportName;
380
-
381
- if (isBuild && enforceSplitRouteModules && routeChunkConfig) {
382
- validateRouteChunks({
383
- config: routeChunkConfig,
384
- id: routeFilePath,
385
- valid: buildManifestChunkValidity(
386
- exports,
387
- routeChunkMap ?? createEmptyRouteChunkByExportName()
388
- ),
389
- });
429
+ const manifestEntries = yield* Effect.forEach(
430
+ Object.entries(routes),
431
+ ([key, route]) =>
432
+ Effect.gen(function* () {
433
+ const routeEntryName = getRouteEntryName(route);
434
+ const { js: jsAssets, css: discoveredCssAssets } =
435
+ getAssetsForChunk(routeEntryName);
436
+ const routeFilePath = resolve(context, route.file);
437
+ const routeAnalysis = yield* analyzeRouteForManifestEffect({
438
+ discoveredCssAssets,
439
+ isBuild,
440
+ routeChunkCache: routeChunkOptions?.cache,
441
+ routeChunkConfig,
442
+ routeEntryName,
443
+ routeFilePath,
444
+ });
445
+
446
+ const hasClientAction = routeAnalysis.exports.has(
447
+ CLIENT_EXPORTS.clientAction
448
+ );
449
+ const hasClientLoader = routeAnalysis.exports.has(
450
+ CLIENT_EXPORTS.clientLoader
451
+ );
452
+ const hasClientMiddleware = routeAnalysis.exports.has(
453
+ CLIENT_EXPORTS.clientMiddleware
454
+ );
455
+ const hasDefaultExport = routeAnalysis.exports.has('default');
456
+ const routeChunkMap = routeAnalysis.hasRouteChunkByExportName;
457
+
458
+ if (isBuild && enforceSplitRouteModules && routeChunkConfig) {
459
+ validateRouteChunks({
460
+ config: routeChunkConfig,
461
+ id: routeFilePath,
462
+ valid: buildManifestChunkValidity(
463
+ routeAnalysis.exports,
464
+ routeChunkMap ?? createEmptyRouteChunkByExportName()
465
+ ),
466
+ });
467
+ }
468
+
469
+ return [
470
+ key,
471
+ {
472
+ id: route.id,
473
+ parentId: route.parentId,
474
+ path: route.path,
475
+ index: route.index,
476
+ caseSensitive: route.caseSensitive,
477
+ module: combineURLs(assetPrefix, jsAssets[0] || ''),
478
+ clientActionModule: routeChunkMap?.clientAction
479
+ ? getModulePathForChunk(
480
+ getRouteChunkEntryName(route.id, 'clientAction')
481
+ )
482
+ : undefined,
483
+ clientLoaderModule: routeChunkMap?.clientLoader
484
+ ? getModulePathForChunk(
485
+ getRouteChunkEntryName(route.id, 'clientLoader')
486
+ )
487
+ : undefined,
488
+ clientMiddlewareModule: routeChunkMap?.clientMiddleware
489
+ ? getModulePathForChunk(
490
+ getRouteChunkEntryName(route.id, 'clientMiddleware')
491
+ )
492
+ : undefined,
493
+ hydrateFallbackModule: routeChunkMap?.HydrateFallback
494
+ ? getModulePathForChunk(
495
+ getRouteChunkEntryName(route.id, 'HydrateFallback')
496
+ )
497
+ : undefined,
498
+ hasAction: routeAnalysis.exports.has(SERVER_EXPORTS.action),
499
+ hasLoader: routeAnalysis.exports.has(SERVER_EXPORTS.loader),
500
+ hasClientAction,
501
+ hasClientLoader,
502
+ hasClientMiddleware,
503
+ hasDefaultExport,
504
+ hasErrorBoundary: routeAnalysis.exports.has(
505
+ CLIENT_EXPORTS.ErrorBoundary
506
+ ),
507
+ imports: jsAssets.map(asset => combineURLs(assetPrefix, asset)),
508
+ css: routeAnalysis.cssAssets.map(asset =>
509
+ combineURLs(assetPrefix, asset)
510
+ ),
511
+ },
512
+ routeAnalysis.routeModuleExports,
513
+ ] as const;
514
+ }),
515
+ {
516
+ concurrency: Math.max(
517
+ 1,
518
+ Math.min(ROUTE_ANALYSIS_CONCURRENCY, Object.keys(routes).length)
519
+ ),
390
520
  }
391
-
392
- return [
393
- key,
394
- {
395
- id: route.id,
396
- parentId: route.parentId,
397
- path: route.path,
398
- index: route.index,
399
- caseSensitive: route.caseSensitive,
400
- module: combineURLs(assetPrefix, jsAssets[0] || ''),
401
- clientActionModule: routeChunkMap?.clientAction
402
- ? getModulePathForChunk(
403
- getRouteChunkEntryName(route.id, 'clientAction')
404
- )
405
- : undefined,
406
- clientLoaderModule: routeChunkMap?.clientLoader
407
- ? getModulePathForChunk(
408
- getRouteChunkEntryName(route.id, 'clientLoader')
409
- )
410
- : undefined,
411
- clientMiddlewareModule: routeChunkMap?.clientMiddleware
412
- ? getModulePathForChunk(
413
- getRouteChunkEntryName(route.id, 'clientMiddleware')
414
- )
415
- : undefined,
416
- hydrateFallbackModule: routeChunkMap?.HydrateFallback
417
- ? getModulePathForChunk(
418
- getRouteChunkEntryName(route.id, 'HydrateFallback')
419
- )
420
- : undefined,
421
- hasAction: exports.has(SERVER_EXPORTS.action),
422
- hasLoader: exports.has(SERVER_EXPORTS.loader),
423
- hasClientAction,
424
- hasClientLoader,
425
- hasClientMiddleware,
426
- hasDefaultExport,
427
- hasErrorBoundary: exports.has(CLIENT_EXPORTS.ErrorBoundary),
428
- imports: jsAssets.map(asset => combineURLs(assetPrefix, asset)),
429
- css: cssAssets.map(asset => combineURLs(assetPrefix, asset)),
430
- },
431
- routeModuleExports,
432
- ] as const;
521
+ );
522
+
523
+ const routeModuleExportsByRouteId: RouteManifestModuleExports = {};
524
+ for (const [
525
+ key,
526
+ routeManifestItem,
527
+ routeModuleExports,
528
+ ] of manifestEntries) {
529
+ result[key] = routeManifestItem;
530
+ routeModuleExportsByRouteId[key] = routeModuleExports;
433
531
  }
434
- );
435
532
 
436
- const routeModuleExportsByRouteId: RouteManifestModuleExports = {};
437
- for (const [key, routeManifestItem, routeModuleExports] of manifestEntries) {
438
- result[key] = routeManifestItem;
439
- routeModuleExportsByRouteId[key] = routeModuleExports;
440
- }
533
+ const { js: entryJsAssets, css: entryCssAssets } =
534
+ getAssetsForChunk('entry.client');
441
535
 
442
- const entryAssets = getAssetsForChunk('entry.client');
443
- const entryJsAssets = entryAssets.filter(asset => asset.endsWith('.js'));
444
- const entryCssAssets = entryAssets.filter(asset => asset.endsWith('.css'));
445
-
446
- const fingerprintedValues = {
447
- entry: {
448
- module: combineURLs(assetPrefix, entryJsAssets[0] || ''),
449
- imports: entryJsAssets.map(asset => combineURLs(assetPrefix, asset)),
450
- css: entryCssAssets.map(asset => combineURLs(assetPrefix, asset)),
451
- },
452
- routes: result,
453
- };
454
- const version = getManifestVersion(fingerprintedValues, isBuild);
455
- const manifestPath = getReactRouterManifestPath({
456
- version,
457
- isBuild,
458
- entryModulePath: entryJsAssets[0],
459
- });
536
+ const fingerprintedValues = {
537
+ entry: {
538
+ module: combineURLs(assetPrefix, entryJsAssets[0] || ''),
539
+ imports: entryJsAssets.map(asset => combineURLs(assetPrefix, asset)),
540
+ css: entryCssAssets.map(asset => combineURLs(assetPrefix, asset)),
541
+ },
542
+ routes: result,
543
+ };
544
+ const version = getManifestVersion(fingerprintedValues, isBuild);
545
+ const manifestPath = getReactRouterManifestPath({
546
+ version,
547
+ isBuild,
548
+ entryModulePath: entryJsAssets[0],
549
+ });
550
+
551
+ const manifest = {
552
+ version,
553
+ url: combineURLs(assetPrefix, manifestPath),
554
+ hmr: undefined,
555
+ entry: fingerprintedValues.entry,
556
+ sri: undefined,
557
+ routes: result,
558
+ };
460
559
 
461
- const manifest = {
462
- version,
463
- url: combineURLs(assetPrefix, manifestPath),
464
- hmr: undefined,
465
- entry: fingerprintedValues.entry,
466
- sri: undefined,
467
- routes: result,
468
- };
560
+ return {
561
+ manifest,
562
+ moduleExportsByRouteId: routeModuleExportsByRouteId,
563
+ };
564
+ });
565
+ }
469
566
 
470
- return {
471
- manifest,
472
- moduleExportsByRouteId: routeModuleExportsByRouteId,
473
- };
567
+ export async function generateReactRouterManifestForDev(
568
+ routes: Record<string, Route>,
569
+ options: PluginOptions,
570
+ clientStats: ReactRouterManifestStats | undefined,
571
+ context: string,
572
+ assetPrefix = '/',
573
+ routeChunkOptions?: RouteChunkManifestOptions
574
+ ): Promise<ReactRouterManifestGenerationResult> {
575
+ return runPluginEffect(
576
+ generateReactRouterManifestForDevEffect(
577
+ routes,
578
+ options,
579
+ clientStats,
580
+ context,
581
+ assetPrefix,
582
+ routeChunkOptions
583
+ )
584
+ );
474
585
  }
475
586
 
476
587
  export async function getReactRouterManifestForDev(