@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.9 → 3.5.0-ultramodern.90

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 (270) 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 +255 -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 +207 -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 +208 -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 +37 -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 +414 -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/castRatchet.test.ts +85 -0
  246. package/tests/realm-isolation.test.ts +114 -0
  247. package/tests/router/cli.test.ts +7 -0
  248. package/tests/router/clientHydration.test.tsx +61 -0
  249. package/tests/router/dataMutation.test.tsx +59 -0
  250. package/tests/router/fixtures/legacyRuntimeRouter.ts +10 -0
  251. package/tests/router/fixtures/router-gen.golden.txt +71 -0
  252. package/tests/router/fixtures/router-gen.oxlint.json +5 -0
  253. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  254. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  255. package/tests/router/hooks.test.ts +14 -0
  256. package/tests/router/loaderBridge.test.ts +21 -0
  257. package/tests/router/prefetchLink.test.tsx +106 -59
  258. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  259. package/tests/router/register.test.ts +1 -39
  260. package/tests/router/routeTree.test.ts +51 -11
  261. package/tests/router/routerCompatibility.test.ts +245 -0
  262. package/tests/router/rsc.test.tsx +189 -0
  263. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  264. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  265. package/tests/router/ssrPreload.test.ts +30 -0
  266. package/tests/router/tanstackTypes.test.ts +298 -1
  267. package/dist/esm/runtime/pluginCore.mjs +0 -14
  268. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  269. package/dist/types/runtime/pluginCore.d.ts +0 -21
  270. package/src/runtime/pluginCore.ts +0 -48
@@ -0,0 +1,255 @@
1
+ // @effect-diagnostics asyncFunction:off nodeBuiltinImport:off strictBooleanExpressions:off processEnv:off processEnvInEffect:off
2
+
3
+ import path from 'node:path';
4
+ import type { AppToolsContext } from '@modern-js/app-tools';
5
+ import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
6
+ import { fs } from '@modern-js/utils';
7
+
8
+ import {
9
+ collectCanonicalRoutesForEntry,
10
+ generateTanstackRouterTypesSourceForEntry,
11
+ } from './tanstackTypes';
12
+
13
+ const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
14
+
15
+ async function writeFileIfChanged(filePath: string, content: string) {
16
+ try {
17
+ const previous = (await fs.pathExists(filePath))
18
+ ? await fs.readFile(filePath, 'utf-8')
19
+ : null;
20
+ if (previous === content) {
21
+ return;
22
+ }
23
+ } catch {
24
+ // Fall through and rewrite the generated file.
25
+ }
26
+
27
+ await fs.outputFile(filePath, content, 'utf-8');
28
+ }
29
+
30
+ function createRegisterDtsContent(opts: {
31
+ entries: string[];
32
+ runtimeModule: string;
33
+ canonicalRoutes?: Record<string, string> | null;
34
+ i18nRuntimeModule?: string;
35
+ }) {
36
+ const importStatements = opts.entries
37
+ .map(
38
+ (entryName, index) =>
39
+ `import type { router as router${index} } from './${entryName}/router.gen';`,
40
+ )
41
+ .join('\n');
42
+ const routerUnionType = opts.entries
43
+ .map((_, index) => `typeof router${index}`)
44
+ .join(' | ');
45
+
46
+ const canonicalEntries = Object.entries(opts.canonicalRoutes ?? {});
47
+ const canonicalRoutesAugmentation =
48
+ canonicalEntries.length > 0
49
+ ? `
50
+ declare module '${opts.i18nRuntimeModule || '@modern-js/plugin-i18n/runtime'}' {
51
+ interface UltramodernCanonicalRoutes {
52
+ ${canonicalEntries
53
+ .map(([routePath, paramsType]) => ` '${routePath}': ${paramsType};`)
54
+ .join('\n')}
55
+ }
56
+ }
57
+ `
58
+ : '';
59
+
60
+ return `// This file is auto-generated by Modern.js. Do not edit manually.
61
+
62
+ ${importStatements}
63
+
64
+ declare module '${opts.runtimeModule}' {
65
+ interface Register {
66
+ router: ${routerUnionType};
67
+ }
68
+ }
69
+ ${canonicalRoutesAugmentation}`;
70
+ }
71
+
72
+ export async function writeTanstackRegisterFile(opts: {
73
+ entries: string[];
74
+ generatedDirName?: string;
75
+ runtimeModule?: string;
76
+ srcDirectory: string;
77
+ canonicalRoutes?: Record<string, string> | null;
78
+ i18nRuntimeModule?: string;
79
+ }) {
80
+ const {
81
+ entries,
82
+ generatedDirName = DEFAULT_GENERATED_DIR_NAME,
83
+ runtimeModule = '@modern-js/plugin-tanstack/runtime',
84
+ srcDirectory,
85
+ canonicalRoutes,
86
+ i18nRuntimeModule,
87
+ } = opts;
88
+
89
+ if (entries.length === 0) {
90
+ return;
91
+ }
92
+
93
+ const registerDtsPath = path.join(
94
+ srcDirectory,
95
+ generatedDirName,
96
+ 'register.gen.d.ts',
97
+ );
98
+
99
+ await writeFileIfChanged(
100
+ registerDtsPath,
101
+ createRegisterDtsContent({
102
+ entries,
103
+ runtimeModule,
104
+ canonicalRoutes,
105
+ i18nRuntimeModule,
106
+ }),
107
+ );
108
+ }
109
+
110
+ export async function writeTanstackRouterTypesForEntries(opts: {
111
+ appContext: AppToolsContext;
112
+ generatedDirName?: string;
113
+ routesByEntry: Record<string, (NestedRouteForCli | PageRoute)[]>;
114
+ /**
115
+ * Whether `@modern-js/plugin-i18n` is actually installed in the app. The
116
+ * `register.gen.d.ts` augmentation of '@modern-js/plugin-i18n/runtime' is
117
+ * only emitted when this is true — otherwise apps with a hand-rolled
118
+ * `/:lang/` param would fail typechecking on an unresolvable module.
119
+ */
120
+ i18nPluginInstalled?: boolean;
121
+ }) {
122
+ const {
123
+ appContext,
124
+ generatedDirName = DEFAULT_GENERATED_DIR_NAME,
125
+ routesByEntry,
126
+ i18nPluginInstalled = false,
127
+ } = opts;
128
+
129
+ const entryNames = Object.keys(routesByEntry);
130
+
131
+ await Promise.all(
132
+ entryNames.map(async entryName => {
133
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
134
+ appContext,
135
+ entryName,
136
+ generatedDirName,
137
+ routes: routesByEntry[entryName],
138
+ });
139
+
140
+ await writeFileIfChanged(
141
+ path.join(
142
+ appContext.srcDirectory,
143
+ generatedDirName,
144
+ entryName,
145
+ 'router.gen.ts',
146
+ ),
147
+ routerGenTs,
148
+ );
149
+ }),
150
+ );
151
+
152
+ const mainEntryName = appContext.entrypoints?.find(
153
+ entrypoint => entrypoint.isMainEntry,
154
+ )?.entryName;
155
+ const registerEntries = entryNames.sort((a, b) => {
156
+ if (mainEntryName && a === mainEntryName) {
157
+ return -1;
158
+ }
159
+
160
+ if (mainEntryName && b === mainEntryName) {
161
+ return 1;
162
+ }
163
+
164
+ return a.localeCompare(b);
165
+ });
166
+
167
+ // Merge the canonical (language-agnostic) route maps of every entry so the
168
+ // typed i18n Link covers all routes the app can navigate to. Only relevant
169
+ // when plugin-i18n is actually installed — without it the emitted module
170
+ // augmentation would reference an unresolvable module.
171
+ let canonicalRoutes: Record<string, string> | null = null;
172
+ if (i18nPluginInstalled) {
173
+ for (const entryName of registerEntries) {
174
+ const entryCanonicalRoutes = collectCanonicalRoutesForEntry(
175
+ routesByEntry[entryName],
176
+ { localeParamHeuristic: true },
177
+ );
178
+ if (entryCanonicalRoutes) {
179
+ canonicalRoutes = {
180
+ ...entryCanonicalRoutes,
181
+ ...(canonicalRoutes ?? {}),
182
+ };
183
+ }
184
+ }
185
+ }
186
+
187
+ await writeTanstackRegisterFile({
188
+ entries: registerEntries,
189
+ generatedDirName,
190
+ srcDirectory: appContext.srcDirectory,
191
+ canonicalRoutes,
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Headless regeneration of `router.gen.ts` / `register.gen.d.ts` for a generated
197
+ * app, without running a full dev/build. Drives the app-tools analyze `onPrepare`
198
+ * (which fires `generateEntryCode`) and returns before any bundling — the app's
199
+ * own registered plugin-tanstack writes the artifacts via its `generateEntryCode`
200
+ * subscriber. Reuses the exact hook dev/build use, so there is no scanning drift.
201
+ */
202
+ export async function generateTanstackRouteArtifacts(opts: {
203
+ appDirectory: string;
204
+ version?: string;
205
+ }): Promise<void> {
206
+ const [{ createRunOptions }, { cli }, utils] = await Promise.all([
207
+ import('@modern-js/app-tools/cli/run'),
208
+ import('@modern-js/plugin/cli'),
209
+ import('@modern-js/utils'),
210
+ ]);
211
+ // Load-bearing, not decoration: app-tools' analyze.onPrepare only fires
212
+ // generateEntryCode (the hook that writes the route artifacts) when
213
+ // checkIsBuildCommands() is true, and getCommand() resolves the command
214
+ // from MODERN_ARGV before process.argv — cli.init's `command` option does
215
+ // not feed that check.
216
+ process.env.MODERN_ARGV = 'node modern build';
217
+ process.env.NODE_ENV = process.env.NODE_ENV || 'production';
218
+
219
+ // Resolve the app's Modern.js config file ourselves and pass it as an
220
+ // absolute base path. `createRunOptions` otherwise defaults its config lookup
221
+ // to `process.cwd()` (not `cwd`/appDirectory), so headless invocations that
222
+ // run from a different directory than the app — e.g. the create CLI spawning
223
+ // this from the workspace root — find no config, and `findExists` returns
224
+ // `false`. That `false` then flows into `cli.init` -> `getConfigFilePath` ->
225
+ // `path.isAbsolute(false)`, throwing the opaque
226
+ // `The "path" argument must be of type string. Received type boolean (false)`.
227
+ // Passing an absolute base is resolution-based: cwd no longer matters, and the
228
+ // downstream `getConfigFile` still appends the correct extension.
229
+ const { findExists, CONFIG_FILE_EXTENSIONS } = utils as {
230
+ findExists: (paths: string[]) => string | false;
231
+ CONFIG_FILE_EXTENSIONS: string[];
232
+ };
233
+ const configFileBase = path.resolve(opts.appDirectory, 'modern.config');
234
+ const resolvedConfigFile = findExists(
235
+ CONFIG_FILE_EXTENSIONS.map(extension => `${configFileBase}${extension}`),
236
+ );
237
+ if (!resolvedConfigFile) {
238
+ throw new Error(
239
+ `[plugin-tanstack] Unable to locate a Modern.js config file (modern.config.{${CONFIG_FILE_EXTENSIONS.map(
240
+ extension => extension.slice(1),
241
+ ).join(',')}}) in ${opts.appDirectory}. ` +
242
+ 'TanStack route generation requires the app config to resolve its plugins.',
243
+ );
244
+ }
245
+
246
+ const runOptions = await createRunOptions({
247
+ cwd: opts.appDirectory,
248
+ configFile: configFileBase,
249
+ version:
250
+ opts.version || process.env.MODERN_JS_VERSION || '0.0.0-routes-generate',
251
+ internalPlugins: (utils as { INTERNAL_RUNTIME_PLUGINS: unknown })
252
+ .INTERNAL_RUNTIME_PLUGINS as never,
253
+ });
254
+ await cli.init({ ...runOptions, command: 'build' });
255
+ }
package/src/cli/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @effect-diagnostics asyncFunction:off nodeBuiltinImport:off strictBooleanExpressions:off
1
+ // @effect-diagnostics asyncFunction:off nodeBuiltinImport:off strictBooleanExpressions:off processEnv:off processEnvInEffect:off
2
2
  import path from 'node:path';
3
3
  import type {
4
4
  AppNormalizedConfig,
@@ -18,15 +18,17 @@ import {
18
18
  fs,
19
19
  NESTED_ROUTE_SPEC_FILE,
20
20
  } from '@modern-js/utils';
21
+ import { writeTanstackRouterTypesForEntries } from './artifacts';
21
22
  import {
22
23
  createTanstackRsbuildRouteSplittingProfile,
23
24
  type TanstackRouteCodeSplittingOption,
24
25
  } from './routeSplitting';
25
- import {
26
- collectCanonicalRoutesForEntry,
27
- generateTanstackRouterTypesSourceForEntry,
28
- } from './tanstackTypes';
29
26
 
27
+ export {
28
+ generateTanstackRouteArtifacts,
29
+ writeTanstackRegisterFile,
30
+ writeTanstackRouterTypesForEntries,
31
+ } from './artifacts';
30
32
  export type {
31
33
  TanstackRouteCodeSplittingOption,
32
34
  TanstackRsbuildRouteSplittingProfile,
@@ -113,186 +115,6 @@ function getRuntimeRouterCli(): RuntimeRouterCliHelpers {
113
115
  );
114
116
  }
115
117
 
116
- async function writeFileIfChanged(filePath: string, content: string) {
117
- try {
118
- const previous = (await fs.pathExists(filePath))
119
- ? await fs.readFile(filePath, 'utf-8')
120
- : null;
121
- if (previous === content) {
122
- return;
123
- }
124
- } catch {
125
- // Fall through and rewrite the generated file.
126
- }
127
-
128
- await fs.outputFile(filePath, content, 'utf-8');
129
- }
130
-
131
- function createRegisterDtsContent(opts: {
132
- entries: string[];
133
- runtimeModule: string;
134
- canonicalRoutes?: Record<string, string> | null;
135
- i18nRuntimeModule?: string;
136
- }) {
137
- const importStatements = opts.entries
138
- .map(
139
- (entryName, index) =>
140
- `import type { router as router${index} } from './${entryName}/router.gen';`,
141
- )
142
- .join('\n');
143
- const routerUnionType = opts.entries
144
- .map((_, index) => `typeof router${index}`)
145
- .join(' | ');
146
-
147
- const canonicalEntries = Object.entries(opts.canonicalRoutes ?? {});
148
- const canonicalRoutesAugmentation =
149
- canonicalEntries.length > 0
150
- ? `
151
- declare module '${opts.i18nRuntimeModule || '@modern-js/plugin-i18n/runtime'}' {
152
- interface UltramodernCanonicalRoutes {
153
- ${canonicalEntries
154
- .map(([routePath, paramsType]) => ` '${routePath}': ${paramsType};`)
155
- .join('\n')}
156
- }
157
- }
158
- `
159
- : '';
160
-
161
- return `// This file is auto-generated by Modern.js. Do not edit manually.
162
-
163
- ${importStatements}
164
-
165
- declare module '${opts.runtimeModule}' {
166
- interface Register {
167
- router: ${routerUnionType};
168
- }
169
- }
170
- ${canonicalRoutesAugmentation}`;
171
- }
172
-
173
- export async function writeTanstackRegisterFile(opts: {
174
- entries: string[];
175
- generatedDirName?: string;
176
- runtimeModule?: string;
177
- srcDirectory: string;
178
- canonicalRoutes?: Record<string, string> | null;
179
- i18nRuntimeModule?: string;
180
- }) {
181
- const {
182
- entries,
183
- generatedDirName = DEFAULT_GENERATED_DIR_NAME,
184
- runtimeModule = '@modern-js/plugin-tanstack/runtime',
185
- srcDirectory,
186
- canonicalRoutes,
187
- i18nRuntimeModule,
188
- } = opts;
189
-
190
- if (entries.length === 0) {
191
- return;
192
- }
193
-
194
- const registerDtsPath = path.join(
195
- srcDirectory,
196
- generatedDirName,
197
- 'register.gen.d.ts',
198
- );
199
-
200
- await writeFileIfChanged(
201
- registerDtsPath,
202
- createRegisterDtsContent({
203
- entries,
204
- runtimeModule,
205
- canonicalRoutes,
206
- i18nRuntimeModule,
207
- }),
208
- );
209
- }
210
-
211
- export async function writeTanstackRouterTypesForEntries(opts: {
212
- appContext: AppToolsContext;
213
- generatedDirName?: string;
214
- routesByEntry: Record<string, (NestedRouteForCli | PageRoute)[]>;
215
- /**
216
- * Whether `@modern-js/plugin-i18n` is actually installed in the app. The
217
- * `register.gen.d.ts` augmentation of '@modern-js/plugin-i18n/runtime' is
218
- * only emitted when this is true — otherwise apps with a hand-rolled
219
- * `/:lang/` param would fail typechecking on an unresolvable module.
220
- */
221
- i18nPluginInstalled?: boolean;
222
- }) {
223
- const {
224
- appContext,
225
- generatedDirName = DEFAULT_GENERATED_DIR_NAME,
226
- routesByEntry,
227
- i18nPluginInstalled = false,
228
- } = opts;
229
-
230
- const entryNames = Object.keys(routesByEntry);
231
-
232
- await Promise.all(
233
- entryNames.map(async entryName => {
234
- const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
235
- appContext,
236
- entryName,
237
- generatedDirName,
238
- routes: routesByEntry[entryName],
239
- });
240
-
241
- await writeFileIfChanged(
242
- path.join(
243
- appContext.srcDirectory,
244
- generatedDirName,
245
- entryName,
246
- 'router.gen.ts',
247
- ),
248
- routerGenTs,
249
- );
250
- }),
251
- );
252
-
253
- const mainEntryName = appContext.entrypoints?.find(
254
- entrypoint => entrypoint.isMainEntry,
255
- )?.entryName;
256
- const registerEntries = entryNames.sort((a, b) => {
257
- if (mainEntryName && a === mainEntryName) {
258
- return -1;
259
- }
260
-
261
- if (mainEntryName && b === mainEntryName) {
262
- return 1;
263
- }
264
-
265
- return a.localeCompare(b);
266
- });
267
-
268
- // Merge the canonical (language-agnostic) route maps of every entry so the
269
- // typed i18n Link covers all routes the app can navigate to. Only relevant
270
- // when plugin-i18n is actually installed — without it the emitted module
271
- // augmentation would reference an unresolvable module.
272
- let canonicalRoutes: Record<string, string> | null = null;
273
- if (i18nPluginInstalled) {
274
- for (const entryName of registerEntries) {
275
- const entryCanonicalRoutes = collectCanonicalRoutesForEntry(
276
- routesByEntry[entryName],
277
- { localeParamHeuristic: true },
278
- );
279
- if (entryCanonicalRoutes) {
280
- canonicalRoutes = {
281
- ...entryCanonicalRoutes,
282
- ...(canonicalRoutes ?? {}),
283
- };
284
- }
285
- }
286
- }
287
-
288
- await writeTanstackRegisterFile({
289
- entries: registerEntries,
290
- generatedDirName,
291
- srcDirectory: appContext.srcDirectory,
292
- canonicalRoutes,
293
- });
294
- }
295
-
296
118
  export function tanstackRouterPlugin(
297
119
  options: TanstackRouterPluginOptions = {},
298
120
  ): CliPlugin<AppTools> {
@@ -0,0 +1,157 @@
1
+ // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
+
3
+ import path from 'node:path';
4
+ import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
5
+ import { MAIN_ENTRY_NAME } from '@modern-js/utils';
6
+
7
+ import { toTanstackPath } from './shared';
8
+
9
+ const LOCALE_PARAM_SEGMENTS = new Set([
10
+ ':lang',
11
+ ':locale',
12
+ ':language',
13
+ '$lang',
14
+ '$locale',
15
+ '$language',
16
+ ]);
17
+
18
+ type CanonicalAwareRoute = (NestedRouteForCli | PageRoute) & {
19
+ modernCanonicalPath?: string;
20
+ index?: boolean;
21
+ isRoot?: boolean;
22
+ children?: CanonicalAwareRoute[];
23
+ };
24
+
25
+ function paramsTypeForCanonicalPath(canonicalPath: string): string {
26
+ const fields: string[] = [];
27
+
28
+ for (const segment of canonicalPath.split('/')) {
29
+ if (!segment) {
30
+ continue;
31
+ }
32
+ if (segment === '*' || segment === '$') {
33
+ fields.push(`'_splat'?: string`);
34
+ continue;
35
+ }
36
+ if (segment.startsWith('{-$') && segment.endsWith('}')) {
37
+ fields.push(`${JSON.stringify(segment.slice(3, -1))}?: string`);
38
+ continue;
39
+ }
40
+ if (segment.startsWith('$')) {
41
+ fields.push(`${JSON.stringify(segment.slice(1))}: string`);
42
+ continue;
43
+ }
44
+ if (segment.startsWith(':')) {
45
+ const optional = segment.endsWith('?');
46
+ const name = segment.slice(1, optional ? undefined : segment.length);
47
+ fields.push(
48
+ `${JSON.stringify(optional ? name.slice(0, -1) : name)}${
49
+ optional ? '?' : ''
50
+ }: string`,
51
+ );
52
+ }
53
+ }
54
+
55
+ return fields.length > 0
56
+ ? `{ ${fields.join('; ')} }`
57
+ : 'Record<string, never>';
58
+ }
59
+
60
+ export type CollectCanonicalRoutesOptions = {
61
+ /**
62
+ * Whether a leading `:lang`/`:locale`/`:language` route param may be
63
+ * treated as an i18n locale prefix. This MUST only be enabled when
64
+ * `@modern-js/plugin-i18n` is actually installed: the emitted
65
+ * `declare module '@modern-js/plugin-i18n/runtime'` augmentation breaks
66
+ * typechecking (TS2664) for apps that hand-roll a `/:lang/` param without
67
+ * the plugin. Routes carrying `modernCanonicalPath` metadata are always
68
+ * honored — only plugin-i18n produces them.
69
+ */
70
+ localeParamHeuristic?: boolean;
71
+ };
72
+
73
+ /**
74
+ * Derive the canonical (language-agnostic) route map for an entry: the
75
+ * leading locale param is stripped and localized physical variants (routes
76
+ * carrying `modernCanonicalPath` metadata from `@modern-js/plugin-i18n`)
77
+ * collapse to their canonical pattern. Returns `null` when the entry has no
78
+ * i18n routing surface (no locale param and no localized variants), so plain
79
+ * TanStack apps never get a `@modern-js/plugin-i18n` module augmentation.
80
+ */
81
+ export function collectCanonicalRoutesForEntry(
82
+ routes: (NestedRouteForCli | PageRoute)[],
83
+ options: CollectCanonicalRoutesOptions = {},
84
+ ): Record<string, string> | null {
85
+ const { localeParamHeuristic = true } = options;
86
+ const canonicalParams = new Map<string, string>();
87
+ let hasI18nSurface = false;
88
+
89
+ const normalizeJoined = (joined: string): string => {
90
+ const collapsed = joined.replace(/\/+/g, '/');
91
+ const withLeading = collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
92
+ return withLeading.length > 1
93
+ ? withLeading.replace(/\/+$/, '')
94
+ : withLeading;
95
+ };
96
+
97
+ const record = (canonicalPath: string) => {
98
+ const normalized = normalizeJoined(canonicalPath || '/');
99
+ const key = toTanstackPath(normalized);
100
+ if (!canonicalParams.has(key)) {
101
+ canonicalParams.set(key, paramsTypeForCanonicalPath(normalized));
102
+ }
103
+ };
104
+
105
+ const visit = (route: CanonicalAwareRoute, parentPath: string) => {
106
+ let currentPath = parentPath;
107
+
108
+ if (typeof route.modernCanonicalPath === 'string') {
109
+ hasI18nSurface = true;
110
+ currentPath = normalizeJoined(route.modernCanonicalPath);
111
+ } else if (typeof route.path === 'string' && route.path.length > 0) {
112
+ const segments = route.path
113
+ .replace(/\[(.+?)\]/g, ':$1')
114
+ .split('/')
115
+ .filter(Boolean);
116
+ if (
117
+ localeParamHeuristic &&
118
+ parentPath === '' &&
119
+ LOCALE_PARAM_SEGMENTS.has(segments[0])
120
+ ) {
121
+ hasI18nSurface = true;
122
+ segments.shift();
123
+ }
124
+ currentPath = segments.length
125
+ ? normalizeJoined(`${parentPath}/${segments.join('/')}`)
126
+ : parentPath;
127
+ }
128
+
129
+ const children = route.children;
130
+ if (children && children.length > 0) {
131
+ for (const child of children) {
132
+ visit(child, currentPath);
133
+ }
134
+ return;
135
+ }
136
+
137
+ // Leaf page or index route: a navigable target.
138
+ record(currentPath || '/');
139
+ };
140
+
141
+ const rootModern = routes.find(
142
+ route => (route as CanonicalAwareRoute).isRoot,
143
+ ) as CanonicalAwareRoute | undefined;
144
+ const topLevel = rootModern ? (rootModern.children ?? []) : routes;
145
+
146
+ for (const route of topLevel) {
147
+ visit(route as CanonicalAwareRoute, '');
148
+ }
149
+
150
+ if (!hasI18nSurface || canonicalParams.size === 0) {
151
+ return null;
152
+ }
153
+
154
+ return Object.fromEntries(
155
+ [...canonicalParams.entries()].sort(([a], [b]) => a.localeCompare(b)),
156
+ );
157
+ }