@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.10 → 3.5.0-ultramodern.101

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 (268) hide show
  1. package/dist/cjs/cli/artifacts.js +157 -0
  2. package/dist/cjs/cli/index.js +8 -79
  3. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +113 -0
  4. package/dist/cjs/cli/tanstackTypes/shared.js +123 -0
  5. package/dist/cjs/cli/tanstackTypes/source.js +254 -0
  6. package/dist/cjs/cli/tanstackTypes.js +4 -350
  7. package/dist/cjs/runtime/blockingSubscribe.js +57 -0
  8. package/dist/cjs/runtime/{pluginCore.js → clientBasepath.js} +12 -19
  9. package/dist/cjs/runtime/clientHydration.js +118 -0
  10. package/dist/cjs/runtime/dataMutation.js +11 -176
  11. package/dist/cjs/runtime/formData.js +116 -0
  12. package/dist/cjs/runtime/loaderBridge.js +7 -11
  13. package/dist/cjs/runtime/plugin.js +21 -125
  14. package/dist/cjs/runtime/plugin.node.js +45 -125
  15. package/dist/cjs/runtime/pluginShared.js +87 -0
  16. package/dist/cjs/runtime/prefetchLink.js +15 -2
  17. package/dist/cjs/runtime/register.js +3 -16
  18. package/dist/cjs/runtime/routeTree/components.js +102 -0
  19. package/dist/cjs/runtime/routeTree/construction.js +152 -0
  20. package/dist/cjs/runtime/routeTree/index.js +47 -0
  21. package/dist/cjs/runtime/routeTree/loaders.js +158 -0
  22. package/dist/cjs/runtime/routeTree/paths.js +57 -0
  23. package/dist/cjs/runtime/routeTree/staticData.js +63 -0
  24. package/dist/cjs/runtime/routeTree/tanstackRoutes.js +57 -0
  25. package/dist/cjs/runtime/routeTree/types.js +18 -0
  26. package/dist/cjs/runtime/routeTree.js +7 -320
  27. package/dist/cjs/runtime/router.js +15 -2
  28. package/dist/cjs/runtime/rsc/CompositeComponent.js +18 -13
  29. package/dist/cjs/runtime/rsc/ReplayableStream.js +0 -4
  30. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
  31. package/dist/cjs/runtime/rsc/client.js +8 -25
  32. package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
  33. package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
  34. package/dist/cjs/runtime/rsc/payloadFetch.js +105 -0
  35. package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
  36. package/dist/cjs/runtime/rsc/payloadRoutes.js +94 -0
  37. package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
  38. package/dist/cjs/runtime/rsc/server.js +14 -18
  39. package/dist/cjs/runtime/rsc/shared.js +68 -0
  40. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +1 -0
  41. package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
  42. package/dist/cjs/runtime/shared/isAbsoluteUrl.js +47 -0
  43. package/dist/cjs/runtime/ssrManagedTags.js +103 -0
  44. package/dist/cjs/runtime/ssrPreload.js +89 -0
  45. package/dist/cjs/runtime/ssrTypes.js +18 -0
  46. package/dist/cjs/runtime/state.js +1 -1
  47. package/dist/cjs/runtime/submitAction.js +161 -0
  48. package/dist/cjs/runtime/utils.js +49 -86
  49. package/dist/esm/cli/artifacts.mjs +103 -0
  50. package/dist/esm/cli/index.mjs +8 -82
  51. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
  52. package/dist/esm/cli/tanstackTypes/shared.mjs +61 -0
  53. package/dist/esm/cli/tanstackTypes/source.mjs +206 -0
  54. package/dist/esm/cli/tanstackTypes.mjs +2 -339
  55. package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
  56. package/dist/esm/runtime/clientBasepath.mjs +10 -0
  57. package/dist/esm/runtime/clientHydration.mjs +71 -0
  58. package/dist/esm/runtime/dataMutation.mjs +2 -167
  59. package/dist/esm/runtime/formData.mjs +63 -0
  60. package/dist/esm/runtime/loaderBridge.mjs +5 -9
  61. package/dist/esm/runtime/plugin.mjs +14 -118
  62. package/dist/esm/runtime/plugin.node.mjs +41 -121
  63. package/dist/esm/runtime/pluginShared.mjs +40 -0
  64. package/dist/esm/runtime/prefetchLink.mjs +18 -5
  65. package/dist/esm/runtime/register.mjs +1 -14
  66. package/dist/esm/runtime/routeTree/components.mjs +55 -0
  67. package/dist/esm/runtime/routeTree/construction.mjs +111 -0
  68. package/dist/esm/runtime/routeTree/index.mjs +2 -0
  69. package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
  70. package/dist/esm/runtime/routeTree/paths.mjs +16 -0
  71. package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
  72. package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
  73. package/dist/esm/runtime/routeTree/types.mjs +0 -0
  74. package/dist/esm/runtime/routeTree.mjs +1 -318
  75. package/dist/esm/runtime/router.mjs +16 -2
  76. package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
  77. package/dist/esm/runtime/rsc/ReplayableStream.mjs +1 -1
  78. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
  79. package/dist/esm/runtime/rsc/client.mjs +6 -26
  80. package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
  81. package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
  82. package/dist/esm/runtime/rsc/payloadFetch.mjs +55 -0
  83. package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
  84. package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
  85. package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
  86. package/dist/esm/runtime/rsc/server.mjs +17 -21
  87. package/dist/esm/runtime/rsc/shared.mjs +21 -0
  88. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  89. package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
  90. package/dist/esm/runtime/shared/isAbsoluteUrl.mjs +9 -0
  91. package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
  92. package/dist/esm/runtime/ssrPreload.mjs +51 -0
  93. package/dist/esm/runtime/ssrTypes.mjs +0 -0
  94. package/dist/esm/runtime/state.mjs +1 -1
  95. package/dist/esm/runtime/submitAction.mjs +120 -0
  96. package/dist/esm/runtime/utils.mjs +47 -75
  97. package/dist/esm-node/cli/artifacts.mjs +104 -0
  98. package/dist/esm-node/cli/index.mjs +8 -82
  99. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
  100. package/dist/esm-node/cli/tanstackTypes/shared.mjs +62 -0
  101. package/dist/esm-node/cli/tanstackTypes/source.mjs +207 -0
  102. package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
  103. package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
  104. package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
  105. package/dist/esm-node/runtime/clientHydration.mjs +72 -0
  106. package/dist/esm-node/runtime/dataMutation.mjs +2 -167
  107. package/dist/esm-node/runtime/formData.mjs +64 -0
  108. package/dist/esm-node/runtime/loaderBridge.mjs +5 -9
  109. package/dist/esm-node/runtime/plugin.mjs +14 -118
  110. package/dist/esm-node/runtime/plugin.node.mjs +41 -121
  111. package/dist/esm-node/runtime/pluginShared.mjs +41 -0
  112. package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
  113. package/dist/esm-node/runtime/register.mjs +1 -14
  114. package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
  115. package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
  116. package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
  117. package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
  118. package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
  119. package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
  120. package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
  121. package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
  122. package/dist/esm-node/runtime/routeTree.mjs +1 -318
  123. package/dist/esm-node/runtime/router.mjs +16 -2
  124. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
  125. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +1 -1
  126. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
  127. package/dist/esm-node/runtime/rsc/client.mjs +6 -26
  128. package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
  129. package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
  130. package/dist/esm-node/runtime/rsc/payloadFetch.mjs +56 -0
  131. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
  132. package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
  133. package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
  134. package/dist/esm-node/runtime/rsc/server.mjs +17 -21
  135. package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
  136. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  137. package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
  138. package/dist/esm-node/runtime/shared/isAbsoluteUrl.mjs +10 -0
  139. package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
  140. package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
  141. package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
  142. package/dist/esm-node/runtime/state.mjs +1 -1
  143. package/dist/esm-node/runtime/submitAction.mjs +121 -0
  144. package/dist/esm-node/runtime/utils.mjs +47 -75
  145. package/dist/types/cli/artifacts.d.ts +33 -0
  146. package/dist/types/cli/index.d.ts +2 -22
  147. package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
  148. package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
  149. package/dist/types/cli/tanstackTypes/source.d.ts +12 -0
  150. package/dist/types/cli/tanstackTypes.d.ts +3 -31
  151. package/dist/types/runtime/blockingSubscribe.d.ts +1 -0
  152. package/dist/types/runtime/clientBasepath.d.ts +1 -0
  153. package/dist/types/runtime/clientHydration.d.ts +10 -0
  154. package/dist/types/runtime/dataMutation.d.ts +3 -12
  155. package/dist/types/runtime/formData.d.ts +24 -0
  156. package/dist/types/runtime/loaderBridge.d.ts +2 -1
  157. package/dist/types/runtime/plugin.d.ts +1 -1
  158. package/dist/types/runtime/plugin.node.d.ts +1 -1
  159. package/dist/types/runtime/pluginShared.d.ts +38 -0
  160. package/dist/types/runtime/register.d.ts +10 -9
  161. package/dist/types/runtime/routeTree/components.d.ts +7 -0
  162. package/dist/types/runtime/routeTree/construction.d.ts +5 -0
  163. package/dist/types/runtime/routeTree/index.d.ts +2 -0
  164. package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
  165. package/dist/types/runtime/routeTree/paths.d.ts +3 -0
  166. package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
  167. package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
  168. package/dist/types/runtime/routeTree/types.d.ts +76 -0
  169. package/dist/types/runtime/routeTree.d.ts +1 -9
  170. package/dist/types/runtime/router.d.ts +6 -7
  171. package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
  172. package/dist/types/runtime/rsc/ReplayableStream.d.ts +3 -2
  173. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
  174. package/dist/types/runtime/rsc/SlotContext.d.ts +2 -1
  175. package/dist/types/runtime/rsc/client.d.ts +2 -0
  176. package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
  177. package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
  178. package/dist/types/runtime/rsc/payloadFetch.d.ts +8 -0
  179. package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
  180. package/dist/types/runtime/rsc/payloadRoutes.d.ts +40 -0
  181. package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
  182. package/dist/types/runtime/rsc/shared.d.ts +18 -0
  183. package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
  184. package/dist/types/runtime/shared/isAbsoluteUrl.d.ts +1 -0
  185. package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
  186. package/dist/types/runtime/ssrPreload.d.ts +2 -0
  187. package/dist/types/runtime/ssrTypes.d.ts +44 -0
  188. package/dist/types/runtime/state.d.ts +1 -1
  189. package/dist/types/runtime/submitAction.d.ts +14 -0
  190. package/dist/types/runtime/types.d.ts +4 -10
  191. package/dist/types/runtime/utils.d.ts +4 -24
  192. package/package.json +38 -17
  193. package/rstest.config.mts +23 -0
  194. package/src/cli/artifacts.ts +255 -0
  195. package/src/cli/index.ts +7 -185
  196. package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
  197. package/src/cli/tanstackTypes/shared.ts +113 -0
  198. package/src/cli/tanstackTypes/source.ts +413 -0
  199. package/src/cli/tanstackTypes.ts +4 -647
  200. package/src/runtime/blockingSubscribe.ts +48 -0
  201. package/src/runtime/clientBasepath.ts +12 -0
  202. package/src/runtime/clientHydration.ts +149 -0
  203. package/src/runtime/dataMutation.tsx +9 -306
  204. package/src/runtime/formData.ts +153 -0
  205. package/src/runtime/loaderBridge.ts +11 -12
  206. package/src/runtime/plugin.node.tsx +76 -291
  207. package/src/runtime/plugin.tsx +21 -257
  208. package/src/runtime/pluginShared.ts +112 -0
  209. package/src/runtime/prefetchLink.tsx +78 -15
  210. package/src/runtime/register.ts +10 -42
  211. package/src/runtime/routeTree/components.ts +130 -0
  212. package/src/runtime/routeTree/construction.ts +238 -0
  213. package/src/runtime/routeTree/index.ts +7 -0
  214. package/src/runtime/routeTree/loaders.ts +232 -0
  215. package/src/runtime/routeTree/paths.ts +36 -0
  216. package/src/runtime/routeTree/staticData.ts +66 -0
  217. package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
  218. package/src/runtime/routeTree/types.ts +87 -0
  219. package/src/runtime/routeTree.ts +5 -750
  220. package/src/runtime/router.ts +35 -7
  221. package/src/runtime/rsc/CompositeComponent.tsx +29 -20
  222. package/src/runtime/rsc/ReplayableStream.ts +2 -2
  223. package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
  224. package/src/runtime/rsc/SlotContext.tsx +1 -1
  225. package/src/runtime/rsc/client.tsx +12 -40
  226. package/src/runtime/rsc/flightSerialization.ts +158 -0
  227. package/src/runtime/rsc/payloadErrors.ts +73 -0
  228. package/src/runtime/rsc/payloadFetch.ts +92 -0
  229. package/src/runtime/rsc/payloadRouter.ts +29 -234
  230. package/src/runtime/rsc/payloadRoutes.ts +122 -0
  231. package/src/runtime/rsc/payloadStorage.ts +35 -0
  232. package/src/runtime/rsc/server.tsx +20 -29
  233. package/src/runtime/rsc/shared.ts +70 -0
  234. package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
  235. package/src/runtime/rsc/treeGetter.tsx +52 -0
  236. package/src/runtime/shared/isAbsoluteUrl.ts +8 -0
  237. package/src/runtime/ssrManagedTags.ts +107 -0
  238. package/src/runtime/ssrPreload.ts +125 -0
  239. package/src/runtime/ssrTypes.ts +51 -0
  240. package/src/runtime/state.ts +9 -3
  241. package/src/runtime/submitAction.ts +201 -0
  242. package/src/runtime/types.ts +4 -18
  243. package/src/runtime/utils.tsx +76 -110
  244. package/src/types/react-server-dom-rspack.d.ts +56 -0
  245. package/tests/realm-isolation.test.ts +114 -0
  246. package/tests/router/cli.test.ts +207 -96
  247. package/tests/router/clientHydration.test.tsx +61 -0
  248. package/tests/router/dataMutation.test.tsx +59 -0
  249. package/tests/router/fixtures/legacyRuntimeRouter.ts +10 -0
  250. package/tests/router/fixtures/router-gen.oxlint.json +5 -0
  251. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  252. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  253. package/tests/router/hooks.test.ts +14 -0
  254. package/tests/router/loaderBridge.test.ts +21 -0
  255. package/tests/router/prefetchLink.test.tsx +106 -59
  256. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  257. package/tests/router/register.test.ts +1 -39
  258. package/tests/router/routeTree.test.ts +51 -11
  259. package/tests/router/routerCompatibility.test.ts +239 -0
  260. package/tests/router/rsc.test.tsx +189 -0
  261. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  262. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  263. package/tests/router/ssrPreload.test.ts +30 -0
  264. package/tests/router/tanstackTypes.test.ts +480 -196
  265. package/dist/esm/runtime/pluginCore.mjs +0 -14
  266. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  267. package/dist/types/runtime/pluginCore.d.ts +0 -21
  268. package/src/runtime/pluginCore.ts +0 -48
@@ -1,16 +1,132 @@
1
- import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
2
3
  import { tmpdir } from 'node:os';
3
4
  import path from 'node:path';
5
+ import { promisify } from 'node:util';
4
6
  import { mergeConfig } from '@modern-js/plugin/cli';
5
7
  import type { Entrypoint } from '@modern-js/types';
6
8
  import { fs, NESTED_ROUTE_SPEC_FILE } from '@modern-js/utils';
7
9
  import {
8
10
  createTanstackRsbuildRouteSplittingProfile,
11
+ generateTanstackRouteArtifacts,
9
12
  tanstackRouterPlugin,
10
13
  writeTanstackRegisterFile,
11
14
  writeTanstackRouterTypesForEntries,
12
15
  } from '../../src/cli';
13
16
 
17
+ const execFileAsync = promisify(execFile);
18
+ const strictestTsconfigPath = path.resolve(
19
+ __dirname,
20
+ '../../node_modules/@tsconfig/strictest/tsconfig.json',
21
+ );
22
+
23
+ async function typecheckGeneratedRegistration(options: {
24
+ entries: string[];
25
+ generatedDirName: string;
26
+ srcDirectory: string;
27
+ runtimeModule?: string;
28
+ i18nRuntimeModule?: string;
29
+ canonicalTypeChecks?: string[];
30
+ }) {
31
+ const {
32
+ entries,
33
+ generatedDirName,
34
+ srcDirectory,
35
+ runtimeModule = '@modern-js/plugin-tanstack/runtime',
36
+ i18nRuntimeModule,
37
+ canonicalTypeChecks = [],
38
+ } = options;
39
+ const projectDirectory = path.dirname(srcDirectory);
40
+
41
+ for (const entry of entries) {
42
+ const routerPath = path.join(
43
+ srcDirectory,
44
+ generatedDirName,
45
+ entry,
46
+ 'router.gen.ts',
47
+ );
48
+ if (!(await fs.pathExists(routerPath))) {
49
+ await fs.outputFile(routerPath, 'export const router = { context: {} };');
50
+ }
51
+ }
52
+
53
+ const runtimeDeclaration = [
54
+ `declare module '${runtimeModule}' {`,
55
+ ' export interface Register {}',
56
+ ' export type ModernRouterContext = { request?: Request; requestContext?: unknown };',
57
+ ' type RouteOptions = { getParentRoute?: (...args: never[]) => unknown; id?: string; loader?: (...args: never[]) => unknown; path?: string; staticData?: unknown };',
58
+ ' type Route<TOptions extends RouteOptions = RouteOptions> = { options: TOptions; addChildren<const TChildren extends readonly unknown[]>(children: TChildren): Route<TOptions> & { children: TChildren } };',
59
+ ' export function createMemoryHistory<TOptions>(options: TOptions): TOptions;',
60
+ ' export function createRootRouteWithContext<TContext extends ModernRouterContext>(): <const TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
61
+ ' export function createRoute<const TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
62
+ ' export function createRouter<const TOptions extends { context: ModernRouterContext; routeTree: unknown }>(options: TOptions): TOptions;',
63
+ ' export function createRouteStaticData<const TData extends Record<string, unknown>>(data: TData): TData;',
64
+ ' export function modernLoaderToTanstack<TLoader extends (...args: never[]) => unknown>(options: { hasSplat: boolean }, loader: TLoader): (context: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
65
+ ' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
66
+ '}',
67
+ ];
68
+ if (i18nRuntimeModule) {
69
+ runtimeDeclaration.push(
70
+ `declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
71
+ );
72
+ }
73
+ await fs.outputFile(
74
+ path.join(srcDirectory, 'generated-runtime-shim.d.ts'),
75
+ runtimeDeclaration.join('\n'),
76
+ );
77
+
78
+ const contractLines = [
79
+ `import type { Register } from '${runtimeModule}';`,
80
+ ...entries.map(
81
+ (entry, index) =>
82
+ `import { router as router${index} } from './${generatedDirName}/${entry}/router.gen';`,
83
+ ),
84
+ ...entries.map(
85
+ (_, index) =>
86
+ `const registeredRouter${index}: Register['router'] = router${index};`,
87
+ ),
88
+ ...entries.map((_, index) => `void registeredRouter${index};`),
89
+ ...canonicalTypeChecks,
90
+ ];
91
+ await fs.outputFile(
92
+ path.join(srcDirectory, 'registration-contract.ts'),
93
+ contractLines.join('\n'),
94
+ );
95
+ await fs.outputJSON(path.join(projectDirectory, 'tsconfig.json'), {
96
+ extends: strictestTsconfigPath,
97
+ compilerOptions: {
98
+ lib: ['ESNext', 'DOM'],
99
+ module: 'Preserve',
100
+ moduleResolution: 'Bundler',
101
+ noEmit: true,
102
+ target: 'ESNext',
103
+ types: [],
104
+ },
105
+ include: ['src/**/*.ts', 'src/**/*.d.ts'],
106
+ });
107
+
108
+ try {
109
+ await execFileAsync(
110
+ process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
111
+ ['-p', 'tsconfig.json'],
112
+ {
113
+ cwd: projectDirectory,
114
+ shell: process.platform === 'win32',
115
+ },
116
+ );
117
+ } catch (error) {
118
+ if (
119
+ error &&
120
+ typeof error === 'object' &&
121
+ 'stdout' in error &&
122
+ typeof error.stdout === 'string'
123
+ ) {
124
+ throw new Error(error.stdout, { cause: error });
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+
14
130
  const runtimeCliMocks = {
15
131
  handleFileChange: rstest.fn(),
16
132
  handleGeneratorEntryCode: rstest.fn(),
@@ -85,7 +201,7 @@ describe('tanstack router cli plugin', () => {
85
201
  }
86
202
  });
87
203
 
88
- test('writes plugin-owned router types and register metadata', async () => {
204
+ test('typechecks plugin-owned routers and register metadata under strictest', async () => {
89
205
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
90
206
  const srcDirectory = path.join(tempDir, 'src');
91
207
  await mkdir(srcDirectory, { recursive: true });
@@ -101,33 +217,33 @@ describe('tanstack router cli plugin', () => {
101
217
  } as any,
102
218
  generatedDirName: 'generated-router',
103
219
  routesByEntry: {
104
- dashboard: [],
105
- main: [],
220
+ dashboard: [
221
+ {
222
+ type: 'nested',
223
+ id: 'layout',
224
+ isRoot: true,
225
+ children: [{ type: 'nested', id: 'page', index: true }],
226
+ },
227
+ ],
228
+ main: [
229
+ {
230
+ type: 'nested',
231
+ id: 'layout',
232
+ isRoot: true,
233
+ children: [{ type: 'nested', id: 'page', index: true }],
234
+ },
235
+ ],
106
236
  },
107
237
  });
108
238
 
109
- const mainRouter = await readFile(
110
- path.join(srcDirectory, 'generated-router', 'main', 'router.gen.ts'),
111
- 'utf-8',
112
- );
113
- expect(mainRouter).toContain(
114
- "} from '@modern-js/plugin-tanstack/runtime';",
115
- );
116
-
117
- const register = await readFile(
118
- path.join(srcDirectory, 'generated-router', 'register.gen.d.ts'),
119
- 'utf-8',
120
- );
121
- expect(register).toContain("from './main/router.gen'");
122
- expect(register.indexOf("from './main/router.gen'")).toBeLessThan(
123
- register.indexOf("from './dashboard/router.gen'"),
124
- );
125
- expect(register).toContain(
126
- "declare module '@modern-js/plugin-tanstack/runtime'",
127
- );
239
+ await typecheckGeneratedRegistration({
240
+ entries: ['main', 'dashboard'],
241
+ generatedDirName: 'generated-router',
242
+ srcDirectory,
243
+ });
128
244
  });
129
245
 
130
- test('can write register metadata without routes for custom entry lists', async () => {
246
+ test('typechecks register metadata for custom entry lists without routes', async () => {
131
247
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
132
248
  const srcDirectory = path.join(tempDir, 'src');
133
249
 
@@ -137,17 +253,14 @@ describe('tanstack router cli plugin', () => {
137
253
  srcDirectory,
138
254
  });
139
255
 
140
- const register = await readFile(
141
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
142
- 'utf-8',
143
- );
144
- expect(register).toContain('router: typeof router0');
145
- expect(register).toContain(
146
- "declare module '@modern-js/plugin-tanstack/runtime'",
147
- );
256
+ await typecheckGeneratedRegistration({
257
+ entries: ['main'],
258
+ generatedDirName: 'tanstack',
259
+ srcDirectory,
260
+ });
148
261
  });
149
262
 
150
- test('writes plugin-i18n module augmentation when canonicalRoutes are provided', async () => {
263
+ test('exposes canonical route params through plugin-i18n types', async () => {
151
264
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
152
265
  const srcDirectory = path.join(tempDir, 'src');
153
266
 
@@ -161,20 +274,22 @@ describe('tanstack router cli plugin', () => {
161
274
  },
162
275
  });
163
276
 
164
- const register = await readFile(
165
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
166
- 'utf-8',
167
- );
168
-
169
- expect(register).toContain(
170
- "declare module '@modern-js/plugin-i18n/runtime'",
171
- );
172
- expect(register).toContain('interface UltramodernCanonicalRoutes');
173
- expect(register).toContain("'/': Record<string, never>;");
174
- expect(register).toContain('\'/products/$slug\': { "slug": string };');
277
+ await typecheckGeneratedRegistration({
278
+ entries: ['main'],
279
+ generatedDirName: 'tanstack',
280
+ srcDirectory,
281
+ i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
282
+ canonicalTypeChecks: [
283
+ "import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
284
+ "const rootParams: UltramodernCanonicalRoutes['/'] = {};",
285
+ "const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
286
+ 'void rootParams;',
287
+ 'void productParams;',
288
+ ],
289
+ });
175
290
  });
176
291
 
177
- test('does not emit plugin-i18n augmentation when canonicalRoutes is absent (back-compat)', async () => {
292
+ test('typechecks a plain TanStack register without plugin-i18n installed', async () => {
178
293
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
179
294
  const srcDirectory = path.join(tempDir, 'src');
180
295
 
@@ -182,23 +297,16 @@ describe('tanstack router cli plugin', () => {
182
297
  entries: ['main'],
183
298
  generatedDirName: 'tanstack',
184
299
  srcDirectory,
185
- // No canonicalRoutes provided at all — plain TanStack app
186
300
  });
187
301
 
188
- const register = await readFile(
189
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
190
- 'utf-8',
191
- );
192
-
193
- expect(register).not.toContain('plugin-i18n');
194
- expect(register).not.toContain('UltramodernCanonicalRoutes');
195
- // But the standard TanStack runtime augmentation must still be present.
196
- expect(register).toContain(
197
- "declare module '@modern-js/plugin-tanstack/runtime'",
198
- );
302
+ await typecheckGeneratedRegistration({
303
+ entries: ['main'],
304
+ generatedDirName: 'tanstack',
305
+ srcDirectory,
306
+ });
199
307
  });
200
308
 
201
- test('uses a custom i18nRuntimeModule when specified', async () => {
309
+ test('exposes canonical route params through a custom i18n runtime module', async () => {
202
310
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
203
311
  const srcDirectory = path.join(tempDir, 'src');
204
312
 
@@ -212,15 +320,17 @@ describe('tanstack router cli plugin', () => {
212
320
  i18nRuntimeModule: '@my-org/i18n/runtime',
213
321
  });
214
322
 
215
- const register = await readFile(
216
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
217
- 'utf-8',
218
- );
219
-
220
- expect(register).toContain("declare module '@my-org/i18n/runtime'");
221
- expect(register).not.toContain(
222
- "declare module '@modern-js/plugin-i18n/runtime'",
223
- );
323
+ await typecheckGeneratedRegistration({
324
+ entries: ['main'],
325
+ generatedDirName: 'tanstack',
326
+ srcDirectory,
327
+ i18nRuntimeModule: '@my-org/i18n/runtime',
328
+ canonicalTypeChecks: [
329
+ "import type { UltramodernCanonicalRoutes } from '@my-org/i18n/runtime';",
330
+ "const talksParams: UltramodernCanonicalRoutes['/talks'] = {};",
331
+ 'void talksParams;',
332
+ ],
333
+ });
224
334
  });
225
335
 
226
336
  test('claims custom routes, injects runtime plugin, and merges route specs', async () => {
@@ -575,32 +685,36 @@ describe('tanstack router cli plugin', () => {
575
685
  tanstackRouterPlugin().setup!(api as any);
576
686
  await taps.generateEntryCode({ entrypoints: [entrypoint] });
577
687
 
578
- const register = await readFile(
579
- path.join(srcDirectory, 'modern-tanstack', 'register.gen.d.ts'),
580
- 'utf-8',
688
+ const hasI18n = registeredPlugins.some(
689
+ plugin => plugin.name === '@modern-js/plugin-i18n',
581
690
  );
691
+ await typecheckGeneratedRegistration({
692
+ entries: ['main'],
693
+ generatedDirName: 'modern-tanstack',
694
+ srcDirectory,
695
+ ...(hasI18n && {
696
+ i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
697
+ }),
698
+ canonicalTypeChecks: hasI18n
699
+ ? [
700
+ "import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
701
+ "const aboutParams: UltramodernCanonicalRoutes['/about'] = {};",
702
+ 'void aboutParams;',
703
+ ]
704
+ : [],
705
+ });
582
706
  await rm(dir, { recursive: true, force: true });
583
- return register;
584
707
  };
585
708
 
586
709
  // A hand-rolled `/:lang/` app WITHOUT plugin-i18n must not get the
587
710
  // augmentation — it would reference an unresolvable module (TS2664).
588
- const withoutI18n = await runGenerate([{ name: '@modern-js/app-tools' }]);
589
- expect(withoutI18n).not.toContain('plugin-i18n');
590
- expect(withoutI18n).not.toContain('UltramodernCanonicalRoutes');
591
- expect(withoutI18n).toContain(
592
- "declare module '@modern-js/plugin-tanstack/runtime'",
593
- );
711
+ await runGenerate([{ name: '@modern-js/app-tools' }]);
594
712
 
595
713
  // With plugin-i18n registered the canonical route map is emitted.
596
- const withI18n = await runGenerate([
714
+ await runGenerate([
597
715
  { name: '@modern-js/app-tools' },
598
716
  { name: '@modern-js/plugin-i18n' },
599
717
  ]);
600
- expect(withI18n).toContain(
601
- "declare module '@modern-js/plugin-i18n/runtime'",
602
- );
603
- expect(withI18n).toContain("'/about': Record<string, never>;");
604
718
  });
605
719
 
606
720
  test('generates plugin-owned TanStack route files through core route generation', async () => {
@@ -691,20 +805,11 @@ describe('tanstack router cli plugin', () => {
691
805
  },
692
806
  );
693
807
 
694
- const routerGen = await readFile(
695
- path.join(srcDirectory, 'tanstack-generated', 'main', 'router.gen.ts'),
696
- 'utf-8',
697
- );
698
- expect(routerGen).toContain("} from '@modern-js/plugin-tanstack/runtime';");
699
- expect(routerGen).toContain('modernRouteAction: action_0');
700
-
701
- const register = await readFile(
702
- path.join(srcDirectory, 'tanstack-generated', 'register.gen.d.ts'),
703
- 'utf-8',
704
- );
705
- expect(register).toContain(
706
- "declare module '@modern-js/plugin-tanstack/runtime'",
707
- );
808
+ await typecheckGeneratedRegistration({
809
+ entries: ['main'],
810
+ generatedDirName: 'tanstack-generated',
811
+ srcDirectory,
812
+ });
708
813
  });
709
814
 
710
815
  test('regenerates plugin-owned TanStack files for scoped file changes', async () => {
@@ -977,3 +1082,9 @@ describe('tanstack router cli plugin', () => {
977
1082
  });
978
1083
  });
979
1084
  });
1085
+
1086
+ describe('generateTanstackRouteArtifacts export', () => {
1087
+ test('is exported as a function', () => {
1088
+ expect(typeof generateTanstackRouteArtifacts).toBe('function');
1089
+ });
1090
+ });
@@ -0,0 +1,61 @@
1
+ import type { AnyRouter } from '@tanstack/react-router';
2
+ import { render, screen } from '@testing-library/react';
3
+ import React, { Suspense } from 'react';
4
+ import { ModernRouterClient } from '../../src/runtime/clientHydration';
5
+
6
+ let hydrateImpl: () => Promise<unknown>;
7
+
8
+ rstest.mock('@tanstack/react-router', () => ({
9
+ RouterProvider: ({ router }: { router: { label: string } }) => (
10
+ <div data-testid="router-provider">{router.label}</div>
11
+ ),
12
+ }));
13
+
14
+ rstest.mock('@tanstack/react-router/ssr/client', () => ({
15
+ hydrate: () => hydrateImpl(),
16
+ }));
17
+
18
+ function createDeferred<T>() {
19
+ let resolve!: (value: T | PromiseLike<T>) => void;
20
+ let reject!: (reason?: unknown) => void;
21
+ const promise = new Promise<T>((resolvePromise, rejectPromise) => {
22
+ resolve = resolvePromise;
23
+ reject = rejectPromise;
24
+ });
25
+ return { promise, reject, resolve };
26
+ }
27
+
28
+ function createRouter(label: string): AnyRouter {
29
+ return {
30
+ label,
31
+ routesById: {},
32
+ stores: {
33
+ matches: {
34
+ get: () => [],
35
+ },
36
+ },
37
+ } as unknown as AnyRouter;
38
+ }
39
+
40
+ describe('TanStack SSR client hydration', () => {
41
+ it('keeps the router behind Suspense until late SSR hydration completes', async () => {
42
+ const hydration = createDeferred<void>();
43
+ hydrateImpl = () => hydration.promise;
44
+
45
+ render(
46
+ <Suspense fallback={<div data-testid="hydrating">hydrating</div>}>
47
+ <ModernRouterClient router={createRouter('inventory')} />
48
+ </Suspense>,
49
+ );
50
+
51
+ expect(screen.getByTestId('hydrating')).toBeTruthy();
52
+ expect(screen.queryByTestId('router-provider')).toBeNull();
53
+
54
+ hydration.resolve();
55
+
56
+ expect(
57
+ (await screen.findByTestId('router-provider')).textContent,
58
+ ).toContain('inventory');
59
+ expect(screen.queryByTestId('hydrating')).toBeNull();
60
+ });
61
+ });
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
2
  import React, { act } from 'react';
3
3
  import type { Fetcher } from '../../src/runtime/dataMutation';
4
4
  import { Form, useFetcher } from '../../src/runtime/dataMutation';
5
+ import { createFormDataFromSubmit } from '../../src/runtime/formData';
5
6
 
6
7
  type RouteHandler = (args: {
7
8
  request: Request;
@@ -276,6 +277,28 @@ describe('tanstack data mutation fetcher', () => {
276
277
  expect(states).toEqual(['idle', 'loading', 'idle']);
277
278
  });
278
279
 
280
+ test('GET Form navigation replaces action query before hash', async () => {
281
+ currentRouter = createRouter({});
282
+
283
+ render(
284
+ <Form method="get" action="/products?sort=old#results">
285
+ <input name="q" defaultValue="boots" />
286
+ <button type="submit">Search</button>
287
+ </Form>,
288
+ );
289
+
290
+ const form = document.querySelector('form');
291
+ expect(form).toBeTruthy();
292
+
293
+ await act(async () => {
294
+ fireEvent.submit(form!);
295
+ });
296
+
297
+ expect(currentRouter.navigate).toHaveBeenCalledWith({
298
+ href: '/products?q=boots#results',
299
+ });
300
+ });
301
+
279
302
  test('keeps non-idle state while overlapping mutation submits are still active', async () => {
280
303
  const actionResults = [
281
304
  createDeferred<Response>(),
@@ -361,6 +384,42 @@ describe('tanstack data mutation fetcher', () => {
361
384
  expect(screen.getByTestId('data').textContent).toBe('{"count":3}');
362
385
  });
363
386
 
387
+ test('preserves submitter name value when FormData submitter overload is unavailable', () => {
388
+ const NativeFormData = globalThis.FormData;
389
+
390
+ class SingleArgumentFormData extends NativeFormData {
391
+ constructor(form?: HTMLFormElement, submitter?: HTMLElement) {
392
+ if (submitter) {
393
+ throw new TypeError('FormData submitter overload unavailable');
394
+ }
395
+ super(form);
396
+ }
397
+ }
398
+
399
+ globalThis.FormData = SingleArgumentFormData as typeof FormData;
400
+
401
+ try {
402
+ const form = document.createElement('form');
403
+ form.innerHTML = `
404
+ <input name="amount" value="2" />
405
+ <button type="submit" name="intent" value="save">Save</button>
406
+ `;
407
+
408
+ const submitter = form.querySelector('button');
409
+ expect(submitter).toBeInstanceOf(HTMLButtonElement);
410
+
411
+ const formData = createFormDataFromSubmit({
412
+ form,
413
+ submitter,
414
+ });
415
+
416
+ expect(formData.get('amount')).toBe('2');
417
+ expect(formData.get('intent')).toBe('save');
418
+ } finally {
419
+ globalThis.FormData = NativeFormData;
420
+ }
421
+ });
422
+
364
423
  test('does not throw for Form submit with non-2xx response and still invalidates', async () => {
365
424
  const action = rstest.fn(
366
425
  async () =>
@@ -0,0 +1,10 @@
1
+ let invocationCount = 0;
2
+
3
+ export const routerPlugin = (
4
+ _userConfig: Record<string, unknown> = {},
5
+ ): { name?: string; setup?: (...args: unknown[]) => unknown } => {
6
+ invocationCount += 1;
7
+ throw new Error('The legacy global router wrapper must not be invoked.');
8
+ };
9
+
10
+ export const getLegacyRouterPluginInvocationCount = () => invocationCount;
@@ -0,0 +1,5 @@
1
+ {
2
+ "rules": {
3
+ "no-unused-vars": "error"
4
+ }
5
+ }