rsbuild-plugin-react-router 0.1.1 → 0.3.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 (99) hide show
  1. package/README.md +185 -204
  2. package/dist/451.js +1087 -0
  3. package/dist/bounded-cache.d.ts +1 -0
  4. package/dist/build-manifest.d.ts +1 -1
  5. package/dist/build-output-transforms.d.ts +30 -0
  6. package/dist/concurrency.d.ts +2 -0
  7. package/dist/config-imports.d.ts +3 -0
  8. package/dist/constants.d.ts +3 -0
  9. package/dist/dev-generation.d.ts +25 -0
  10. package/dist/dev-runtime-artifacts.d.ts +39 -0
  11. package/dist/dev-runtime-compilation.d.ts +32 -0
  12. package/dist/dev-runtime-controller.d.ts +14 -0
  13. package/dist/dev-runtime-session.d.ts +34 -0
  14. package/dist/dev-server.d.ts +11 -2
  15. package/dist/export-utils.d.ts +15 -7
  16. package/dist/index.cjs +3085 -1272
  17. package/dist/index.d.ts +4 -1
  18. package/dist/index.js +2039 -1299
  19. package/dist/lazy-compilation.d.ts +5 -0
  20. package/dist/manifest.d.ts +32 -9
  21. package/dist/modify-browser-manifest.d.ts +30 -13
  22. package/dist/parallel-route-transform-protocol.d.ts +25 -0
  23. package/dist/parallel-route-transform-worker.d.ts +1 -0
  24. package/dist/parallel-route-transform-worker.js +38 -0
  25. package/dist/parallel-route-transforms.d.ts +14 -0
  26. package/dist/performance.d.ts +26 -0
  27. package/dist/plugin-utils.d.ts +2 -12
  28. package/dist/prerender-build.d.ts +32 -0
  29. package/dist/prerender.d.ts +28 -2
  30. package/dist/react-router-config.d.ts +8 -1
  31. package/dist/route-artifacts.d.ts +33 -0
  32. package/dist/route-ast.d.ts +21 -0
  33. package/dist/route-chunks.d.ts +9 -1
  34. package/dist/route-component-transform.d.ts +5 -0
  35. package/dist/route-export-pruning.d.ts +6 -0
  36. package/dist/route-export-resolution.d.ts +5 -0
  37. package/dist/route-transform-tasks.d.ts +47 -0
  38. package/dist/route-watch.d.ts +27 -0
  39. package/dist/server-build-plan.d.ts +22 -0
  40. package/dist/server-utils.d.ts +3 -2
  41. package/dist/templates/entry.client.js +3 -3
  42. package/dist/templates/entry.server.cjs +11 -8
  43. package/dist/templates/entry.server.js +5 -5
  44. package/dist/typegen.d.ts +2 -0
  45. package/dist/types.d.ts +30 -12
  46. package/dist/virtual-modules.d.ts +2 -0
  47. package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
  48. package/dist/yuku.d.ts +15 -0
  49. package/package.json +26 -22
  50. package/src/bounded-cache.ts +18 -0
  51. package/src/build-manifest.ts +168 -0
  52. package/src/build-output-transforms.ts +273 -0
  53. package/src/concurrency.ts +34 -0
  54. package/src/config-imports.ts +45 -0
  55. package/src/constants.ts +91 -0
  56. package/src/dev-generation.ts +700 -0
  57. package/src/dev-runtime-artifacts.ts +207 -0
  58. package/src/dev-runtime-compilation.ts +92 -0
  59. package/src/dev-runtime-controller.ts +462 -0
  60. package/src/dev-runtime-session.ts +184 -0
  61. package/src/dev-server.ts +58 -0
  62. package/src/export-utils.ts +219 -0
  63. package/src/index.ts +1025 -0
  64. package/src/lazy-compilation.ts +94 -0
  65. package/src/manifest.ts +480 -0
  66. package/src/modify-browser-manifest.ts +245 -0
  67. package/src/parallel-route-transform-protocol.ts +35 -0
  68. package/src/parallel-route-transform-worker.ts +82 -0
  69. package/src/parallel-route-transforms.ts +332 -0
  70. package/src/performance.ts +254 -0
  71. package/src/plugin-utils.ts +82 -0
  72. package/src/prerender-build.ts +623 -0
  73. package/src/prerender.ts +367 -0
  74. package/src/react-router-config.ts +220 -0
  75. package/src/route-artifacts.ts +155 -0
  76. package/src/route-ast.ts +163 -0
  77. package/src/route-chunks.ts +857 -0
  78. package/src/route-component-transform.ts +314 -0
  79. package/src/route-config.ts +106 -0
  80. package/src/route-export-pruning.ts +668 -0
  81. package/src/route-export-resolution.ts +329 -0
  82. package/src/route-transform-tasks.ts +249 -0
  83. package/src/route-watch.ts +322 -0
  84. package/src/server-build-plan.ts +91 -0
  85. package/src/server-utils.ts +210 -0
  86. package/src/ssr-externals.ts +59 -0
  87. package/src/templates/context.ts +12 -0
  88. package/src/templates/entry.client.tsx +12 -0
  89. package/src/templates/entry.server.tsx +76 -0
  90. package/src/typegen.ts +49 -0
  91. package/src/types.ts +82 -0
  92. package/src/validation/validate-plugin-order.ts +76 -0
  93. package/src/virtual-modules.ts +30 -0
  94. package/src/warnings/warn-on-client-source-maps.ts +96 -0
  95. package/src/yuku.ts +67 -0
  96. package/dist/906.js +0 -1
  97. package/dist/946.js +0 -1
  98. package/dist/babel.d.ts +0 -8
  99. package/dist/rslib-runtime.js +0 -23
@@ -0,0 +1,857 @@
1
+ import {
2
+ Analyzer,
3
+ type Module,
4
+ type Symbol as YukuSymbol,
5
+ } from 'yuku-analyzer';
6
+ import { print } from 'yuku-codegen';
7
+ import { walk } from 'yuku-parser';
8
+ import { normalize, relative, resolve } from 'pathe';
9
+
10
+ type AnyNode = Record<string, any>;
11
+
12
+ export type RouteChunkExportName =
13
+ | 'clientAction'
14
+ | 'clientLoader'
15
+ | 'clientMiddleware'
16
+ | 'HydrateFallback';
17
+
18
+ export type RouteChunkName = 'main' | RouteChunkExportName;
19
+
20
+ export type RouteChunkConfig = {
21
+ splitRouteModules?: boolean | 'enforce';
22
+ appDirectory: string;
23
+ rootRouteFile: string;
24
+ };
25
+
26
+ type RouteChunkCacheEntry<T> = {
27
+ value: T;
28
+ version: string;
29
+ };
30
+
31
+ export type RouteChunkCache = Map<string, RouteChunkCacheEntry<unknown>>;
32
+
33
+ export type RouteChunkInfo = {
34
+ exportNames: string[];
35
+ hasRouteChunks: boolean;
36
+ hasRouteChunkByExportName: Record<RouteChunkExportName, boolean>;
37
+ chunkedExports: RouteChunkExportName[];
38
+ };
39
+
40
+ export const routeChunkExportNames: RouteChunkExportName[] = [
41
+ 'clientAction',
42
+ 'clientLoader',
43
+ 'clientMiddleware',
44
+ 'HydrateFallback',
45
+ ];
46
+
47
+ export const routeChunkNames: RouteChunkName[] = [
48
+ 'main',
49
+ ...routeChunkExportNames,
50
+ ];
51
+
52
+ export const mightContainRouteChunkExportName = (source: string): boolean =>
53
+ routeChunkExportNames.some(exportName => source.includes(exportName));
54
+
55
+ const createRouteChunkExportMap = (
56
+ getValue: (exportName: RouteChunkExportName) => boolean
57
+ ): Record<RouteChunkExportName, boolean> =>
58
+ Object.fromEntries(
59
+ routeChunkExportNames.map(exportName => [exportName, getValue(exportName)])
60
+ ) as Record<RouteChunkExportName, boolean>;
61
+
62
+ export const emptyRouteChunkSnippet = (): string => 'export {};';
63
+
64
+ const routeChunkQueryStringPrefix = '?route-chunk=';
65
+
66
+ const routeChunkQueryStrings: Record<RouteChunkName, string> = {
67
+ main: `${routeChunkQueryStringPrefix}main`,
68
+ clientAction: `${routeChunkQueryStringPrefix}clientAction`,
69
+ clientLoader: `${routeChunkQueryStringPrefix}clientLoader`,
70
+ clientMiddleware: `${routeChunkQueryStringPrefix}clientMiddleware`,
71
+ HydrateFallback: `${routeChunkQueryStringPrefix}HydrateFallback`,
72
+ };
73
+
74
+ const routeChunkEntrySuffix: Record<RouteChunkExportName, string> = {
75
+ clientAction: 'client-action',
76
+ clientLoader: 'client-loader',
77
+ clientMiddleware: 'client-middleware',
78
+ HydrateFallback: 'hydrate-fallback',
79
+ };
80
+
81
+ const invariant: (value: unknown, message: string) => asserts value = (
82
+ value,
83
+ message
84
+ ) => {
85
+ if (!value) {
86
+ throw new Error(message);
87
+ }
88
+ };
89
+
90
+ const getOrSetFromCache = <T>(
91
+ cache: RouteChunkCache,
92
+ key: string,
93
+ version: string,
94
+ getValue: () => T
95
+ ): T => {
96
+ const entry = cache.get(key) as RouteChunkCacheEntry<T> | undefined;
97
+ if (entry?.version === version) {
98
+ return entry.value;
99
+ }
100
+ const value = getValue();
101
+ cache.set(key, { value, version });
102
+ return value;
103
+ };
104
+
105
+ const hasCachedValue = (
106
+ cache: RouteChunkCache,
107
+ key: string,
108
+ version: string
109
+ ): boolean => cache.get(key)?.version === version;
110
+
111
+ type AnalyzedModule = {
112
+ module: Module;
113
+ // Dependency sets use these node identities. Consumers must shallow-copy
114
+ // any node whose children they narrow instead of mutating this cached AST.
115
+ program: AnyNode;
116
+ };
117
+
118
+ const analyzeCode = (
119
+ code: string,
120
+ cache: RouteChunkCache,
121
+ cacheKey: string
122
+ ): AnalyzedModule => {
123
+ return getOrSetFromCache(cache, `${cacheKey}::analyzeCode`, code, () => {
124
+ const analyzer = new Analyzer();
125
+ const module = analyzer.addFile(cacheKey, code, {
126
+ lang: 'tsx',
127
+ sourceType: 'module',
128
+ attachComments: true,
129
+ });
130
+ const errors = module.diagnostics.filter(
131
+ diagnostic => diagnostic.severity === 'error'
132
+ );
133
+ if (errors.length > 0) {
134
+ throw new Error(errors.map(error => error.message).join('\n'));
135
+ }
136
+ return { module, program: module.ast as AnyNode };
137
+ });
138
+ };
139
+
140
+ type ExportDependencies = {
141
+ topLevelStatements: Set<AnyNode>;
142
+ topLevelNonModuleStatements: Set<AnyNode>;
143
+ importedIdentifierNames: Set<string>;
144
+ exportedVariableDeclarators: Set<AnyNode>;
145
+ };
146
+
147
+ const getTopLevelStatementForNode = (
148
+ module: Module,
149
+ node: AnyNode
150
+ ): AnyNode => {
151
+ let current: AnyNode = node;
152
+ let parent = module.parentOf(current as never) as AnyNode | null;
153
+ while (parent && parent.type !== 'Program') {
154
+ current = parent;
155
+ parent = module.parentOf(current as never) as AnyNode | null;
156
+ }
157
+ invariant(parent?.type === 'Program', 'Expected node to be within Program');
158
+ return current;
159
+ };
160
+
161
+ const getVariableDeclaratorForNode = (
162
+ module: Module,
163
+ node: AnyNode
164
+ ): AnyNode | null => {
165
+ let current: AnyNode | null = node;
166
+ while (current) {
167
+ if (current.type === 'VariableDeclarator') {
168
+ return current;
169
+ }
170
+ current = module.parentOf(current as never) as AnyNode | null;
171
+ }
172
+ return null;
173
+ };
174
+
175
+ const getExportedName = (exported: AnyNode): string => {
176
+ if (exported.type === 'Identifier') {
177
+ return exported.name;
178
+ }
179
+ return String(exported.value);
180
+ };
181
+
182
+ const setsIntersect = <T>(set1: Set<T>, set2: Set<T>) => {
183
+ let smallerSet = set1;
184
+ let largerSet = set2;
185
+ if (set1.size > set2.size) {
186
+ smallerSet = set2;
187
+ largerSet = set1;
188
+ }
189
+ for (const element of smallerSet) {
190
+ if (largerSet.has(element)) {
191
+ return true;
192
+ }
193
+ }
194
+ return false;
195
+ };
196
+
197
+ const getExportDependencies = (
198
+ code: string,
199
+ cache: RouteChunkCache,
200
+ cacheKey: string
201
+ ): Map<string, ExportDependencies> => {
202
+ return getOrSetFromCache(
203
+ cache,
204
+ `${cacheKey}::getExportDependencies`,
205
+ code,
206
+ () => {
207
+ const { module } = analyzeCode(code, cache, cacheKey);
208
+ const exportDependencies = new Map<string, ExportDependencies>();
209
+ const topLevelStatementCache = new Map<AnyNode, AnyNode>();
210
+ const variableDeclaratorCache = new Map<AnyNode, AnyNode | null>();
211
+ const getCachedTopLevelStatementForNode = (node: AnyNode): AnyNode => {
212
+ const cached = topLevelStatementCache.get(node);
213
+ if (cached) {
214
+ return cached;
215
+ }
216
+ const statement = getTopLevelStatementForNode(module, node);
217
+ topLevelStatementCache.set(node, statement);
218
+ return statement;
219
+ };
220
+
221
+ const getCachedVariableDeclaratorForNode = (
222
+ node: AnyNode
223
+ ): AnyNode | null => {
224
+ if (variableDeclaratorCache.has(node)) {
225
+ return variableDeclaratorCache.get(node) ?? null;
226
+ }
227
+ const declarator = getVariableDeclaratorForNode(module, node);
228
+ variableDeclaratorCache.set(node, declarator);
229
+ return declarator;
230
+ };
231
+
232
+ const addCachedTopLevelStatement = (
233
+ dependencies: ExportDependencies,
234
+ node: AnyNode
235
+ ) => {
236
+ const statement = getCachedTopLevelStatementForNode(node);
237
+ dependencies.topLevelStatements.add(statement);
238
+ if (
239
+ statement.type !== 'ImportDeclaration' &&
240
+ !statement.type.startsWith('Export')
241
+ ) {
242
+ dependencies.topLevelNonModuleStatements.add(statement);
243
+ }
244
+ return statement;
245
+ };
246
+
247
+ const handleExport = (
248
+ exportName: string,
249
+ exportNode: AnyNode,
250
+ localSymbol: YukuSymbol | null
251
+ ) => {
252
+ const dependencies: ExportDependencies = {
253
+ topLevelStatements: new Set(),
254
+ topLevelNonModuleStatements: new Set(),
255
+ importedIdentifierNames: new Set(),
256
+ exportedVariableDeclarators: new Set(),
257
+ };
258
+ const visitedSymbols = new Set<YukuSymbol>();
259
+ const scannedNodes = new Set<AnyNode>();
260
+
261
+ const scanNode = (node: AnyNode) => {
262
+ if (scannedNodes.has(node)) {
263
+ return;
264
+ }
265
+ scannedNodes.add(node);
266
+ walk(node as any, {
267
+ Identifier(node: AnyNode) {
268
+ const reference = module.referenceOf(node as never);
269
+ if (reference?.symbol) {
270
+ visitSymbol(reference.symbol);
271
+ }
272
+ },
273
+ });
274
+ };
275
+
276
+ const visitSymbol = (symbol: YukuSymbol) => {
277
+ if (visitedSymbols.has(symbol)) {
278
+ return;
279
+ }
280
+ visitedSymbols.add(symbol);
281
+
282
+ for (const declaration of symbol.declarations as AnyNode[]) {
283
+ const statement = addCachedTopLevelStatement(
284
+ dependencies,
285
+ declaration
286
+ );
287
+ if (statement.type === 'ImportDeclaration') {
288
+ dependencies.importedIdentifierNames.add(symbol.name);
289
+ }
290
+ const declarator = getCachedVariableDeclaratorForNode(declaration);
291
+ if (
292
+ declarator &&
293
+ getCachedTopLevelStatementForNode(declarator).type ===
294
+ 'ExportNamedDeclaration'
295
+ ) {
296
+ dependencies.exportedVariableDeclarators.add(declarator);
297
+ }
298
+ scanNode(declarator ?? statement);
299
+ }
300
+
301
+ for (const reference of symbol.references as any[]) {
302
+ const statement = addCachedTopLevelStatement(
303
+ dependencies,
304
+ reference.node
305
+ );
306
+ const declarator = getCachedVariableDeclaratorForNode(
307
+ reference.node
308
+ );
309
+ scanNode(declarator ?? statement);
310
+ }
311
+ };
312
+
313
+ addCachedTopLevelStatement(dependencies, exportNode);
314
+
315
+ if (localSymbol) {
316
+ visitSymbol(localSymbol);
317
+ } else {
318
+ const statement = getCachedTopLevelStatementForNode(exportNode);
319
+ scanNode(statement);
320
+ }
321
+
322
+ exportDependencies.set(exportName, dependencies);
323
+ };
324
+
325
+ for (const exp of module.exports as any[]) {
326
+ if (exp.typeOnly || exp.isStar || exp.isExportEquals) {
327
+ continue;
328
+ }
329
+ handleExport(exp.name, exp.node as AnyNode, exp.local ?? null);
330
+ }
331
+
332
+ return exportDependencies;
333
+ }
334
+ );
335
+ };
336
+
337
+ const isExportChunkable = (
338
+ exportName: string,
339
+ exportDependencies: Map<string, ExportDependencies>
340
+ ) => {
341
+ const dependencies = exportDependencies.get(exportName);
342
+ if (!dependencies) {
343
+ return false;
344
+ }
345
+ for (const [currentExportName, currentDependencies] of exportDependencies) {
346
+ if (currentExportName === exportName) {
347
+ continue;
348
+ }
349
+ if (
350
+ setsIntersect(
351
+ currentDependencies.topLevelNonModuleStatements,
352
+ dependencies.topLevelNonModuleStatements
353
+ )
354
+ ) {
355
+ return false;
356
+ }
357
+ }
358
+ if (dependencies.exportedVariableDeclarators.size > 1) {
359
+ return false;
360
+ }
361
+ if (dependencies.exportedVariableDeclarators.size > 0) {
362
+ for (const [currentExportName, currentDependencies] of exportDependencies) {
363
+ if (currentExportName === exportName) {
364
+ continue;
365
+ }
366
+ if (
367
+ setsIntersect(
368
+ currentDependencies.exportedVariableDeclarators,
369
+ dependencies.exportedVariableDeclarators
370
+ )
371
+ ) {
372
+ return false;
373
+ }
374
+ }
375
+ }
376
+ return true;
377
+ };
378
+
379
+ const getChunkableExportMap = (
380
+ code: string,
381
+ cache: RouteChunkCache,
382
+ cacheKey: string
383
+ ): Record<RouteChunkExportName, boolean> =>
384
+ getOrSetFromCache(cache, `${cacheKey}::getChunkableExportMap`, code, () => {
385
+ const exportDependencies = getExportDependencies(code, cache, cacheKey);
386
+ return createRouteChunkExportMap(exportName =>
387
+ isExportChunkable(exportName, exportDependencies)
388
+ );
389
+ });
390
+
391
+ const hasChunkableExport = (
392
+ code: string,
393
+ exportName: string,
394
+ cache: RouteChunkCache,
395
+ cacheKey: string
396
+ ) =>
397
+ (routeChunkExportNames as string[]).includes(exportName)
398
+ ? getChunkableExportMap(code, cache, cacheKey)[
399
+ exportName as RouteChunkExportName
400
+ ]
401
+ : false;
402
+
403
+ const generateCode = (program: AnyNode): string | undefined => {
404
+ if (program.body.length === 0) {
405
+ return undefined;
406
+ }
407
+ const result = print(program as any, { comments: true });
408
+ if (result.errors.length > 0) {
409
+ throw new Error(result.errors.map(error => error.message).join('\n'));
410
+ }
411
+ return result.code;
412
+ };
413
+
414
+ const filterImportSpecifiers = (
415
+ node: AnyNode,
416
+ shouldKeep: (importedName: string) => boolean
417
+ ) => {
418
+ if (node.specifiers.length === 0) {
419
+ return node;
420
+ }
421
+ const specifiers = node.specifiers.filter((specifier: AnyNode) =>
422
+ shouldKeep(specifier.local.name)
423
+ );
424
+ return specifiers.length > 0 ? { ...node, specifiers } : null;
425
+ };
426
+
427
+ const getChunkedExport = (
428
+ code: string,
429
+ exportName: string,
430
+ cache: RouteChunkCache,
431
+ cacheKey: string
432
+ ): string | undefined => {
433
+ return getOrSetFromCache(
434
+ cache,
435
+ `${cacheKey}::getChunkedExport::${exportName}`,
436
+ code,
437
+ () => {
438
+ if (!hasChunkableExport(code, exportName, cache, cacheKey)) {
439
+ return undefined;
440
+ }
441
+ const exportDependencies = getExportDependencies(code, cache, cacheKey);
442
+ const dependencies = exportDependencies.get(exportName);
443
+ invariant(dependencies, 'Expected export to have dependencies');
444
+
445
+ const program = analyzeCode(code, cache, cacheKey).program;
446
+ const body = program.body
447
+ .filter((node: AnyNode) => dependencies.topLevelStatements.has(node))
448
+ .map((node: AnyNode) => {
449
+ if (node.type !== 'ImportDeclaration') {
450
+ return node;
451
+ }
452
+ if (dependencies.importedIdentifierNames.size === 0) {
453
+ return null;
454
+ }
455
+ return filterImportSpecifiers(node, importedName =>
456
+ dependencies.importedIdentifierNames.has(importedName)
457
+ );
458
+ })
459
+ .map((node: AnyNode | null) => {
460
+ if (!node || !node.type.startsWith('Export')) {
461
+ return node;
462
+ }
463
+ if (node.type === 'ExportAllDeclaration') {
464
+ return null;
465
+ }
466
+ if (node.type === 'ExportDefaultDeclaration') {
467
+ return exportName === 'default' ? node : null;
468
+ }
469
+ const { declaration } = node;
470
+ if (declaration?.type === 'VariableDeclaration') {
471
+ const declarations = declaration.declarations.filter(
472
+ (declarationNode: AnyNode) =>
473
+ dependencies.exportedVariableDeclarators.has(declarationNode)
474
+ );
475
+ return declarations.length > 0
476
+ ? {
477
+ ...node,
478
+ declaration: { ...declaration, declarations },
479
+ }
480
+ : null;
481
+ }
482
+ if (
483
+ declaration?.type === 'FunctionDeclaration' ||
484
+ declaration?.type === 'ClassDeclaration'
485
+ ) {
486
+ return declaration.id?.name === exportName ? node : null;
487
+ }
488
+ if (node.type === 'ExportNamedDeclaration') {
489
+ const specifiers = node.specifiers.filter(
490
+ (specifier: AnyNode) =>
491
+ getExportedName(specifier.exported) === exportName
492
+ );
493
+ return specifiers.length > 0 ? { ...node, specifiers } : null;
494
+ }
495
+ throw new Error('Unknown export node type');
496
+ })
497
+ .filter(Boolean) as AnyNode[];
498
+
499
+ return generateCode({ ...program, body });
500
+ }
501
+ );
502
+ };
503
+
504
+ const getChunkedExportCacheKey = (
505
+ cacheKey: string,
506
+ exportName: RouteChunkExportName
507
+ ) => `${cacheKey}::getChunkedExport::${exportName}`;
508
+
509
+ const hasCachedChunkedExport = (
510
+ code: string,
511
+ exportName: RouteChunkExportName,
512
+ cache: RouteChunkCache,
513
+ cacheKey: string
514
+ ): boolean =>
515
+ hasCachedValue(cache, getChunkedExportCacheKey(cacheKey, exportName), code);
516
+
517
+ const omitChunkedExports = (
518
+ code: string,
519
+ exportNames: string[],
520
+ cache: RouteChunkCache,
521
+ cacheKey: string
522
+ ): string | undefined => {
523
+ return getOrSetFromCache(
524
+ cache,
525
+ `${cacheKey}::omitChunkedExports::${exportNames.join(',')}`,
526
+ code,
527
+ () => {
528
+ const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey);
529
+ const exportNameSet = new Set(exportNames);
530
+ const isOmitted = (exportName: string) =>
531
+ exportNameSet.has(exportName) &&
532
+ Boolean(chunkableExportMap[exportName as RouteChunkExportName]);
533
+ const isRetained = (exportName: string) => !isOmitted(exportName);
534
+
535
+ const exportDependencies = getExportDependencies(code, cache, cacheKey);
536
+ const allExportNames = Array.from(exportDependencies.keys());
537
+ const omittedExportNames = allExportNames.filter(isOmitted);
538
+ const retainedExportNames = allExportNames.filter(isRetained);
539
+
540
+ const omittedStatements = new Set<AnyNode>();
541
+ const omittedExportedVariableDeclarators = new Set<AnyNode>();
542
+ const retainedImportedIdentifierNames = new Set<string>();
543
+ const omittedImportedIdentifierNames = new Set<string>();
544
+
545
+ for (const omittedExportName of omittedExportNames) {
546
+ const dependencies = exportDependencies.get(omittedExportName);
547
+ invariant(
548
+ dependencies,
549
+ `Expected dependencies for ${omittedExportName}`
550
+ );
551
+ for (const statement of dependencies.topLevelNonModuleStatements) {
552
+ omittedStatements.add(statement);
553
+ }
554
+ for (const declarator of dependencies.exportedVariableDeclarators) {
555
+ omittedExportedVariableDeclarators.add(declarator);
556
+ }
557
+ for (const importedName of dependencies.importedIdentifierNames) {
558
+ omittedImportedIdentifierNames.add(importedName);
559
+ }
560
+ }
561
+
562
+ for (const retainedExportName of retainedExportNames) {
563
+ const dependencies = exportDependencies.get(retainedExportName);
564
+ if (!dependencies) {
565
+ continue;
566
+ }
567
+ for (const importedName of dependencies.importedIdentifierNames) {
568
+ retainedImportedIdentifierNames.add(importedName);
569
+ }
570
+ }
571
+
572
+ const program = analyzeCode(code, cache, cacheKey).program;
573
+ const body = program.body
574
+ .filter((node: AnyNode) => !omittedStatements.has(node))
575
+ .map((node: AnyNode) => {
576
+ if (node.type !== 'ImportDeclaration') {
577
+ return node;
578
+ }
579
+ return filterImportSpecifiers(node, importedName => {
580
+ if (retainedImportedIdentifierNames.has(importedName)) return true;
581
+ if (omittedImportedIdentifierNames.has(importedName)) return false;
582
+ return true;
583
+ });
584
+ })
585
+ .map((node: AnyNode | null) => {
586
+ if (!node || !node.type.startsWith('Export')) {
587
+ return node;
588
+ }
589
+ if (node.type === 'ExportAllDeclaration') {
590
+ return node;
591
+ }
592
+ if (node.type === 'ExportDefaultDeclaration') {
593
+ return isOmitted('default') ? null : node;
594
+ }
595
+ if (node.declaration?.type === 'VariableDeclaration') {
596
+ const declarations = node.declaration.declarations.filter(
597
+ (declarationNode: AnyNode) =>
598
+ !omittedExportedVariableDeclarators.has(declarationNode)
599
+ );
600
+ return declarations.length > 0
601
+ ? {
602
+ ...node,
603
+ declaration: { ...node.declaration, declarations },
604
+ }
605
+ : null;
606
+ }
607
+ if (
608
+ node.declaration?.type === 'FunctionDeclaration' ||
609
+ node.declaration?.type === 'ClassDeclaration'
610
+ ) {
611
+ return isOmitted(node.declaration.id.name) ? null : node;
612
+ }
613
+ if (node.type === 'ExportNamedDeclaration') {
614
+ const specifiers = node.specifiers.filter((specifier: AnyNode) => {
615
+ const exportedName = getExportedName(specifier.exported);
616
+ return !isOmitted(exportedName);
617
+ });
618
+ return specifiers.length > 0 || node.declaration
619
+ ? { ...node, specifiers }
620
+ : null;
621
+ }
622
+ throw new Error('Unknown node type');
623
+ })
624
+ .filter(Boolean) as AnyNode[];
625
+
626
+ return generateCode({ ...program, body });
627
+ }
628
+ );
629
+ };
630
+
631
+ const precomputeChunkedExports = (
632
+ code: string,
633
+ cache: RouteChunkCache,
634
+ cacheKey: string
635
+ ) => {
636
+ const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey);
637
+ for (const exportName of routeChunkExportNames) {
638
+ if (!chunkableExportMap[exportName]) {
639
+ continue;
640
+ }
641
+ if (!hasCachedChunkedExport(code, exportName, cache, cacheKey)) {
642
+ getChunkedExport(code, exportName, cache, cacheKey);
643
+ }
644
+ }
645
+ };
646
+
647
+ export const detectRouteChunks = (
648
+ code: string,
649
+ cache: RouteChunkCache | undefined,
650
+ cacheKey: string
651
+ ): RouteChunkInfo => {
652
+ const analysisCache = cache ?? new Map();
653
+ const exportDependencies = getExportDependencies(
654
+ code,
655
+ analysisCache,
656
+ cacheKey
657
+ );
658
+ const hasRouteChunkByExportName = getChunkableExportMap(
659
+ code,
660
+ analysisCache,
661
+ cacheKey
662
+ );
663
+ const chunkedExports = Object.entries(hasRouteChunkByExportName)
664
+ .filter(([, isChunked]) => isChunked)
665
+ .map(([exportName]) => exportName as RouteChunkExportName);
666
+ const hasRouteChunks = chunkedExports.length > 0;
667
+ return {
668
+ exportNames: Array.from(exportDependencies.keys()),
669
+ hasRouteChunks,
670
+ hasRouteChunkByExportName,
671
+ chunkedExports,
672
+ };
673
+ };
674
+
675
+ export const getRouteChunkCode: (
676
+ code: string,
677
+ chunkName: RouteChunkName,
678
+ cache: RouteChunkCache | undefined,
679
+ cacheKey: string
680
+ ) => string | undefined = (
681
+ code: string,
682
+ chunkName: RouteChunkName,
683
+ cache: RouteChunkCache | undefined,
684
+ cacheKey: string
685
+ ) => {
686
+ const analysisCache = cache ?? new Map();
687
+ if (chunkName === 'main') {
688
+ return omitChunkedExports(
689
+ code,
690
+ routeChunkExportNames,
691
+ analysisCache,
692
+ cacheKey
693
+ );
694
+ }
695
+ if (!hasCachedChunkedExport(code, chunkName, analysisCache, cacheKey)) {
696
+ precomputeChunkedExports(code, analysisCache, cacheKey);
697
+ }
698
+ return getChunkedExport(code, chunkName, analysisCache, cacheKey);
699
+ };
700
+
701
+ export const getRouteChunkModuleId = (
702
+ filePath: string,
703
+ chunkName: RouteChunkName
704
+ ) => `${filePath}${routeChunkQueryStrings[chunkName]}`;
705
+
706
+ export const isRouteChunkModuleId: (id: string) => boolean = (id: string) =>
707
+ getRouteChunkNameFromModuleId(id) !== null;
708
+
709
+ const isRouteChunkName = (name: string): name is RouteChunkName =>
710
+ name === 'main' || (routeChunkExportNames as string[]).includes(name);
711
+
712
+ export const getRouteChunkNameFromModuleId = (
713
+ id: string
714
+ ): RouteChunkName | null => {
715
+ const queryIndex = id.indexOf(routeChunkQueryStringPrefix);
716
+ if (queryIndex === -1) {
717
+ return null;
718
+ }
719
+ const chunkNameStart = queryIndex + routeChunkQueryStringPrefix.length;
720
+ const chunkNameEnd = id.indexOf('&', chunkNameStart);
721
+ const chunkName = id.slice(
722
+ chunkNameStart,
723
+ chunkNameEnd === -1 ? undefined : chunkNameEnd
724
+ );
725
+ if (!isRouteChunkName(chunkName)) {
726
+ return null;
727
+ }
728
+ return chunkName;
729
+ };
730
+
731
+ const normalizeRelativeFilePath = (file: string, appDirectory: string) => {
732
+ const fullPath = resolve(appDirectory, file);
733
+ const relativePath = relative(appDirectory, fullPath);
734
+ return normalize(relativePath).split('?')[0];
735
+ };
736
+
737
+ const isRootRouteModuleId = (config: RouteChunkConfig, id: string) =>
738
+ normalizeRelativeFilePath(id, config.appDirectory) === config.rootRouteFile;
739
+
740
+ export const shouldAnalyzeRouteChunks = (
741
+ config: RouteChunkConfig,
742
+ id: string,
743
+ code: string
744
+ ): boolean =>
745
+ Boolean(config.splitRouteModules) &&
746
+ mightContainRouteChunkExportName(code) &&
747
+ !isRootRouteModuleId(config, id);
748
+
749
+ export const createEmptyRouteChunkByExportName = (): Record<
750
+ RouteChunkExportName,
751
+ boolean
752
+ > => createRouteChunkExportMap(() => false);
753
+
754
+ export const buildEnforceChunkValidity = (
755
+ exportNames: readonly string[]
756
+ ): Record<RouteChunkExportName, boolean> => {
757
+ const exportNameSet = new Set(exportNames);
758
+ return createRouteChunkExportMap(
759
+ exportName => !exportNameSet.has(exportName)
760
+ );
761
+ };
762
+
763
+ export const buildManifestChunkValidity = (
764
+ exportNames: ReadonlySet<string>,
765
+ hasRouteChunkByExportName: Readonly<Record<RouteChunkExportName, boolean>>
766
+ ): Record<RouteChunkExportName, boolean> =>
767
+ createRouteChunkExportMap(
768
+ exportName =>
769
+ !exportNames.has(exportName) || hasRouteChunkByExportName[exportName]
770
+ );
771
+
772
+ export const detectRouteChunksIfEnabled: (
773
+ cache: RouteChunkCache | undefined,
774
+ config: RouteChunkConfig,
775
+ id: string,
776
+ code: string
777
+ ) => Promise<RouteChunkInfo> = async (
778
+ cache: RouteChunkCache | undefined,
779
+ config: RouteChunkConfig,
780
+ id: string,
781
+ code: string
782
+ ) => {
783
+ const noRouteChunks = (): RouteChunkInfo => ({
784
+ exportNames: [],
785
+ chunkedExports: [] as RouteChunkExportName[],
786
+ hasRouteChunks: false,
787
+ hasRouteChunkByExportName: createEmptyRouteChunkByExportName(),
788
+ });
789
+
790
+ if (!shouldAnalyzeRouteChunks(config, id, code)) {
791
+ return noRouteChunks();
792
+ }
793
+
794
+ const cacheKey = normalizeRelativeFilePath(id, config.appDirectory);
795
+ return detectRouteChunks(code, cache, cacheKey);
796
+ };
797
+
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 (
805
+ cache: RouteChunkCache | undefined,
806
+ config: RouteChunkConfig,
807
+ id: string,
808
+ chunkName: RouteChunkName,
809
+ code: string
810
+ ) => {
811
+ if (!config.splitRouteModules) {
812
+ return null;
813
+ }
814
+ if (chunkName === 'main') {
815
+ if (!mightContainRouteChunkExportName(code)) {
816
+ return code;
817
+ }
818
+ } else if (!code.includes(chunkName)) {
819
+ return null;
820
+ }
821
+ const cacheKey = normalizeRelativeFilePath(id, config.appDirectory);
822
+ return getRouteChunkCode(code, chunkName, cache, cacheKey) ?? null;
823
+ };
824
+
825
+ export const validateRouteChunks: (args: {
826
+ config: RouteChunkConfig;
827
+ id: string;
828
+ valid: Record<RouteChunkExportName, boolean>;
829
+ }) => void = ({ config, id, valid }) => {
830
+ if (isRootRouteModuleId(config, id)) {
831
+ return;
832
+ }
833
+ const invalidChunks = Object.entries(valid)
834
+ .filter(([, isValid]) => !isValid)
835
+ .map(([chunkName]) => chunkName);
836
+ if (invalidChunks.length === 0) {
837
+ return;
838
+ }
839
+ const plural = invalidChunks.length > 1;
840
+ throw new Error(
841
+ [
842
+ `Error splitting route module: ${normalizeRelativeFilePath(
843
+ id,
844
+ config.appDirectory
845
+ )}`,
846
+ invalidChunks.map(name => `- ${name}`).join('\n'),
847
+ `${plural ? 'These exports' : 'This export'} could not be split into ${
848
+ plural ? 'their own chunks' : 'its own chunk'
849
+ } because ${plural ? 'they share' : 'it shares'} code with other exports. You should extract any shared code into its own module and then import it within the route module.`,
850
+ ].join('\n\n')
851
+ );
852
+ };
853
+
854
+ export const getRouteChunkEntryName = (
855
+ routeId: string,
856
+ chunkName: RouteChunkExportName
857
+ ) => `${routeId}-${routeChunkEntrySuffix[chunkName]}`;