rsbuild-plugin-react-router 0.4.1 → 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 (126) hide show
  1. package/README.md +72 -240
  2. package/dist/{451.js → 511.js} +461 -331
  3. package/dist/build-output-transforms.d.ts +32 -7
  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 -3
  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/federation.d.ts +2 -0
  17. package/dist/index.cjs +3675 -2107
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3614 -2214
  20. package/dist/lazy-compilation-prewarm.d.ts +8 -5
  21. package/dist/manifest.d.ts +16 -4
  22. package/dist/mode-plan.d.ts +82 -0
  23. package/dist/modify-browser-manifest.d.ts +8 -4
  24. package/dist/performance.d.ts +4 -1
  25. package/dist/plugin-utils.d.ts +27 -1
  26. package/dist/prerender-build.d.ts +5 -5
  27. package/dist/prerender.d.ts +1 -5
  28. package/dist/react-router-config.d.ts +11 -3
  29. package/dist/route-artifacts.d.ts +4 -2
  30. package/dist/route-chunks.d.ts +2 -1
  31. package/dist/route-component-transform.d.ts +0 -2
  32. package/dist/route-export-pruning.d.ts +4 -1
  33. package/dist/route-imports.d.ts +18 -0
  34. package/dist/route-module-transform-loader.d.ts +14 -0
  35. package/dist/route-module-transform-loader.js +44 -0
  36. package/dist/route-module-transform-rules.d.ts +14 -0
  37. package/dist/route-transform-tasks.d.ts +6 -0
  38. package/dist/route-watch.d.ts +11 -6
  39. package/dist/rsc-dev-server.d.ts +26 -0
  40. package/dist/rsc-prerender.d.ts +54 -0
  41. package/dist/rsc-route-config.d.ts +6 -0
  42. package/dist/rsc-route-exports.d.ts +15 -0
  43. package/dist/rsc-route-transform-loader.cjs +43 -0
  44. package/dist/rsc-route-transform-loader.d.ts +30 -0
  45. package/dist/rsc-route-transform-loader.js +16 -0
  46. package/dist/rsc-route-transform-registration.d.ts +12 -0
  47. package/dist/rsc-route-transforms.d.ts +21 -0
  48. package/dist/rsc-support.d.ts +23 -0
  49. package/dist/rsc-virtual-modules.d.ts +19 -0
  50. package/dist/server-build-plan.d.ts +2 -1
  51. package/dist/server-build-resolution.d.ts +1 -2
  52. package/dist/server-utils.d.ts +4 -5
  53. package/dist/ssr-asset-relocation.d.ts +98 -0
  54. package/dist/templates/entry.rsc.client.d.ts +1 -0
  55. package/dist/templates/entry.rsc.client.js +61 -0
  56. package/dist/templates/entry.rsc.d.ts +9 -0
  57. package/dist/templates/entry.rsc.js +38 -0
  58. package/dist/templates/entry.rsc.ssr.d.ts +4 -0
  59. package/dist/templates/entry.rsc.ssr.js +24 -0
  60. package/dist/typegen.d.ts +4 -2
  61. package/dist/types.d.ts +34 -6
  62. package/package.json +76 -22
  63. package/src/build-output-transforms.ts +181 -43
  64. package/src/classic-mode.ts +253 -0
  65. package/src/config-imports.ts +153 -6
  66. package/src/constants.ts +6 -2
  67. package/src/dev-background-resources.ts +46 -100
  68. package/src/dev-generation.ts +52 -33
  69. package/src/dev-hmr.ts +112 -80
  70. package/src/dev-runtime-artifacts.ts +11 -12
  71. package/src/dev-runtime-controller.ts +75 -85
  72. package/src/dev-runtime-session.ts +14 -18
  73. package/src/dev-server.ts +2 -0
  74. package/src/dev-source-maps.ts +257 -0
  75. package/src/effect-runtime.ts +105 -57
  76. package/src/entry-paths.ts +80 -0
  77. package/src/environment-output.ts +55 -0
  78. package/src/export-utils.ts +15 -11
  79. package/src/federation.ts +38 -0
  80. package/src/index.ts +666 -519
  81. package/src/lazy-compilation-prewarm.ts +20 -4
  82. package/src/manifest.ts +157 -82
  83. package/src/mode-plan.ts +367 -0
  84. package/src/modify-browser-manifest.ts +130 -124
  85. package/src/performance.ts +11 -2
  86. package/src/plugin-utils.ts +103 -39
  87. package/src/prerender-build.ts +82 -104
  88. package/src/prerender.ts +23 -24
  89. package/src/react-router-config.ts +72 -26
  90. package/src/route-artifacts.ts +96 -66
  91. package/src/route-chunks.ts +167 -51
  92. package/src/route-component-transform.ts +14 -21
  93. package/src/route-export-pruning.ts +4 -3
  94. package/src/route-imports.ts +100 -0
  95. package/src/route-module-transform-loader.ts +149 -0
  96. package/src/route-module-transform-rules.ts +115 -0
  97. package/src/route-transform-tasks.ts +44 -29
  98. package/src/route-watch.ts +166 -188
  99. package/src/rsc-dev-server.ts +112 -0
  100. package/src/rsc-prerender.ts +362 -0
  101. package/src/rsc-route-config.ts +175 -0
  102. package/src/rsc-route-exports.ts +67 -0
  103. package/src/rsc-route-transform-loader.ts +65 -0
  104. package/src/rsc-route-transform-registration.ts +145 -0
  105. package/src/rsc-route-transforms.ts +995 -0
  106. package/src/rsc-runtime.d.ts +143 -0
  107. package/src/rsc-support.ts +116 -0
  108. package/src/rsc-virtual-modules.ts +113 -0
  109. package/src/server-build-plan.ts +14 -3
  110. package/src/server-build-resolution.ts +37 -47
  111. package/src/server-utils.ts +21 -35
  112. package/src/ssr-asset-relocation.ts +183 -0
  113. package/src/ssr-externals.ts +8 -26
  114. package/src/templates/entry.rsc.client.tsx +168 -0
  115. package/src/templates/entry.rsc.ssr.tsx +45 -0
  116. package/src/templates/entry.rsc.tsx +80 -0
  117. package/src/typegen.ts +40 -23
  118. package/src/types.ts +43 -6
  119. package/src/warnings/warn-on-client-source-maps.ts +6 -10
  120. package/dist/parallel-route-transform-protocol.d.ts +0 -25
  121. package/dist/parallel-route-transform-worker.d.ts +0 -1
  122. package/dist/parallel-route-transform-worker.js +0 -38
  123. package/dist/parallel-route-transforms.d.ts +0 -29
  124. package/src/parallel-route-transform-protocol.ts +0 -35
  125. package/src/parallel-route-transform-worker.ts +0 -82
  126. package/src/parallel-route-transforms.ts +0 -458
@@ -5,7 +5,8 @@ import {
5
5
  } from 'yuku-analyzer';
6
6
  import { print } from 'yuku-codegen';
7
7
  import { walk } from 'yuku-parser';
8
- import { normalize, relative, resolve } from 'pathe';
8
+ import { dirname, normalize, relative, resolve } from 'pathe';
9
+ import { SERVER_ONLY_ROUTE_EXPORTS_SET } from './constants.js';
9
10
 
10
11
  type AnyNode = Record<string, any>;
11
12
 
@@ -15,7 +16,7 @@ export type RouteChunkExportName =
15
16
  | 'clientMiddleware'
16
17
  | 'HydrateFallback';
17
18
 
18
- export type RouteChunkName = 'main' | RouteChunkExportName;
19
+ export type RouteChunkName = 'main' | RouteChunkExportName | (string & {});
19
20
 
20
21
  export type RouteChunkConfig = {
21
22
  splitRouteModules?: boolean | 'enforce';
@@ -35,6 +36,7 @@ export type RouteChunkInfo = {
35
36
  hasRouteChunks: boolean;
36
37
  hasRouteChunkByExportName: Record<RouteChunkExportName, boolean>;
37
38
  chunkedExports: RouteChunkExportName[];
39
+ sharedChunkedExports: string[];
38
40
  };
39
41
 
40
42
  export const routeChunkExportNames: RouteChunkExportName[] = [
@@ -63,7 +65,7 @@ export const emptyRouteChunkSnippet = (): string => 'export {};';
63
65
 
64
66
  const routeChunkQueryStringPrefix = '?route-chunk=';
65
67
 
66
- const routeChunkQueryStrings: Record<RouteChunkName, string> = {
68
+ const routeChunkQueryStrings: Record<RouteChunkExportName | 'main', string> = {
67
69
  main: `${routeChunkQueryStringPrefix}main`,
68
70
  clientAction: `${routeChunkQueryStringPrefix}clientAction`,
69
71
  clientLoader: `${routeChunkQueryStringPrefix}clientLoader`,
@@ -141,6 +143,7 @@ type ExportDependencies = {
141
143
  topLevelStatements: Set<AnyNode>;
142
144
  topLevelNonModuleStatements: Set<AnyNode>;
143
145
  importedIdentifierNames: Set<string>;
146
+ importSources: Set<string>;
144
147
  exportedVariableDeclarators: Set<AnyNode>;
145
148
  };
146
149
 
@@ -172,6 +175,18 @@ const getVariableDeclaratorForNode = (
172
175
  return null;
173
176
  };
174
177
 
178
+ const isTopLevelExportedVariableDeclarator = (
179
+ module: Module,
180
+ node: AnyNode
181
+ ): boolean => {
182
+ const declaration = module.parentOf(node as never) as AnyNode | null;
183
+ if (declaration?.type !== 'VariableDeclaration') {
184
+ return false;
185
+ }
186
+ const statement = module.parentOf(declaration as never) as AnyNode | null;
187
+ return statement?.type === 'ExportNamedDeclaration';
188
+ };
189
+
175
190
  const getExportedName = (exported: AnyNode): string => {
176
191
  if (exported.type === 'Identifier') {
177
192
  return exported.name;
@@ -194,6 +209,26 @@ const setsIntersect = <T>(set1: Set<T>, set2: Set<T>) => {
194
209
  return false;
195
210
  };
196
211
 
212
+ const isEntryClientImport = (source: string, importer: string): boolean => {
213
+ if (!source.startsWith('.') && !source.startsWith('/')) {
214
+ return false;
215
+ }
216
+ const resolved = normalize(resolve('/', dirname(importer), source));
217
+ return /(?:^|\/)entry\.client(?:\.[cm]?[jt]sx?)?$/.test(resolved);
218
+ };
219
+
220
+ const hasEntryClientImport = (
221
+ dependencies: ExportDependencies,
222
+ importer: string
223
+ ): boolean => {
224
+ for (const source of dependencies.importSources) {
225
+ if (isEntryClientImport(source, importer)) {
226
+ return true;
227
+ }
228
+ }
229
+ return false;
230
+ };
231
+
197
232
  const getExportDependencies = (
198
233
  code: string,
199
234
  cache: RouteChunkCache,
@@ -253,6 +288,7 @@ const getExportDependencies = (
253
288
  topLevelStatements: new Set(),
254
289
  topLevelNonModuleStatements: new Set(),
255
290
  importedIdentifierNames: new Set(),
291
+ importSources: new Set(),
256
292
  exportedVariableDeclarators: new Set(),
257
293
  };
258
294
  const visitedSymbols = new Set<YukuSymbol>();
@@ -278,6 +314,9 @@ const getExportDependencies = (
278
314
  return;
279
315
  }
280
316
  visitedSymbols.add(symbol);
317
+ if (symbol.declarations.length === 0) {
318
+ return;
319
+ }
281
320
 
282
321
  for (const declaration of symbol.declarations as AnyNode[]) {
283
322
  const statement = addCachedTopLevelStatement(
@@ -286,12 +325,15 @@ const getExportDependencies = (
286
325
  );
287
326
  if (statement.type === 'ImportDeclaration') {
288
327
  dependencies.importedIdentifierNames.add(symbol.name);
328
+ if (typeof statement.source?.value === 'string') {
329
+ dependencies.importSources.add(statement.source.value);
330
+ }
331
+ return;
289
332
  }
290
333
  const declarator = getCachedVariableDeclaratorForNode(declaration);
291
334
  if (
292
335
  declarator &&
293
- getCachedTopLevelStatementForNode(declarator).type ===
294
- 'ExportNamedDeclaration'
336
+ isTopLevelExportedVariableDeclarator(module, declarator)
295
337
  ) {
296
338
  dependencies.exportedVariableDeclarators.add(declarator);
297
339
  }
@@ -336,12 +378,22 @@ const getExportDependencies = (
336
378
 
337
379
  const isExportChunkable = (
338
380
  exportName: string,
339
- exportDependencies: Map<string, ExportDependencies>
381
+ exportDependencies: Map<string, ExportDependencies>,
382
+ importer: string
340
383
  ) => {
341
384
  const dependencies = exportDependencies.get(exportName);
342
385
  if (!dependencies) {
343
386
  return false;
344
387
  }
388
+ if (exportName === 'clientLoader' && hasHydrateAssignment(dependencies)) {
389
+ return false;
390
+ }
391
+ if (
392
+ exportName === 'clientLoader' &&
393
+ hasEntryClientImport(dependencies, importer)
394
+ ) {
395
+ return false;
396
+ }
345
397
  for (const [currentExportName, currentDependencies] of exportDependencies) {
346
398
  if (currentExportName === exportName) {
347
399
  continue;
@@ -376,6 +428,21 @@ const isExportChunkable = (
376
428
  return true;
377
429
  };
378
430
 
431
+ const hasHydrateAssignment = (dependencies: ExportDependencies): boolean =>
432
+ Array.from(dependencies.topLevelNonModuleStatements).some(statement => {
433
+ const expression = statement.expression;
434
+ const left = expression?.left;
435
+ return (
436
+ statement.type === 'ExpressionStatement' &&
437
+ expression?.type === 'AssignmentExpression' &&
438
+ left?.type === 'MemberExpression' &&
439
+ left.object?.type === 'Identifier' &&
440
+ left.object.name === 'clientLoader' &&
441
+ left.property?.type === 'Identifier' &&
442
+ left.property.name === 'hydrate'
443
+ );
444
+ });
445
+
379
446
  const getChunkableExportMap = (
380
447
  code: string,
381
448
  cache: RouteChunkCache,
@@ -384,7 +451,7 @@ const getChunkableExportMap = (
384
451
  getOrSetFromCache(cache, `${cacheKey}::getChunkableExportMap`, code, () => {
385
452
  const exportDependencies = getExportDependencies(code, cache, cacheKey);
386
453
  return createRouteChunkExportMap(exportName =>
387
- isExportChunkable(exportName, exportDependencies)
454
+ isExportChunkable(exportName, exportDependencies, cacheKey)
388
455
  );
389
456
  });
390
457
 
@@ -393,12 +460,18 @@ const hasChunkableExport = (
393
460
  exportName: string,
394
461
  cache: RouteChunkCache,
395
462
  cacheKey: string
396
- ) =>
397
- (routeChunkExportNames as string[]).includes(exportName)
398
- ? getChunkableExportMap(code, cache, cacheKey)[
399
- exportName as RouteChunkExportName
400
- ]
401
- : false;
463
+ ) => {
464
+ if ((routeChunkExportNames as string[]).includes(exportName)) {
465
+ return getChunkableExportMap(code, cache, cacheKey)[
466
+ exportName as RouteChunkExportName
467
+ ];
468
+ }
469
+ return isExportChunkable(
470
+ exportName,
471
+ getExportDependencies(code, cache, cacheKey),
472
+ cacheKey
473
+ );
474
+ };
402
475
 
403
476
  const generateCode = (program: AnyNode): string | undefined => {
404
477
  if (program.body.length === 0) {
@@ -501,14 +574,12 @@ const getChunkedExport = (
501
574
  );
502
575
  };
503
576
 
504
- const getChunkedExportCacheKey = (
505
- cacheKey: string,
506
- exportName: RouteChunkExportName
507
- ) => `${cacheKey}::getChunkedExport::${exportName}`;
577
+ const getChunkedExportCacheKey = (cacheKey: string, exportName: string) =>
578
+ `${cacheKey}::getChunkedExport::${exportName}`;
508
579
 
509
580
  const hasCachedChunkedExport = (
510
581
  code: string,
511
- exportName: RouteChunkExportName,
582
+ exportName: string,
512
583
  cache: RouteChunkCache,
513
584
  cacheKey: string
514
585
  ): boolean =>
@@ -525,11 +596,10 @@ const omitChunkedExports = (
525
596
  `${cacheKey}::omitChunkedExports::${exportNames.join(',')}`,
526
597
  code,
527
598
  () => {
528
- const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey);
529
599
  const exportNameSet = new Set(exportNames);
530
600
  const isOmitted = (exportName: string) =>
531
601
  exportNameSet.has(exportName) &&
532
- Boolean(chunkableExportMap[exportName as RouteChunkExportName]);
602
+ hasChunkableExport(code, exportName, cache, cacheKey);
533
603
  const isRetained = (exportName: string) => !isOmitted(exportName);
534
604
 
535
605
  const exportDependencies = getExportDependencies(code, cache, cacheKey);
@@ -633,17 +703,57 @@ const precomputeChunkedExports = (
633
703
  cache: RouteChunkCache,
634
704
  cacheKey: string
635
705
  ) => {
636
- const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey);
637
- for (const exportName of routeChunkExportNames) {
638
- if (!chunkableExportMap[exportName]) {
706
+ for (const exportName of getChunkedExportNames(code, cache, cacheKey)) {
707
+ if (!hasChunkableExport(code, exportName, cache, cacheKey)) {
708
+ continue;
709
+ }
710
+ if (hasCachedChunkedExport(code, exportName, cache, cacheKey)) {
639
711
  continue;
640
712
  }
641
- if (!hasCachedChunkedExport(code, exportName, cache, cacheKey)) {
713
+ if (
714
+ !hasCachedValue(
715
+ cache,
716
+ getChunkedExportCacheKey(cacheKey, exportName),
717
+ code
718
+ )
719
+ ) {
642
720
  getChunkedExport(code, exportName, cache, cacheKey);
643
721
  }
644
722
  }
645
723
  };
646
724
 
725
+ const getSharedChunkedExports = (
726
+ exportDependencies: Map<string, ExportDependencies>,
727
+ cacheKey: string
728
+ ): string[] =>
729
+ Array.from(exportDependencies.keys())
730
+ .filter(
731
+ exportName =>
732
+ exportName !== 'default' &&
733
+ !(routeChunkExportNames as string[]).includes(exportName) &&
734
+ !SERVER_ONLY_ROUTE_EXPORTS_SET.has(exportName) &&
735
+ isExportChunkable(exportName, exportDependencies, cacheKey)
736
+ )
737
+ .sort();
738
+
739
+ const getChunkedExportNames = (
740
+ code: string,
741
+ cache: RouteChunkCache,
742
+ cacheKey: string
743
+ ): string[] => {
744
+ const exportDependencies = getExportDependencies(code, cache, cacheKey);
745
+ const routeChunkedExports = routeChunkExportNames.filter(exportName =>
746
+ isExportChunkable(exportName, exportDependencies, cacheKey)
747
+ );
748
+ if (routeChunkedExports.length === 0) {
749
+ return [];
750
+ }
751
+ return [
752
+ ...routeChunkedExports,
753
+ ...getSharedChunkedExports(exportDependencies, cacheKey),
754
+ ];
755
+ };
756
+
647
757
  export const detectRouteChunks = (
648
758
  code: string,
649
759
  cache: RouteChunkCache | undefined,
@@ -663,31 +773,43 @@ export const detectRouteChunks = (
663
773
  const chunkedExports = Object.entries(hasRouteChunkByExportName)
664
774
  .filter(([, isChunked]) => isChunked)
665
775
  .map(([exportName]) => exportName as RouteChunkExportName);
666
- const hasRouteChunks = chunkedExports.length > 0;
776
+ const sharedChunkedExports =
777
+ chunkedExports.length > 0
778
+ ? getSharedChunkedExports(exportDependencies, cacheKey)
779
+ : [];
780
+ const hasRouteChunks =
781
+ chunkedExports.length > 0 || sharedChunkedExports.length > 0;
667
782
  return {
668
783
  exportNames: Array.from(exportDependencies.keys()),
669
784
  hasRouteChunks,
670
785
  hasRouteChunkByExportName,
671
786
  chunkedExports,
787
+ sharedChunkedExports,
672
788
  };
673
789
  };
674
790
 
675
- export const getRouteChunkCode: (
676
- code: string,
677
- chunkName: RouteChunkName,
678
- cache: RouteChunkCache | undefined,
679
- cacheKey: string
680
- ) => string | undefined = (
791
+ export const getRouteChunkCode = (
681
792
  code: string,
682
793
  chunkName: RouteChunkName,
683
794
  cache: RouteChunkCache | undefined,
684
795
  cacheKey: string
685
- ) => {
796
+ ): string | undefined => {
686
797
  const analysisCache = cache ?? new Map();
687
798
  if (chunkName === 'main') {
799
+ const exportDependencies = getExportDependencies(
800
+ code,
801
+ analysisCache,
802
+ cacheKey
803
+ );
804
+ const serverOnlyExports = Array.from(exportDependencies.keys()).filter(
805
+ exportName => SERVER_ONLY_ROUTE_EXPORTS_SET.has(exportName)
806
+ );
688
807
  return omitChunkedExports(
689
808
  code,
690
- routeChunkExportNames,
809
+ [
810
+ ...getChunkedExportNames(code, analysisCache, cacheKey),
811
+ ...serverOnlyExports,
812
+ ],
691
813
  analysisCache,
692
814
  cacheKey
693
815
  );
@@ -701,13 +823,17 @@ export const getRouteChunkCode: (
701
823
  export const getRouteChunkModuleId = (
702
824
  filePath: string,
703
825
  chunkName: RouteChunkName
704
- ) => `${filePath}${routeChunkQueryStrings[chunkName]}`;
826
+ ) =>
827
+ `${filePath}${
828
+ routeChunkQueryStrings[chunkName as RouteChunkExportName | 'main'] ??
829
+ `${routeChunkQueryStringPrefix}${encodeURIComponent(chunkName)}`
830
+ }`;
705
831
 
706
- export const isRouteChunkModuleId: (id: string) => boolean = (id: string) =>
832
+ export const isRouteChunkModuleId = (id: string): boolean =>
707
833
  getRouteChunkNameFromModuleId(id) !== null;
708
834
 
709
835
  const isRouteChunkName = (name: string): name is RouteChunkName =>
710
- name === 'main' || (routeChunkExportNames as string[]).includes(name);
836
+ name === 'main' || /^[A-Za-z_$][\w$]*$/.test(name);
711
837
 
712
838
  export const getRouteChunkNameFromModuleId = (
713
839
  id: string
@@ -769,20 +895,16 @@ export const buildManifestChunkValidity = (
769
895
  !exportNames.has(exportName) || hasRouteChunkByExportName[exportName]
770
896
  );
771
897
 
772
- export const detectRouteChunksIfEnabled: (
898
+ export const detectRouteChunksIfEnabled = async (
773
899
  cache: RouteChunkCache | undefined,
774
900
  config: RouteChunkConfig,
775
901
  id: string,
776
902
  code: string
777
- ) => Promise<RouteChunkInfo> = async (
778
- cache: RouteChunkCache | undefined,
779
- config: RouteChunkConfig,
780
- id: string,
781
- code: string
782
- ) => {
903
+ ): Promise<RouteChunkInfo> => {
783
904
  const noRouteChunks = (): RouteChunkInfo => ({
784
905
  exportNames: [],
785
906
  chunkedExports: [] as RouteChunkExportName[],
907
+ sharedChunkedExports: [],
786
908
  hasRouteChunks: false,
787
909
  hasRouteChunkByExportName: createEmptyRouteChunkByExportName(),
788
910
  });
@@ -795,19 +917,13 @@ export const detectRouteChunksIfEnabled: (
795
917
  return detectRouteChunks(code, cache, cacheKey);
796
918
  };
797
919
 
798
- export const getRouteChunkIfEnabled: (
799
- cache: RouteChunkCache | undefined,
800
- config: RouteChunkConfig,
801
- id: string,
802
- chunkName: RouteChunkName,
803
- code: string
804
- ) => Promise<string | null> = async (
920
+ export const getRouteChunkIfEnabled = async (
805
921
  cache: RouteChunkCache | undefined,
806
922
  config: RouteChunkConfig,
807
923
  id: string,
808
924
  chunkName: RouteChunkName,
809
925
  code: string
810
- ) => {
926
+ ): Promise<string | null> => {
811
927
  if (!config.splitRouteModules) {
812
928
  return null;
813
929
  }
@@ -16,21 +16,14 @@ import {
16
16
  type AnyNode,
17
17
  } from './route-ast.js';
18
18
 
19
- export function toFunctionExpression(decl: AnyNode): AnyNode {
20
- return {
21
- ...decl,
22
- type: 'FunctionExpression',
23
- declare: undefined,
24
- };
25
- }
26
-
27
- export function toClassExpression(decl: AnyNode): AnyNode {
28
- return {
29
- ...decl,
30
- type: 'ClassExpression',
31
- declare: undefined,
32
- };
33
- }
19
+ const toExpression = (
20
+ decl: AnyNode,
21
+ type: 'FunctionExpression' | 'ClassExpression'
22
+ ): AnyNode => ({
23
+ ...decl,
24
+ type,
25
+ declare: undefined,
26
+ });
34
27
 
35
28
  const getComponentExportName = (exportedName: string): string | null => {
36
29
  if (exportedName === 'default') {
@@ -124,7 +117,7 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => {
124
117
 
125
118
  function getHocUid(hocName: string) {
126
119
  const uid = getUid(hocName);
127
- hocs.push([hocName, uid]);
120
+ hocs.push([`UNSAFE_${hocName}`, uid]);
128
121
  return identifier(uid);
129
122
  }
130
123
 
@@ -132,9 +125,9 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => {
132
125
  const uid = getHocUid(`with${name}Props`);
133
126
  const expression =
134
127
  declaration.type === 'FunctionDeclaration'
135
- ? toFunctionExpression(declaration)
128
+ ? toExpression(declaration, 'FunctionExpression')
136
129
  : declaration.type === 'ClassDeclaration'
137
- ? toClassExpression(declaration)
130
+ ? toExpression(declaration, 'ClassExpression')
138
131
  : declaration;
139
132
  return variableDeclaration(name, callExpression(uid, [expression]));
140
133
  }
@@ -166,9 +159,9 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => {
166
159
  }
167
160
  const expression =
168
161
  declaration.type === 'FunctionDeclaration'
169
- ? toFunctionExpression(declaration)
162
+ ? toExpression(declaration, 'FunctionExpression')
170
163
  : declaration.type === 'ClassDeclaration'
171
- ? toClassExpression(declaration)
164
+ ? toExpression(declaration, 'ClassExpression')
172
165
  : declaration;
173
166
  statement.declaration = callExpression(uid, [expression]);
174
167
  continue;
@@ -301,7 +294,7 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => {
301
294
  0,
302
295
  importDeclaration(
303
296
  hocs.map(([name, local]) => ({ imported: name, local })),
304
- 'virtual/react-router/with-props'
297
+ 'react-router'
305
298
  )
306
299
  );
307
300
  }
@@ -198,7 +198,7 @@ const isNonReferenceIdentifier = (node: AnyNode, parent: AnyNode | null) => {
198
198
 
199
199
  const isUppercaseName = (name: string): boolean => /^[A-Z]/.test(name);
200
200
 
201
- const collectReferencedNames = (node: AnyNode): Set<string> => {
201
+ export const collectReferencedNames = (node: AnyNode): Set<string> => {
202
202
  const referenced = new Set<string>();
203
203
  walk(node as never, {
204
204
  Identifier(node, ctx) {
@@ -489,7 +489,8 @@ const hasRemovableExport = (
489
489
  export const removeExports = (
490
490
  ast: ParseResult | AnyNode,
491
491
  exportsToRemove: readonly string[],
492
- exportsToRemoveSet: ReadonlySet<string> = new Set(exportsToRemove)
492
+ exportsToRemoveSet: ReadonlySet<string> = new Set(exportsToRemove),
493
+ options: { pruneDeadDeclarations?: boolean } = {}
493
494
  ): boolean => {
494
495
  const program = getProgram(ast);
495
496
  if (!hasRemovableExport(program, exportsToRemoveSet)) {
@@ -631,7 +632,7 @@ export const removeExports = (
631
632
  }
632
633
  }
633
634
 
634
- if (exportsChanged) {
635
+ if (exportsChanged && options.pruneDeadDeclarations !== false) {
635
636
  removeNewlyDeadTopLevelDeclarations(
636
637
  program,
637
638
  declarationGraph,
@@ -0,0 +1,100 @@
1
+ import type { Rspack } from '@rsbuild/core';
2
+ import { resolve } from 'pathe';
3
+
4
+ import { JS_EXTENSIONS, PLUGIN_NAME } from './constants.js';
5
+ import type { Route } from './types.js';
6
+
7
+ type QuerylessRouteImportPlugin = {
8
+ name: string;
9
+ apply(compiler: Rspack.Compiler): void;
10
+ };
11
+
12
+ const RSC_CLIENT_ROUTE_MODULE_QUERY_PREFIX = '?client-route-module=';
13
+ const RSC_SHARED_CLIENT_ROUTE_MODULE_QUERY = '?client-route-module=shared';
14
+ const RSC_SERVER_ROUTE_MODULE_QUERY = '?server-route-module=';
15
+ const CLASSIC_CLIENT_ROUTE_MODULE_QUERY = '?react-router-route';
16
+
17
+ const isRscClientRouteModuleIssuer = (issuer: string): boolean =>
18
+ issuer.includes(RSC_CLIENT_ROUTE_MODULE_QUERY_PREFIX);
19
+
20
+ export const resolveQuerylessRouteImportRequest = ({
21
+ compilerName,
22
+ context,
23
+ issuer,
24
+ rsc = false,
25
+ request,
26
+ routeByFilePath,
27
+ }: {
28
+ compilerName?: string;
29
+ context?: string;
30
+ issuer?: string;
31
+ rsc?: boolean;
32
+ request?: string;
33
+ routeByFilePath: ReadonlyMap<string, Route>;
34
+ }): string | undefined => {
35
+ if (
36
+ typeof request !== 'string' ||
37
+ typeof context !== 'string' ||
38
+ typeof issuer !== 'string' ||
39
+ request.includes('?') ||
40
+ (!request.startsWith('.') && !request.startsWith('/'))
41
+ ) {
42
+ return;
43
+ }
44
+
45
+ const issuerPath = issuer.split('?')[0];
46
+ if (!routeByFilePath.has(issuerPath)) {
47
+ return;
48
+ }
49
+
50
+ const isRscClientIssuer = isRscClientRouteModuleIssuer(issuer);
51
+ const isWebCompiler = compilerName === 'web';
52
+ if (!rsc && !isWebCompiler && !isRscClientIssuer) {
53
+ return;
54
+ }
55
+
56
+ const candidate = resolve(context, request);
57
+ const routeFilePath = routeByFilePath.has(candidate)
58
+ ? candidate
59
+ : JS_EXTENSIONS.map(extension => `${candidate}${extension}`).find(
60
+ candidate => routeByFilePath.has(candidate)
61
+ );
62
+
63
+ if (!routeFilePath) {
64
+ return;
65
+ }
66
+
67
+ if (!rsc && isWebCompiler) {
68
+ return `${routeFilePath}${CLASSIC_CLIENT_ROUTE_MODULE_QUERY}`;
69
+ }
70
+
71
+ if (isWebCompiler || isRscClientIssuer) {
72
+ return `${routeFilePath}${RSC_SHARED_CLIENT_ROUTE_MODULE_QUERY}`;
73
+ }
74
+
75
+ return `${routeFilePath}${RSC_SERVER_ROUTE_MODULE_QUERY}`;
76
+ };
77
+
78
+ export const createQuerylessRouteImportPlugin = (
79
+ routeByFilePath: ReadonlyMap<string, Route>,
80
+ options: { rsc?: boolean } = {}
81
+ ): QuerylessRouteImportPlugin => ({
82
+ name: `${PLUGIN_NAME}:queryless-route-imports`,
83
+ apply(compiler: Rspack.Compiler) {
84
+ compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, factory => {
85
+ factory.hooks.beforeResolve.tap(PLUGIN_NAME, data => {
86
+ const resolvedRequest = resolveQuerylessRouteImportRequest({
87
+ compilerName: compiler.options?.name,
88
+ context: data?.context ?? data?.contextInfo?.issuer,
89
+ issuer: data?.contextInfo?.issuer,
90
+ rsc: options.rsc,
91
+ request: data?.request,
92
+ routeByFilePath,
93
+ });
94
+ if (resolvedRequest) {
95
+ data.request = resolvedRequest;
96
+ }
97
+ });
98
+ });
99
+ },
100
+ });