@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.120

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 (87) hide show
  1. package/dist/cjs/cli/index.js +47 -9
  2. package/dist/cjs/cli/routeSplitting.js +87 -0
  3. package/dist/cjs/cli/tanstackTypes.js +230 -63
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/DefaultNotFound.js +9 -5
  6. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  7. package/dist/cjs/runtime/dataMutation.js +9 -5
  8. package/dist/cjs/runtime/hooks.js +9 -5
  9. package/dist/cjs/runtime/hydrationBoundary.js +48 -0
  10. package/dist/cjs/runtime/index.js +330 -74
  11. package/dist/cjs/runtime/lifecycle.js +15 -11
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +203 -98
  14. package/dist/cjs/runtime/plugin.node.js +38 -16
  15. package/dist/cjs/runtime/plugin.worker.js +53 -0
  16. package/dist/cjs/runtime/prefetchLink.js +10 -6
  17. package/dist/cjs/runtime/routeTree.js +81 -17
  18. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  19. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  20. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  21. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  22. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  23. package/dist/cjs/runtime/rsc/client.js +9 -5
  24. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  25. package/dist/cjs/runtime/rsc/index.js +9 -5
  26. package/dist/cjs/runtime/rsc/payloadRouter.js +9 -5
  27. package/dist/cjs/runtime/rsc/server.js +9 -5
  28. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  29. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  30. package/dist/cjs/runtime/types.js +31 -1
  31. package/dist/cjs/runtime/utils.js +9 -5
  32. package/dist/cjs/runtime.js +9 -5
  33. package/dist/esm/cli/index.mjs +28 -6
  34. package/dist/esm/cli/routeSplitting.mjs +43 -0
  35. package/dist/esm/cli/tanstackTypes.mjs +219 -59
  36. package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
  37. package/dist/esm/runtime/index.mjs +3 -2
  38. package/dist/esm/runtime/outlet.mjs +17 -0
  39. package/dist/esm/runtime/plugin.mjs +197 -96
  40. package/dist/esm/runtime/plugin.node.mjs +30 -12
  41. package/dist/esm/runtime/plugin.worker.mjs +1 -0
  42. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  43. package/dist/esm/runtime/routeTree.mjs +73 -13
  44. package/dist/esm/runtime/types.mjs +7 -0
  45. package/dist/esm-node/cli/index.mjs +28 -6
  46. package/dist/esm-node/cli/routeSplitting.mjs +44 -0
  47. package/dist/esm-node/cli/tanstackTypes.mjs +219 -59
  48. package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
  49. package/dist/esm-node/runtime/index.mjs +3 -2
  50. package/dist/esm-node/runtime/outlet.mjs +18 -0
  51. package/dist/esm-node/runtime/plugin.mjs +197 -96
  52. package/dist/esm-node/runtime/plugin.node.mjs +30 -12
  53. package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
  54. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  55. package/dist/esm-node/runtime/routeTree.mjs +73 -13
  56. package/dist/esm-node/runtime/types.mjs +7 -0
  57. package/dist/types/cli/index.d.ts +7 -1
  58. package/dist/types/cli/routeSplitting.d.ts +29 -0
  59. package/dist/types/cli/tanstackTypes.d.ts +9 -0
  60. package/dist/types/runtime/hooks.d.ts +9 -24
  61. package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
  62. package/dist/types/runtime/index.d.ts +5 -2
  63. package/dist/types/runtime/outlet.d.ts +2 -0
  64. package/dist/types/runtime/plugin.d.ts +1 -1
  65. package/dist/types/runtime/plugin.node.d.ts +1 -1
  66. package/dist/types/runtime/plugin.worker.d.ts +1 -0
  67. package/dist/types/runtime/types.d.ts +7 -0
  68. package/package.json +20 -20
  69. package/src/cli/index.ts +59 -2
  70. package/src/cli/routeSplitting.ts +81 -0
  71. package/src/cli/tanstackTypes.ts +347 -67
  72. package/src/runtime/hydrationBoundary.tsx +12 -0
  73. package/src/runtime/index.tsx +107 -2
  74. package/src/runtime/outlet.tsx +48 -0
  75. package/src/runtime/plugin.node.tsx +58 -8
  76. package/src/runtime/plugin.tsx +372 -157
  77. package/src/runtime/plugin.worker.tsx +4 -0
  78. package/src/runtime/prefetchLink.tsx +1 -1
  79. package/src/runtime/routeTree.ts +194 -23
  80. package/src/runtime/ssr-shim.d.ts +1 -3
  81. package/src/runtime/types.ts +13 -0
  82. package/tests/router/cli.test.ts +315 -0
  83. package/tests/router/fastDefaults.test.ts +25 -0
  84. package/tests/router/hydrationBoundary.test.tsx +23 -0
  85. package/tests/router/prefetchLink.test.tsx +43 -7
  86. package/tests/router/routeTree.test.ts +416 -1
  87. package/tests/router/tanstackTypes.test.ts +415 -1
@@ -0,0 +1,81 @@
1
+ export type TanstackRouteCodeSplittingOption =
2
+ | boolean
3
+ | {
4
+ enabled?: boolean;
5
+ };
6
+
7
+ export type TanstackRsbuildRouteSplittingProfile = {
8
+ defaultConfig: {
9
+ output: {
10
+ splitRouteChunks: boolean;
11
+ };
12
+ };
13
+ modernRouteChunks: {
14
+ enabled: boolean;
15
+ owner: 'modern';
16
+ };
17
+ builderChunkSplit: {
18
+ owner: 'modern-rsbuild';
19
+ preserved: true;
20
+ };
21
+ tanstackStartRspackSplitter: {
22
+ compatible: boolean;
23
+ reason: string;
24
+ clientDeleteNodes: string[];
25
+ routeFactoryCalls: string[];
26
+ };
27
+ };
28
+
29
+ const TANSTACK_START_ROUTE_FACTORY_CALLS = [
30
+ 'createFileRoute',
31
+ 'createRootRoute',
32
+ 'createRootRouteWithContext',
33
+ ] as const;
34
+
35
+ const TANSTACK_START_ROUTE_FACTORY_REGEX =
36
+ /\b(createFileRoute|createRootRoute|createRootRouteWithContext)\s*(?:<|\()/;
37
+
38
+ export function isTanstackStartRouteModuleSource(source: string) {
39
+ return TANSTACK_START_ROUTE_FACTORY_REGEX.test(source);
40
+ }
41
+
42
+ export function resolveTanstackRouteCodeSplittingEnabled(
43
+ option?: TanstackRouteCodeSplittingOption,
44
+ ) {
45
+ if (typeof option === 'boolean') {
46
+ return option;
47
+ }
48
+
49
+ return option?.enabled ?? true;
50
+ }
51
+
52
+ export function createTanstackRsbuildRouteSplittingProfile(opts: {
53
+ routeCodeSplitting?: TanstackRouteCodeSplittingOption;
54
+ }): TanstackRsbuildRouteSplittingProfile {
55
+ return {
56
+ defaultConfig: {
57
+ output: {
58
+ splitRouteChunks: resolveTanstackRouteCodeSplittingEnabled(
59
+ opts.routeCodeSplitting,
60
+ ),
61
+ },
62
+ },
63
+ modernRouteChunks: {
64
+ enabled: resolveTanstackRouteCodeSplittingEnabled(
65
+ opts.routeCodeSplitting,
66
+ ),
67
+ owner: 'modern',
68
+ },
69
+ builderChunkSplit: {
70
+ owner: 'modern-rsbuild',
71
+ preserved: true,
72
+ },
73
+ tanstackStartRspackSplitter: {
74
+ compatible: false,
75
+ reason:
76
+ 'TanStack Start Rsbuild route splitting is tied to TanStack file-route factory modules; Modern generates TanStack route trees from Modern route metadata and owns route chunking through output.splitRouteChunks.',
77
+ clientDeleteNodes: ['ssr', 'server', 'headers'],
78
+ routeFactoryCalls: [...TANSTACK_START_ROUTE_FACTORY_CALLS],
79
+ },
80
+ };
81
+ }
@@ -86,6 +86,17 @@ function pickModernLoaderModule(route: NestedRouteForCli | PageRoute) {
86
86
  return { loaderPath, inline };
87
87
  }
88
88
 
89
+ function pickRouteSearchContractModules(route: NestedRouteForCli | PageRoute) {
90
+ const validateSearchPath = (route as any).validateSearch;
91
+ const loaderDepsPath = (route as any).loaderDeps;
92
+
93
+ return {
94
+ validateSearchPath:
95
+ typeof validateSearchPath === 'string' ? validateSearchPath : null,
96
+ loaderDepsPath: typeof loaderDepsPath === 'string' ? loaderDepsPath : null,
97
+ };
98
+ }
99
+
89
100
  function isPathlessLayout(route: NestedRouteForCli | PageRoute) {
90
101
  return (
91
102
  (route as any).type === 'nested' &&
@@ -126,6 +137,137 @@ function createRouteStaticDataSnippet(opts: {
126
137
  )}\n }),`;
127
138
  }
128
139
 
140
+ const LOCALE_PARAM_SEGMENTS = new Set([
141
+ ':lang',
142
+ ':locale',
143
+ ':language',
144
+ '$lang',
145
+ '$locale',
146
+ '$language',
147
+ ]);
148
+
149
+ type CanonicalAwareRoute = (NestedRouteForCli | PageRoute) & {
150
+ modernCanonicalPath?: string;
151
+ index?: boolean;
152
+ isRoot?: boolean;
153
+ children?: CanonicalAwareRoute[];
154
+ };
155
+
156
+ function paramsTypeForCanonicalPath(canonicalPath: string): string {
157
+ const fields: string[] = [];
158
+
159
+ for (const segment of canonicalPath.split('/')) {
160
+ if (!segment) {
161
+ continue;
162
+ }
163
+ if (segment === '*' || segment === '$') {
164
+ fields.push(`'_splat'?: string`);
165
+ continue;
166
+ }
167
+ if (segment.startsWith('{-$') && segment.endsWith('}')) {
168
+ fields.push(`${JSON.stringify(segment.slice(3, -1))}?: string`);
169
+ continue;
170
+ }
171
+ if (segment.startsWith('$')) {
172
+ fields.push(`${JSON.stringify(segment.slice(1))}: string`);
173
+ continue;
174
+ }
175
+ if (segment.startsWith(':')) {
176
+ const optional = segment.endsWith('?');
177
+ const name = segment.slice(1, optional ? undefined : segment.length);
178
+ fields.push(
179
+ `${JSON.stringify(optional ? name.slice(0, -1) : name)}${
180
+ optional ? '?' : ''
181
+ }: string`,
182
+ );
183
+ }
184
+ }
185
+
186
+ return fields.length > 0
187
+ ? `{ ${fields.join('; ')} }`
188
+ : 'Record<string, never>';
189
+ }
190
+
191
+ /**
192
+ * Derive the canonical (language-agnostic) route map for an entry: the
193
+ * leading locale param is stripped and localized physical variants (routes
194
+ * carrying `modernCanonicalPath` metadata from `@modern-js/plugin-i18n`)
195
+ * collapse to their canonical pattern. Returns `null` when the entry has no
196
+ * i18n routing surface (no locale param and no localized variants), so plain
197
+ * TanStack apps never get a `@modern-js/plugin-i18n` module augmentation.
198
+ */
199
+ export function collectCanonicalRoutesForEntry(
200
+ routes: (NestedRouteForCli | PageRoute)[],
201
+ ): Record<string, string> | null {
202
+ const canonicalParams = new Map<string, string>();
203
+ let hasI18nSurface = false;
204
+
205
+ const normalizeJoined = (joined: string): string => {
206
+ const collapsed = joined.replace(/\/+/g, '/');
207
+ const withLeading = collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
208
+ return withLeading.length > 1
209
+ ? withLeading.replace(/\/+$/, '')
210
+ : withLeading;
211
+ };
212
+
213
+ const record = (canonicalPath: string) => {
214
+ const normalized = normalizeJoined(canonicalPath || '/');
215
+ const key = toTanstackPath(normalized);
216
+ if (!canonicalParams.has(key)) {
217
+ canonicalParams.set(key, paramsTypeForCanonicalPath(normalized));
218
+ }
219
+ };
220
+
221
+ const visit = (route: CanonicalAwareRoute, parentPath: string) => {
222
+ let currentPath = parentPath;
223
+
224
+ if (typeof route.modernCanonicalPath === 'string') {
225
+ hasI18nSurface = true;
226
+ currentPath = normalizeJoined(route.modernCanonicalPath);
227
+ } else if (typeof route.path === 'string' && route.path.length > 0) {
228
+ const segments = route.path
229
+ .replace(/\[(.+?)\]/g, ':$1')
230
+ .split('/')
231
+ .filter(Boolean);
232
+ if (parentPath === '' && LOCALE_PARAM_SEGMENTS.has(segments[0])) {
233
+ hasI18nSurface = true;
234
+ segments.shift();
235
+ }
236
+ currentPath = segments.length
237
+ ? normalizeJoined(`${parentPath}/${segments.join('/')}`)
238
+ : parentPath;
239
+ }
240
+
241
+ const children = route.children;
242
+ if (children && children.length > 0) {
243
+ for (const child of children) {
244
+ visit(child, currentPath);
245
+ }
246
+ return;
247
+ }
248
+
249
+ // Leaf page or index route: a navigable target.
250
+ record(currentPath || '/');
251
+ };
252
+
253
+ const rootModern = routes.find(
254
+ route => (route as CanonicalAwareRoute).isRoot,
255
+ ) as CanonicalAwareRoute | undefined;
256
+ const topLevel = rootModern ? (rootModern.children ?? []) : routes;
257
+
258
+ for (const route of topLevel) {
259
+ visit(route as CanonicalAwareRoute, '');
260
+ }
261
+
262
+ if (!hasI18nSurface || canonicalParams.size === 0) {
263
+ return null;
264
+ }
265
+
266
+ return Object.fromEntries(
267
+ [...canonicalParams.entries()].sort(([a], [b]) => a.localeCompare(b)),
268
+ );
269
+ }
270
+
129
271
  export async function isTanstackRouterFrameworkEnabled(
130
272
  appContext: AppToolsContext,
131
273
  ): Promise<boolean> {
@@ -182,9 +324,29 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
182
324
  const statements: string[] = [];
183
325
 
184
326
  const loaderImportMap = new Map<string, string>();
327
+ const searchContractImportMap = new Map<string, string>();
328
+ const usedRouteVarNames = new Set<string>();
185
329
  let loaderIndex = 0;
330
+ let validateSearchIndex = 0;
331
+ let loaderDepsIndex = 0;
186
332
  let routeIndex = 0;
187
333
 
334
+ const resolveRouteModuleNoExt = async (aliasedNoExtPath: string) => {
335
+ const prefix = `${appContext.internalSrcAlias}/`;
336
+ let absNoExt: string;
337
+ if (aliasedNoExtPath.startsWith(prefix)) {
338
+ const rel = aliasedNoExtPath.slice(prefix.length);
339
+ absNoExt = path.join(appContext.srcDirectory, rel);
340
+ } else if (path.isAbsolute(aliasedNoExtPath)) {
341
+ absNoExt = aliasedNoExtPath;
342
+ } else {
343
+ // Unknown format; treat as already relative to src.
344
+ absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
345
+ }
346
+
347
+ return resolveFileNoExt(absNoExt);
348
+ };
349
+
188
350
  const getImportNamesForLoader = async (
189
351
  aliasedNoExtPath: string,
190
352
  inline: boolean,
@@ -201,19 +363,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
201
363
  };
202
364
  }
203
365
 
204
- const prefix = `${appContext.internalSrcAlias}/`;
205
- let absNoExt: string;
206
- if (aliasedNoExtPath.startsWith(prefix)) {
207
- const rel = aliasedNoExtPath.slice(prefix.length);
208
- absNoExt = path.join(appContext.srcDirectory, rel);
209
- } else if (path.isAbsolute(aliasedNoExtPath)) {
210
- absNoExt = aliasedNoExtPath;
211
- } else {
212
- // Unknown format; treat as already relative to src.
213
- absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
214
- }
215
-
216
- const resolvedNoExt = await resolveFileNoExt(absNoExt);
366
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
217
367
  if (!resolvedNoExt) {
218
368
  return null;
219
369
  }
@@ -242,10 +392,49 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
242
392
  return { loaderName: importName, actionName };
243
393
  };
244
394
 
395
+ const getImportNameForSearchContract = async (
396
+ aliasedNoExtPath: string,
397
+ exportName: 'validateSearch' | 'loaderDeps',
398
+ ) => {
399
+ const key = `${exportName}:${aliasedNoExtPath}`;
400
+ const existing = searchContractImportMap.get(key);
401
+ if (existing) {
402
+ return existing;
403
+ }
404
+
405
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
406
+ if (!resolvedNoExt) {
407
+ return null;
408
+ }
409
+
410
+ const relImport = normalizeRelativeImport(
411
+ path.relative(outDir, resolvedNoExt),
412
+ );
413
+ const importName =
414
+ exportName === 'validateSearch'
415
+ ? `validateSearch_${validateSearchIndex++}`
416
+ : `loaderDeps_${loaderDepsIndex++}`;
417
+ imports.push(
418
+ `import { ${exportName} as ${importName} } from ${quote(relImport)};`,
419
+ );
420
+ searchContractImportMap.set(key, importName);
421
+ return importName;
422
+ };
423
+
424
+ const reserveRouteVarName = (preferred: string) => {
425
+ let candidate = preferred;
426
+ let suffix = 1;
427
+ while (usedRouteVarNames.has(candidate)) {
428
+ candidate = `${preferred}_${suffix++}`;
429
+ }
430
+ usedRouteVarNames.add(candidate);
431
+ return candidate;
432
+ };
433
+
245
434
  const createRouteVarName = (route: NestedRouteForCli | PageRoute) => {
246
435
  const id = (route as any).id as string | undefined;
247
436
  const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
248
- return `route_${base}`;
437
+ return reserveRouteVarName(`route_${base}`);
249
438
  };
250
439
 
251
440
  const buildRoute = async (opts: {
@@ -267,6 +456,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
267
456
  : null;
268
457
  const loaderName = loaderImports?.loaderName || null;
269
458
  const actionName = loaderImports?.actionName || null;
459
+ const searchContractInfo = pickRouteSearchContractModules(route);
460
+ const validateSearchName = searchContractInfo.validateSearchPath
461
+ ? await getImportNameForSearchContract(
462
+ searchContractInfo.validateSearchPath,
463
+ 'validateSearch',
464
+ )
465
+ : null;
466
+ const loaderDepsName = searchContractInfo.loaderDepsPath
467
+ ? await getImportNameForSearchContract(
468
+ searchContractInfo.loaderDepsPath,
469
+ 'loaderDeps',
470
+ )
471
+ : null;
270
472
 
271
473
  const rawPath = (route as any).path as string | undefined;
272
474
  const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
@@ -286,6 +488,12 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
286
488
  `loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
287
489
  );
288
490
  }
491
+ if (validateSearchName) {
492
+ routeOpts.push(`validateSearch: ${validateSearchName},`);
493
+ }
494
+ if (loaderDepsName) {
495
+ routeOpts.push(`loaderDeps: ${loaderDepsName},`);
496
+ }
289
497
 
290
498
  const staticDataSnippet = createRouteStaticDataSnippet({
291
499
  modernRouteId: (route as any).id as string | undefined,
@@ -296,18 +504,27 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
296
504
  routeOpts.push(staticDataSnippet);
297
505
  }
298
506
 
299
- statements.push(
300
- `const ${varName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
301
- );
302
-
303
507
  const children = (route as any).children as
304
508
  | Array<NestedRouteForCli | PageRoute>
305
509
  | undefined;
510
+ const hasChildren = Boolean(children && children.length > 0);
511
+ const routeCtorVarName = hasChildren
512
+ ? reserveRouteVarName(`${varName}__base`)
513
+ : varName;
514
+
515
+ statements.push(
516
+ `const ${routeCtorVarName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
517
+ );
518
+
306
519
  if (children && children.length > 0) {
307
520
  const childVars = await Promise.all(
308
- children.map(child => buildRoute({ parentVar: varName, route: child })),
521
+ children.map(child =>
522
+ buildRoute({ parentVar: routeCtorVarName, route: child }),
523
+ ),
524
+ );
525
+ statements.push(
526
+ `const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
309
527
  );
310
- statements.push(`${varName}.addChildren([${childVars.join(', ')}]);`);
311
528
  }
312
529
 
313
530
  return varName;
@@ -326,6 +543,21 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
326
543
  : null;
327
544
  const rootLoaderName = rootLoaderImports?.loaderName || null;
328
545
  const rootActionName = rootLoaderImports?.actionName || null;
546
+ const rootSearchContractInfo = rootModern
547
+ ? pickRouteSearchContractModules(rootModern)
548
+ : null;
549
+ const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
550
+ ? await getImportNameForSearchContract(
551
+ rootSearchContractInfo.validateSearchPath,
552
+ 'validateSearch',
553
+ )
554
+ : null;
555
+ const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
556
+ ? await getImportNameForSearchContract(
557
+ rootSearchContractInfo.loaderDepsPath,
558
+ 'loaderDeps',
559
+ )
560
+ : null;
329
561
 
330
562
  const topLevelVars = await Promise.all(
331
563
  topLevel.map(route => buildRoute({ parentVar: 'rootRoute', route })),
@@ -337,12 +569,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
337
569
  `loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
338
570
  );
339
571
  }
572
+ if (rootValidateSearchName) {
573
+ rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
574
+ }
575
+ if (rootLoaderDepsName) {
576
+ rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
577
+ }
340
578
 
341
579
  const routerGenTs = `/* eslint-disable */
342
580
  // This file is auto-generated by Modern.js. Do not edit manually.
343
581
 
344
582
  import {
345
583
  createMemoryHistory,
584
+ modernTanstackRouterFastDefaults,
346
585
  createRootRouteWithContext,
347
586
  createRoute,
348
587
  createRouter,
@@ -370,7 +609,7 @@ function isRedirectResponse(res: Response) {
370
609
  }
371
610
 
372
611
  function throwTanstackRedirect(location: string) {
373
- const target = location || '/';
612
+ const target = location.length > 0 ? location : '/';
374
613
  try {
375
614
  void new URL(target);
376
615
  throw redirect({ href: target });
@@ -396,21 +635,87 @@ function createRouteStaticData(opts: {
396
635
  modernRouteAction?: unknown;
397
636
  modernRouteLoader?: unknown;
398
637
  }) {
399
- const staticData: Record<string, unknown> = {};
638
+ const staticData: {
639
+ modernRouteId?: string;
640
+ modernRouteAction?: unknown;
641
+ modernRouteLoader?: unknown;
642
+ } = {};
400
643
 
401
- if (opts.modernRouteId) {
644
+ if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
402
645
  staticData.modernRouteId = opts.modernRouteId;
403
646
  }
404
647
 
405
- if (opts.modernRouteLoader) {
648
+ if (typeof opts.modernRouteLoader !== 'undefined') {
406
649
  staticData.modernRouteLoader = opts.modernRouteLoader;
407
650
  }
408
651
 
409
- if (opts.modernRouteAction) {
652
+ if (typeof opts.modernRouteAction !== 'undefined') {
410
653
  staticData.modernRouteAction = opts.modernRouteAction;
411
654
  }
412
655
 
413
- return Object.keys(staticData).length > 0 ? staticData : undefined;
656
+ return staticData;
657
+ }
658
+
659
+ function getLoaderSignal(ctx: any): AbortSignal {
660
+ const abortSignal = ctx?.abortController?.signal;
661
+ if (abortSignal instanceof AbortSignal) {
662
+ return abortSignal;
663
+ }
664
+ if (ctx?.signal instanceof AbortSignal) {
665
+ return ctx.signal;
666
+ }
667
+ return new AbortController().signal;
668
+ }
669
+
670
+ function getLoaderHref(ctx: any): string {
671
+ if (typeof ctx?.location === 'string') {
672
+ return ctx.location;
673
+ }
674
+
675
+ const publicHref = ctx?.location?.publicHref;
676
+ if (typeof publicHref === 'string') {
677
+ return publicHref;
678
+ }
679
+
680
+ const href = ctx?.location?.href;
681
+ if (typeof href === 'string') {
682
+ return href;
683
+ }
684
+
685
+ const urlHref = ctx?.location?.url?.href;
686
+ return typeof urlHref === 'string' ? urlHref : '';
687
+ }
688
+
689
+ function getLoaderParams(ctx: any): Record<string, string> {
690
+ return typeof ctx?.params === 'object' && ctx.params !== null ? ctx.params : {};
691
+ }
692
+
693
+ function handleModernLoaderResult<LoaderResult>(result: LoaderResult): LoaderResult {
694
+ if (isResponse(result)) {
695
+ if (isRedirectResponse(result)) {
696
+ const location = result.headers.get('Location') ?? '/';
697
+ throwTanstackRedirect(location);
698
+ }
699
+ if (result.status === 404) {
700
+ throw notFound();
701
+ }
702
+ }
703
+
704
+ return result;
705
+ }
706
+
707
+ function handleModernLoaderError(err: unknown): never {
708
+ if (isResponse(err)) {
709
+ if (isRedirectResponse(err)) {
710
+ const location = err.headers.get('Location') ?? '/';
711
+ throwTanstackRedirect(location);
712
+ }
713
+ if (err.status === 404) {
714
+ throw notFound();
715
+ }
716
+ }
717
+
718
+ throw err;
414
719
  }
415
720
 
416
721
  function modernLoaderToTanstack<TLoader extends (args: any) => any>(
@@ -419,57 +724,31 @@ function modernLoaderToTanstack<TLoader extends (args: any) => any>(
419
724
  ) {
420
725
  type LoaderResult = Awaited<ReturnType<TLoader>>;
421
726
 
422
- return async (ctx: any): Promise<LoaderResult> => {
727
+ return (ctx: any): Promise<LoaderResult> => {
423
728
  try {
424
- const signal: AbortSignal =
425
- ctx?.abortController?.signal ||
426
- ctx?.signal ||
427
- new AbortController().signal;
729
+ const signal = getLoaderSignal(ctx);
428
730
  const baseRequest: Request | undefined =
429
731
  ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
430
732
 
431
- const href =
432
- typeof ctx?.location === 'string'
433
- ? ctx.location
434
- : ctx?.location?.publicHref ||
435
- ctx?.location?.href ||
436
- ctx?.location?.url?.href ||
437
- '';
733
+ const href = getLoaderHref(ctx);
438
734
 
439
- const request = baseRequest
735
+ const request = baseRequest !== undefined
440
736
  ? new Request(baseRequest, { signal })
441
737
  : new Request(href, { signal });
442
738
 
443
- const params = mapParamsForModernLoader(ctx?.params || {}, opts.hasSplat);
444
-
445
- const result = await (modernLoader as any)({
446
- request,
447
- params,
448
- context: ctx?.context?.requestContext,
449
- });
739
+ const params = mapParamsForModernLoader(getLoaderParams(ctx), opts.hasSplat);
450
740
 
451
- if (isResponse(result)) {
452
- if (isRedirectResponse(result)) {
453
- const location = result.headers.get('Location') || '/';
454
- throwTanstackRedirect(location);
455
- }
456
- if (result.status === 404) {
457
- throw notFound();
458
- }
459
- }
460
-
461
- return result as LoaderResult;
741
+ return Promise.resolve(
742
+ (modernLoader as any)({
743
+ request,
744
+ params,
745
+ context: ctx?.context?.requestContext,
746
+ }),
747
+ )
748
+ .then((result: LoaderResult) => handleModernLoaderResult(result))
749
+ .catch(handleModernLoaderError);
462
750
  } catch (err) {
463
- if (isResponse(err)) {
464
- if (isRedirectResponse(err)) {
465
- const location = err.headers.get('Location') || '/';
466
- throwTanstackRedirect(location);
467
- }
468
- if (err.status === 404) {
469
- throw notFound();
470
- }
471
- }
472
- throw err;
751
+ handleModernLoaderError(err);
473
752
  }
474
753
  };
475
754
  }
@@ -492,6 +771,7 @@ ${statements.join('\n\n')}
492
771
  export const routeTree = rootRoute.addChildren([${topLevelVars.join(', ')}]);
493
772
 
494
773
  export const router = createRouter({
774
+ ...modernTanstackRouterFastDefaults,
495
775
  routeTree,
496
776
  history: createMemoryHistory({
497
777
  initialEntries: ['/'],
@@ -0,0 +1,12 @@
1
+ import { type ReactElement, Suspense } from 'react';
2
+
3
+ export function wrapTanstackSsrHydrationBoundary(
4
+ routerContent: ReactElement,
5
+ shouldWrap: boolean,
6
+ ) {
7
+ if (shouldWrap) {
8
+ return <Suspense fallback={null}>{routerContent}</Suspense>;
9
+ }
10
+
11
+ return routerContent;
12
+ }