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
@@ -1,7 +1,71 @@
1
- import { normalize } from 'pathe';
2
- import { existsSync } from 'node:fs';
1
+ import { normalize, resolve } from 'pathe';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
3
4
  import { JS_EXTENSIONS } from './constants.js';
4
5
 
6
+ const requireFromApp = createRequire(resolve(process.cwd(), 'package.json'));
7
+
8
+ export const resolveAppPackagePath = (
9
+ specifier: string
10
+ ): string | undefined => {
11
+ try {
12
+ return requireFromApp.resolve(specifier);
13
+ } catch {
14
+ return undefined;
15
+ }
16
+ };
17
+
18
+ export const parseVersionMajorMinor = (
19
+ version: string | undefined
20
+ ): { major: number; minor: number } | undefined => {
21
+ const match = version?.match(/^(\d+)\.(\d+)\./);
22
+ if (!match) {
23
+ return undefined;
24
+ }
25
+ return { major: Number(match[1]), minor: Number(match[2]) };
26
+ };
27
+
28
+ const packageVersionCache = new Map<string, string | undefined>();
29
+
30
+ export const getPackageVersion = (
31
+ packageName: string,
32
+ resolvePackagePath: (
33
+ specifier: string
34
+ ) => string | undefined = resolveAppPackagePath
35
+ ): string | undefined => {
36
+ // Only default resolution is cached; injected resolvers (tests) stay live.
37
+ const cacheable = resolvePackagePath === resolveAppPackagePath;
38
+ if (cacheable && packageVersionCache.has(packageName)) {
39
+ return packageVersionCache.get(packageName);
40
+ }
41
+ const packageJsonPath = resolvePackagePath(`${packageName}/package.json`);
42
+ if (!packageJsonPath) {
43
+ return undefined;
44
+ }
45
+ try {
46
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
47
+ version?: unknown;
48
+ };
49
+ const version =
50
+ typeof packageJson.version === 'string' ? packageJson.version : undefined;
51
+ if (cacheable) {
52
+ packageVersionCache.set(packageName, version);
53
+ }
54
+ return version;
55
+ } catch {
56
+ if (cacheable) {
57
+ packageVersionCache.set(packageName, undefined);
58
+ }
59
+ return undefined;
60
+ }
61
+ };
62
+
63
+ export const escapeHtml = (value: string): string =>
64
+ value
65
+ .replaceAll('&', '&amp;')
66
+ .replaceAll('<', '&lt;')
67
+ .replaceAll('>', '&gt;');
68
+
5
69
  export function combineURLs(baseURL: string, relativeURL: string): string {
6
70
  return relativeURL
7
71
  ? `${baseURL.replace(/\/+$/, '')}/${relativeURL.replace(/^\/+/, '')}`
@@ -15,12 +79,38 @@ export function normalizeAssetPrefix(assetPrefix?: string): string {
15
79
  return assetPrefix.endsWith('/') ? assetPrefix : `${assetPrefix}/`;
16
80
  }
17
81
 
18
- export function stripFileExtension(file: string): string {
19
- return file.replace(/\.[^/.]+$/, '');
82
+ /**
83
+ * Resolve the asset prefix Rsbuild applies to emitted asset URLs for the given
84
+ * mode. In development the effective prefix is `dev.assetPrefix` (which Rsbuild
85
+ * defaults from `server.base`, falling back to `output.assetPrefix`); in a
86
+ * production build it is `output.assetPrefix`. Both fields are already resolved
87
+ * on the normalized config, so this mirrors Rsbuild's own precedence rather than
88
+ * re-deriving it from `server.base`.
89
+ *
90
+ * `dev.assetPrefix` may be a boolean on the raw config (`false` disables it);
91
+ * boolean/`'auto'`/empty values normalize to the root prefix `'/'`.
92
+ */
93
+ export function resolveEffectiveAssetPrefix(config: {
94
+ dev?: { assetPrefix?: unknown };
95
+ output?: { assetPrefix?: unknown };
96
+ isBuild: boolean;
97
+ }): string {
98
+ const outputPrefix =
99
+ typeof config.output?.assetPrefix === 'string'
100
+ ? config.output.assetPrefix
101
+ : undefined;
102
+ if (config.isBuild) {
103
+ return normalizeAssetPrefix(outputPrefix);
104
+ }
105
+ const devPrefix =
106
+ typeof config.dev?.assetPrefix === 'string'
107
+ ? config.dev.assetPrefix
108
+ : undefined;
109
+ return normalizeAssetPrefix(devPrefix ?? outputPrefix);
20
110
  }
21
111
 
22
112
  export function createRouteId(file: string): string {
23
- return normalize(stripFileExtension(file));
113
+ return normalize(file.replace(/\.[^/.]+$/, ''));
24
114
  }
25
115
 
26
116
  export function findEntryFile(basePath: string): string {
@@ -35,41 +125,15 @@ export function findEntryFile(basePath: string): string {
35
125
 
36
126
  export function generateWithProps() {
37
127
  return `
38
- import { createElement as h } from "react";
39
- import { useActionData, useLoaderData, useMatches, useParams, useRouteError } from "react-router";
128
+ import {
129
+ UNSAFE_withComponentProps,
130
+ UNSAFE_withErrorBoundaryProps,
131
+ UNSAFE_withHydrateFallbackProps,
132
+ } from "react-router";
40
133
 
41
- export function withComponentProps(Component) {
42
- return function Wrapped() {
43
- const props = {
44
- params: useParams(),
45
- loaderData: useLoaderData(),
46
- actionData: useActionData(),
47
- matches: useMatches(),
48
- };
49
- return h(Component, props);
50
- };
51
- }
52
-
53
- export function withHydrateFallbackProps(HydrateFallback) {
54
- return function Wrapped() {
55
- const props = {
56
- params: useParams(),
57
- };
58
- return h(HydrateFallback, props);
59
- };
60
- }
61
-
62
- export function withErrorBoundaryProps(ErrorBoundary) {
63
- return function Wrapped() {
64
- const props = {
65
- params: useParams(),
66
- loaderData: useLoaderData(),
67
- actionData: useActionData(),
68
- error: useRouteError(),
69
- };
70
- return h(ErrorBoundary, props);
71
- };
72
- }
134
+ export const withComponentProps = UNSAFE_withComponentProps;
135
+ export const withHydrateFallbackProps = UNSAFE_withHydrateFallbackProps;
136
+ export const withErrorBoundaryProps = UNSAFE_withErrorBoundaryProps;
73
137
  `;
74
138
  }
75
139
 
@@ -10,12 +10,15 @@ import {
10
10
  type ServerBuild,
11
11
  } from 'react-router';
12
12
  import { dirname, relative, resolve } from 'pathe';
13
- import { PLUGIN_NAME } from './constants.js';
13
+ import { PLUGIN_NAME, SPA_FALLBACK_HTML_FILE } from './constants.js';
14
14
  import { getBuildManifest } from './build-manifest.js';
15
15
  import {
16
+ createReactRouterManifestOptions,
16
17
  generateReactRouterManifestForDev,
17
- getReactRouterManifestForDev,
18
+ type ReactRouterManifestForDev as ReactRouterManifest,
18
19
  type ReactRouterManifestStats,
20
+ type RouteChunkManifestOptions,
21
+ type RouteModuleAnalysisProvider,
19
22
  type RouteManifestModuleExports,
20
23
  } from './manifest.js';
21
24
  import {
@@ -32,10 +35,6 @@ import { resolveServerBuildModule } from './server-utils.js';
32
35
  import type { PluginOptions, Route } from './types.js';
33
36
  import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
34
37
 
35
- type ReactRouterManifest = Awaited<
36
- ReturnType<typeof getReactRouterManifestForDev>
37
- >;
38
-
39
38
  type BuildRouteModule = {
40
39
  loader?: unknown;
41
40
  default?: unknown;
@@ -73,9 +72,10 @@ type RunReactRouterPrerenderBuildOptions = {
73
72
  pluginOptions: PluginOptions;
74
73
  appDirectory: string;
75
74
  assetPrefix: string;
76
- routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5];
75
+ routeChunkOptions: RouteChunkManifestOptions | undefined;
76
+ routeModuleAnalysis?: RouteModuleAnalysisProvider;
77
77
  buildManifest: Awaited<ReturnType<typeof getBuildManifest>>;
78
- resolvedConfigWithRoutes: ResolvedReactRouterConfig;
78
+ buildEndReactRouterConfig: ResolvedReactRouterConfig;
79
79
  buildEnd: Config['buildEnd'];
80
80
  };
81
81
 
@@ -160,7 +160,7 @@ export const withBuildRequest = <T>(
160
160
  handle: (request: Request) => Promise<T>
161
161
  ): Promise<T> => runPluginEffect(createBuildRequestEffect(input, init, handle));
162
162
 
163
- const prerenderData = async ({
163
+ const prerenderDataEffect = ({
164
164
  handler,
165
165
  prerenderPath,
166
166
  onlyRoutes,
@@ -178,7 +178,7 @@ const prerenderData = async ({
178
178
  trailingSlashAwareDataRequests: boolean;
179
179
  api: PrerenderBuildApi;
180
180
  requestInit?: RequestInit;
181
- }): Promise<string> => {
181
+ }) => {
182
182
  const dataOutputPath = createDataRequestPath(
183
183
  prerenderPath,
184
184
  trailingSlashAwareDataRequests
@@ -199,7 +199,7 @@ const prerenderData = async ({
199
199
  url.searchParams.set('_routes', onlyRoutes.join(','));
200
200
  }
201
201
 
202
- return withBuildRequest(url, requestInit, async request => {
202
+ return createBuildRequestEffect(url, requestInit, async request => {
203
203
  const response = await handler(request);
204
204
  const data = await response.text();
205
205
 
@@ -227,7 +227,7 @@ const prerenderData = async ({
227
227
  });
228
228
  };
229
229
 
230
- const prerenderRoute = async ({
230
+ const prerenderRouteEffect = ({
231
231
  handler,
232
232
  prerenderPath,
233
233
  clientBuildDir,
@@ -241,9 +241,9 @@ const prerenderRoute = async ({
241
241
  basename: string;
242
242
  api: PrerenderBuildApi;
243
243
  requestInit?: RequestInit;
244
- }): Promise<void> => {
244
+ }) => {
245
245
  const normalizedPath = `${basename}${prerenderPath}/`.replace(/\/\/+/g, '/');
246
- await withBuildRequest(
246
+ return createBuildRequestEffect(
247
247
  `http://localhost${normalizedPath}`,
248
248
  requestInit,
249
249
  async request => {
@@ -290,7 +290,7 @@ const prerenderRoute = async ({
290
290
  );
291
291
  };
292
292
 
293
- const prerenderResourceRoute = async ({
293
+ const prerenderResourceRouteEffect = ({
294
294
  handler,
295
295
  prerenderPath,
296
296
  clientBuildDir,
@@ -304,11 +304,11 @@ const prerenderResourceRoute = async ({
304
304
  basename: string;
305
305
  api: PrerenderBuildApi;
306
306
  requestInit?: RequestInit;
307
- }): Promise<void> => {
307
+ }) => {
308
308
  const normalizedPath = `${basename}${prerenderPath}/`
309
309
  .replace(/\/\/+/g, '/')
310
310
  .replace(/\/$/g, '');
311
- await withBuildRequest(
311
+ return createBuildRequestEffect(
312
312
  `http://localhost${normalizedPath}`,
313
313
  requestInit,
314
314
  async request => {
@@ -361,7 +361,7 @@ const handleSpaMode = async ({
361
361
  const html = await response.text();
362
362
  const isPrerenderSpaFallback = build.prerender?.includes('/');
363
363
  const filename = isPrerenderSpaFallback
364
- ? '__spa-fallback.html'
364
+ ? SPA_FALLBACK_HTML_FILE
365
365
  : 'index.html';
366
366
 
367
367
  if (response.status !== 200) {
@@ -374,7 +374,7 @@ const handleSpaMode = async ({
374
374
  }
375
375
  throw new Error(
376
376
  `SPA Mode: Received a ${response.status} status code from ` +
377
- `\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` +
377
+ '`entry.server.tsx` while prerendering your SPA Fallback HTML file.\n' +
378
378
  html
379
379
  );
380
380
  }
@@ -443,10 +443,10 @@ const validatePrerenderPathMatches = (
443
443
  }
444
444
  };
445
445
 
446
- export const createBoundedPrerenderTasksEffect = (
447
- prerenderPaths: string[],
446
+ export const createBoundedPrerenderTasksEffect = <T>(
447
+ prerenderPaths: T[],
448
448
  concurrency: number,
449
- renderPath: (path: string) => Effect.Effect<void, Error, never>
449
+ renderPath: (path: T) => Effect.Effect<void, Error, never>
450
450
  ): Effect.Effect<void, Error, never> =>
451
451
  Effect.forEach(prerenderPaths, renderPath, { concurrency, discard: true });
452
452
 
@@ -480,27 +480,23 @@ const createPrerenderPathEffect = ({
480
480
 
481
481
  if (isResourceRoute) {
482
482
  if (manifestRoute.loader && routeId) {
483
- yield* tryPluginPromise(() =>
484
- prerenderData({
485
- handler: requestHandler,
486
- prerenderPath: path,
487
- onlyRoutes: [routeId],
488
- clientBuildDir,
489
- basename,
490
- trailingSlashAwareDataRequests:
491
- future.unstable_trailingSlashAwareDataRequests,
492
- api,
493
- })
494
- );
495
- yield* tryPluginPromise(() =>
496
- prerenderResourceRoute({
497
- handler: requestHandler,
498
- prerenderPath: path,
499
- clientBuildDir,
500
- basename,
501
- api,
502
- })
503
- );
483
+ yield* prerenderDataEffect({
484
+ handler: requestHandler,
485
+ prerenderPath: path,
486
+ onlyRoutes: [routeId],
487
+ clientBuildDir,
488
+ basename,
489
+ trailingSlashAwareDataRequests:
490
+ future.unstable_trailingSlashAwareDataRequests,
491
+ api,
492
+ });
493
+ yield* prerenderResourceRouteEffect({
494
+ handler: requestHandler,
495
+ prerenderPath: path,
496
+ clientBuildDir,
497
+ basename,
498
+ api,
499
+ });
504
500
  } else {
505
501
  yield* Effect.sync(() => {
506
502
  api.logger.warn(
@@ -519,30 +515,26 @@ const createPrerenderPathEffect = ({
519
515
  return build.assets?.routes?.[matchedRouteId]?.hasLoader;
520
516
  });
521
517
  const data = hasLoaders
522
- ? yield* tryPluginPromise(() =>
523
- prerenderData({
524
- handler: requestHandler,
525
- prerenderPath: path,
526
- onlyRoutes: null,
527
- clientBuildDir,
528
- basename,
529
- trailingSlashAwareDataRequests:
530
- future.unstable_trailingSlashAwareDataRequests,
531
- api,
532
- })
533
- )
518
+ ? yield* prerenderDataEffect({
519
+ handler: requestHandler,
520
+ prerenderPath: path,
521
+ onlyRoutes: null,
522
+ clientBuildDir,
523
+ basename,
524
+ trailingSlashAwareDataRequests:
525
+ future.unstable_trailingSlashAwareDataRequests,
526
+ api,
527
+ })
534
528
  : undefined;
535
529
 
536
- yield* tryPluginPromise(() =>
537
- prerenderRoute({
538
- handler: requestHandler,
539
- prerenderPath: path,
540
- clientBuildDir,
541
- basename,
542
- api,
543
- requestInit: data ? createPrerenderDataRequestInit(data) : undefined,
544
- })
545
- );
530
+ yield* prerenderRouteEffect({
531
+ handler: requestHandler,
532
+ prerenderPath: path,
533
+ clientBuildDir,
534
+ basename,
535
+ api,
536
+ requestInit: data ? createPrerenderDataRequestInit(data) : undefined,
537
+ });
546
538
  });
547
539
 
548
540
  const createPrerenderDataRequestInit = (
@@ -558,35 +550,6 @@ const createPrerenderDataRequestInit = (
558
550
  : undefined;
559
551
  };
560
552
 
561
- const runPrerenderPaths = async ({
562
- build,
563
- requestHandler,
564
- clientBuildDir,
565
- options,
566
- }: {
567
- build: PrerenderServerBuild;
568
- requestHandler: (request: Request) => Promise<Response>;
569
- clientBuildDir: string;
570
- options: RunReactRouterPrerenderBuildOptions;
571
- }): Promise<void> => {
572
- const { prerenderConfig, prerenderPaths } = options;
573
- const buildRoutes = createPrerenderRoutes(build.routes);
574
- const concurrency = getPrerenderConcurrency(prerenderConfig);
575
-
576
- await runPluginEffect(
577
- createBoundedPrerenderTasksEffect(prerenderPaths, concurrency, path =>
578
- createPrerenderPathEffect({
579
- path,
580
- build,
581
- buildRoutes,
582
- requestHandler,
583
- clientBuildDir,
584
- options,
585
- })
586
- )
587
- );
588
- };
589
-
590
553
  export const runReactRouterPrerenderBuild = async (
591
554
  options: RunReactRouterPrerenderBuildOptions
592
555
  ): Promise<void> => {
@@ -600,6 +563,7 @@ export const runReactRouterPrerenderBuild = async (
600
563
  serverBuildFile,
601
564
  ssr,
602
565
  isPrerenderEnabled,
566
+ prerenderConfig,
603
567
  prerenderPaths,
604
568
  routes,
605
569
  latestBrowserManifest,
@@ -609,8 +573,9 @@ export const runReactRouterPrerenderBuild = async (
609
573
  appDirectory,
610
574
  assetPrefix,
611
575
  routeChunkOptions,
576
+ routeModuleAnalysis,
612
577
  buildManifest,
613
- resolvedConfigWithRoutes,
578
+ buildEndReactRouterConfig,
614
579
  buildEnd,
615
580
  basename,
616
581
  } = options;
@@ -652,7 +617,10 @@ export const runReactRouterPrerenderBuild = async (
652
617
  clientStats,
653
618
  appDirectory,
654
619
  assetPrefix,
655
- routeChunkOptions
620
+ createReactRouterManifestOptions({
621
+ routeChunks: routeChunkOptions,
622
+ routeModuleAnalysis,
623
+ })
656
624
  );
657
625
  assertValidSsrFalsePrerenderExports({
658
626
  routes,
@@ -671,12 +639,22 @@ export const runReactRouterPrerenderBuild = async (
671
639
  );
672
640
  }
673
641
 
674
- await runPrerenderPaths({
675
- build,
676
- requestHandler,
677
- clientBuildDir,
678
- options,
679
- });
642
+ const buildRoutes = createPrerenderRoutes(build.routes);
643
+ await runPluginEffect(
644
+ createBoundedPrerenderTasksEffect(
645
+ prerenderPaths,
646
+ getPrerenderConcurrency(prerenderConfig),
647
+ path =>
648
+ createPrerenderPathEffect({
649
+ path,
650
+ build,
651
+ buildRoutes,
652
+ requestHandler,
653
+ clientBuildDir,
654
+ options,
655
+ })
656
+ )
657
+ );
680
658
  }
681
659
 
682
660
  if (!ssr) {
@@ -700,8 +678,8 @@ export const runReactRouterPrerenderBuild = async (
700
678
  if (buildEnd) {
701
679
  await buildEnd({
702
680
  buildManifest,
703
- reactRouterConfig: resolvedConfigWithRoutes,
704
- viteConfig: api.getNormalizedConfig(),
681
+ reactRouterConfig: buildEndReactRouterConfig,
682
+ rsbuildConfig: api.getNormalizedConfig(),
705
683
  });
706
684
  }
707
685
  };
package/src/prerender.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Config } from './react-router-config.js';
2
+ import type { PrerenderConfigObject, PrerenderPathsConfig } from './types.js';
2
3
  import type { RouteConfigEntry } from '@react-router/dev/routes';
3
4
  import { matchRoutes } from 'react-router';
4
5
 
@@ -6,20 +7,6 @@ type ReactRouterPrerenderConfig = Config['prerender'];
6
7
  type MatchRouteObject =
7
8
  Parameters<typeof matchRoutes>[0] extends Array<infer R> ? R : never;
8
9
 
9
- type PrerenderPathsConfig =
10
- | boolean
11
- | string[]
12
- | ((args: {
13
- getStaticPaths: () => string[];
14
- }) => boolean | string[] | Promise<boolean | string[]>);
15
-
16
- type PrerenderConfigObject = Extract<
17
- NonNullable<ReactRouterPrerenderConfig>,
18
- { paths: unknown }
19
- > & {
20
- unstable_concurrency?: number;
21
- };
22
-
23
10
  type PrerenderConfig = ReactRouterPrerenderConfig | PrerenderConfigObject;
24
11
  type PrerenderConcurrencyConfig =
25
12
  | {
@@ -78,7 +65,11 @@ export const createPrerenderRoutes = (
78
65
  grouped: Record<string, any[]> = groupRoutesByParentId(manifest)
79
66
  ): MatchRouteObject[] => {
80
67
  return (grouped[parentId] || []).map(route => {
81
- const common = { id: route.id, path: route.path };
68
+ const common = {
69
+ id: route.id,
70
+ path: route.path,
71
+ caseSensitive: route.caseSensitive,
72
+ };
82
73
  if (route.index) {
83
74
  return { index: true, ...common } as MatchRouteObject;
84
75
  }
@@ -120,10 +111,9 @@ export const getSsrFalsePrerenderExportErrors = ({
120
111
  const errors: string[] = [];
121
112
  for (const [routeId, route] of Object.entries(manifestRoutes)) {
122
113
  const exports = routeExports[routeId] ?? [];
123
- const invalidApis: string[] = [];
124
-
125
- if (exports.includes('headers')) invalidApis.push('headers');
126
- if (exports.includes('action')) invalidApis.push('action');
114
+ const invalidApis = ['headers', 'action', 'middleware'].filter(api =>
115
+ exports.includes(api)
116
+ );
127
117
 
128
118
  if (invalidApis.length > 0) {
129
119
  errors.push(
@@ -143,11 +133,17 @@ export const getSsrFalsePrerenderExportErrors = ({
143
133
  );
144
134
  }
145
135
 
136
+ // A loader on an ancestor route is only invalid when the ancestor is not
137
+ // itself fully prerendered. The root route is exempt: its loader always
138
+ // runs to render the app shell and it is always part of the prerender set
139
+ // whenever any path is prerendered, so upstream never flags it here.
146
140
  let parentRoute =
147
- route.parentId && manifestRoutes[route.parentId]
141
+ route.parentId &&
142
+ route.parentId !== 'root' &&
143
+ manifestRoutes[route.parentId]
148
144
  ? manifestRoutes[route.parentId]
149
145
  : null;
150
- while (parentRoute) {
146
+ while (parentRoute && parentRoute.id !== 'root') {
151
147
  if (parentRoute.hasLoader && !parentRoute.hasClientLoader) {
152
148
  errors.push(
153
149
  `Prerender: 1 invalid route export in \`${parentRoute.id}\` when ` +
@@ -156,7 +152,9 @@ export const getSsrFalsePrerenderExportErrors = ({
156
152
  );
157
153
  }
158
154
  parentRoute =
159
- parentRoute.parentId && manifestRoutes[parentRoute.parentId]
155
+ parentRoute.parentId &&
156
+ parentRoute.parentId !== 'root' &&
157
+ manifestRoutes[parentRoute.parentId]
160
158
  ? manifestRoutes[parentRoute.parentId]
161
159
  : null;
162
160
  }
@@ -247,8 +245,9 @@ export const resolvePrerenderPaths = async (
247
245
  });
248
246
  warn(
249
247
  [
250
- 'Warning: Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.',
251
- 'You may want to use the `prerender()` API to prerender the following paths:',
248
+ '⚠️ Paths with dynamic/splat params cannot be prerendered when ' +
249
+ 'using `prerender: true`. You may want to use the `prerender()` ' +
250
+ 'API to prerender the following paths:',
252
251
  ...paramRoutes.map(path => ` - ${path.replace(/^\/(?=[:*])/, '')}`),
253
252
  ].join('\n')
254
253
  );