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.
- package/README.md +58 -208
- package/dist/511.js +370 -331
- package/dist/build-output-transforms.d.ts +30 -6
- package/dist/classic-mode.d.ts +56 -0
- package/dist/config-imports.d.ts +8 -2
- package/dist/constants.d.ts +3 -0
- package/dist/dev-background-resources.d.ts +3 -1
- package/dist/dev-generation.d.ts +2 -3
- package/dist/dev-hmr.d.ts +8 -2
- package/dist/dev-runtime-controller.d.ts +6 -1
- package/dist/dev-source-maps.d.ts +4 -0
- package/dist/effect-runtime.d.ts +17 -5
- package/dist/entry-paths.d.ts +16 -0
- package/dist/environment-output.d.ts +6 -0
- package/dist/export-utils.d.ts +2 -1
- package/dist/index.cjs +3957 -2208
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3470 -1793
- package/dist/lazy-compilation-prewarm.d.ts +8 -5
- package/dist/manifest.d.ts +16 -4
- package/dist/mode-plan.d.ts +82 -0
- package/dist/modify-browser-manifest.d.ts +8 -4
- package/dist/plugin-utils.d.ts +27 -1
- package/dist/prerender-build.d.ts +5 -5
- package/dist/prerender.d.ts +1 -5
- package/dist/react-router-config.d.ts +11 -3
- package/dist/route-artifacts.d.ts +4 -2
- package/dist/route-chunks.d.ts +2 -1
- package/dist/route-component-transform.d.ts +0 -2
- package/dist/route-export-pruning.d.ts +4 -1
- package/dist/route-imports.d.ts +18 -0
- package/dist/route-transform-tasks.d.ts +5 -0
- package/dist/route-watch.d.ts +11 -6
- package/dist/rsc-dev-server.d.ts +26 -0
- package/dist/rsc-prerender.d.ts +54 -0
- package/dist/rsc-route-config.d.ts +6 -0
- package/dist/rsc-route-exports.d.ts +15 -0
- package/dist/rsc-route-transform-loader.cjs +43 -0
- package/dist/rsc-route-transform-loader.d.ts +30 -0
- package/dist/rsc-route-transform-loader.js +16 -0
- package/dist/rsc-route-transform-registration.d.ts +12 -0
- package/dist/rsc-route-transforms.d.ts +21 -0
- package/dist/rsc-support.d.ts +23 -0
- package/dist/rsc-virtual-modules.d.ts +19 -0
- package/dist/server-build-plan.d.ts +2 -1
- package/dist/server-build-resolution.d.ts +1 -2
- package/dist/server-utils.d.ts +4 -5
- package/dist/ssr-asset-relocation.d.ts +98 -0
- package/dist/templates/entry.rsc.client.d.ts +1 -0
- package/dist/templates/entry.rsc.client.js +61 -0
- package/dist/templates/entry.rsc.d.ts +9 -0
- package/dist/templates/entry.rsc.js +38 -0
- package/dist/templates/entry.rsc.ssr.d.ts +4 -0
- package/dist/templates/entry.rsc.ssr.js +24 -0
- package/dist/typegen.d.ts +4 -2
- package/dist/types.d.ts +30 -1
- package/package.json +69 -14
- package/src/build-output-transforms.ts +155 -21
- package/src/classic-mode.ts +253 -0
- package/src/config-imports.ts +153 -6
- package/src/constants.ts +6 -2
- package/src/dev-background-resources.ts +46 -85
- package/src/dev-generation.ts +52 -33
- package/src/dev-hmr.ts +112 -80
- package/src/dev-runtime-artifacts.ts +11 -12
- package/src/dev-runtime-controller.ts +75 -85
- package/src/dev-runtime-session.ts +14 -18
- package/src/dev-server.ts +2 -0
- package/src/dev-source-maps.ts +257 -0
- package/src/effect-runtime.ts +105 -57
- package/src/entry-paths.ts +80 -0
- package/src/environment-output.ts +55 -0
- package/src/export-utils.ts +15 -11
- package/src/index.ts +661 -496
- package/src/lazy-compilation-prewarm.ts +20 -4
- package/src/manifest.ts +157 -82
- package/src/mode-plan.ts +367 -0
- package/src/modify-browser-manifest.ts +130 -124
- package/src/plugin-utils.ts +103 -39
- package/src/prerender-build.ts +82 -104
- package/src/prerender.ts +23 -24
- package/src/react-router-config.ts +72 -26
- package/src/route-artifacts.ts +96 -66
- package/src/route-chunks.ts +167 -51
- package/src/route-component-transform.ts +14 -21
- package/src/route-export-pruning.ts +4 -3
- package/src/route-imports.ts +100 -0
- package/src/route-transform-tasks.ts +39 -25
- package/src/route-watch.ts +166 -188
- package/src/rsc-dev-server.ts +112 -0
- package/src/rsc-prerender.ts +362 -0
- package/src/rsc-route-config.ts +175 -0
- package/src/rsc-route-exports.ts +67 -0
- package/src/rsc-route-transform-loader.ts +65 -0
- package/src/rsc-route-transform-registration.ts +145 -0
- package/src/rsc-route-transforms.ts +995 -0
- package/src/rsc-runtime.d.ts +143 -0
- package/src/rsc-support.ts +116 -0
- package/src/rsc-virtual-modules.ts +113 -0
- package/src/server-build-plan.ts +14 -3
- package/src/server-build-resolution.ts +37 -47
- package/src/server-utils.ts +21 -35
- package/src/ssr-asset-relocation.ts +183 -0
- package/src/ssr-externals.ts +8 -26
- package/src/templates/entry.rsc.client.tsx +168 -0
- package/src/templates/entry.rsc.ssr.tsx +45 -0
- package/src/templates/entry.rsc.tsx +80 -0
- package/src/typegen.ts +40 -23
- package/src/types.ts +39 -1
- package/src/warnings/warn-on-client-source-maps.ts +6 -10
package/src/route-chunks.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
637
|
-
|
|
638
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
) =>
|
|
826
|
+
) =>
|
|
827
|
+
`${filePath}${
|
|
828
|
+
routeChunkQueryStrings[chunkName as RouteChunkExportName | 'main'] ??
|
|
829
|
+
`${routeChunkQueryStringPrefix}${encodeURIComponent(chunkName)}`
|
|
830
|
+
}`;
|
|
705
831
|
|
|
706
|
-
export const isRouteChunkModuleId
|
|
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' ||
|
|
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
|
-
)
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
?
|
|
128
|
+
? toExpression(declaration, 'FunctionExpression')
|
|
136
129
|
: declaration.type === 'ClassDeclaration'
|
|
137
|
-
?
|
|
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
|
-
?
|
|
162
|
+
? toExpression(declaration, 'FunctionExpression')
|
|
170
163
|
: declaration.type === 'ClassDeclaration'
|
|
171
|
-
?
|
|
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
|
-
'
|
|
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
|
+
});
|
|
@@ -310,6 +310,41 @@ const buildComponentRefreshRegistrations = (names: string[]): string => {
|
|
|
310
310
|
return `\nif (typeof $RefreshReg$ === 'function') {\n${registrations}\n}\n`;
|
|
311
311
|
};
|
|
312
312
|
|
|
313
|
+
export const validateSpaModeRouteExports = ({
|
|
314
|
+
exportNames,
|
|
315
|
+
resourcePath,
|
|
316
|
+
rootRoutePath,
|
|
317
|
+
}: {
|
|
318
|
+
exportNames: readonly string[];
|
|
319
|
+
resourcePath: string;
|
|
320
|
+
rootRoutePath: string | null;
|
|
321
|
+
}): void => {
|
|
322
|
+
const isRootRoute = resourcePath === rootRoutePath;
|
|
323
|
+
const relativePath = relative(process.cwd(), resourcePath);
|
|
324
|
+
const invalidServerOnly = exportNames.filter(exp => {
|
|
325
|
+
if (isRootRoute && exp === 'loader') return false;
|
|
326
|
+
return SERVER_ONLY_ROUTE_EXPORTS_SET.has(exp);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (invalidServerOnly.length > 0) {
|
|
330
|
+
const list = invalidServerOnly.map(exp => `\`${exp}\``).join(', ');
|
|
331
|
+
throw new Error(
|
|
332
|
+
`SPA Mode: ${invalidServerOnly.length} invalid route export(s) in ` +
|
|
333
|
+
`\`${relativePath}\`: ${list}. ` +
|
|
334
|
+
`See https://reactrouter.com/how-to/spa for more information.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!isRootRoute && exportNames.includes('HydrateFallback')) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`SPA Mode: Invalid \`HydrateFallback\` export found in ` +
|
|
341
|
+
`\`${relativePath}\`. ` +
|
|
342
|
+
`\`HydrateFallback\` is only permitted on the root route in SPA Mode. ` +
|
|
343
|
+
`See https://reactrouter.com/how-to/spa for more information.`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
313
348
|
const transformRouteModule = async (
|
|
314
349
|
task: RouteModuleTransformTask
|
|
315
350
|
): Promise<RouteTransformResult> => {
|
|
@@ -325,32 +360,11 @@ const transformRouteModule = async (
|
|
|
325
360
|
|
|
326
361
|
const ast = parse(code, { sourceType: 'module' });
|
|
327
362
|
if (task.environmentName === 'web' && !task.ssr && task.isSpaMode) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const invalidServerOnly = resolvedExportNames.filter(exp => {
|
|
333
|
-
if (isRootRoute && exp === 'loader') return false;
|
|
334
|
-
return SERVER_ONLY_ROUTE_EXPORTS_SET.has(exp);
|
|
363
|
+
validateSpaModeRouteExports({
|
|
364
|
+
exportNames: collectProgramExportNames(getProgram(ast)),
|
|
365
|
+
resourcePath: task.resourcePath,
|
|
366
|
+
rootRoutePath: task.rootRoutePath,
|
|
335
367
|
});
|
|
336
|
-
|
|
337
|
-
if (invalidServerOnly.length > 0) {
|
|
338
|
-
const list = invalidServerOnly.map(e => `\`${e}\``).join(', ');
|
|
339
|
-
throw new Error(
|
|
340
|
-
`SPA Mode: ${invalidServerOnly.length} invalid route export(s) in ` +
|
|
341
|
-
`\`${relativePath}\`: ${list}. ` +
|
|
342
|
-
`See https://reactrouter.com/how-to/spa for more information.`
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (!isRootRoute && resolvedExportNames.includes('HydrateFallback')) {
|
|
347
|
-
throw new Error(
|
|
348
|
-
`SPA Mode: Invalid \`HydrateFallback\` export found in ` +
|
|
349
|
-
`\`${relativePath}\`. ` +
|
|
350
|
-
`\`HydrateFallback\` is only permitted on the root route in SPA Mode. ` +
|
|
351
|
-
`See https://reactrouter.com/how-to/spa for more information.`
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
368
|
}
|
|
355
369
|
|
|
356
370
|
const removedServerOnlyExports =
|