@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/cli/index.js +268 -0
  3. package/dist/cjs/cli/tanstackTypes.js +388 -0
  4. package/dist/cjs/cli.js +65 -0
  5. package/dist/cjs/runtime/DefaultNotFound.js +47 -0
  6. package/dist/cjs/runtime/basepathRewrite.js +62 -0
  7. package/dist/cjs/runtime/dataMutation.js +345 -0
  8. package/dist/cjs/runtime/hooks.js +57 -0
  9. package/dist/cjs/runtime/index.js +114 -0
  10. package/dist/cjs/runtime/lifecycle.js +125 -0
  11. package/dist/cjs/runtime/plugin.js +250 -0
  12. package/dist/cjs/runtime/plugin.node.js +304 -0
  13. package/dist/cjs/runtime/prefetchLink.js +55 -0
  14. package/dist/cjs/runtime/routeTree.js +492 -0
  15. package/dist/cjs/runtime/rsc/ClientSlot.js +53 -0
  16. package/dist/cjs/runtime/rsc/CompositeComponent.js +75 -0
  17. package/dist/cjs/runtime/rsc/ReplayableStream.js +141 -0
  18. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +65 -0
  19. package/dist/cjs/runtime/rsc/SlotContext.js +54 -0
  20. package/dist/cjs/runtime/rsc/client.js +93 -0
  21. package/dist/cjs/runtime/rsc/createRscProxy.js +141 -0
  22. package/dist/cjs/runtime/rsc/index.js +42 -0
  23. package/dist/cjs/runtime/rsc/payloadRouter.js +211 -0
  24. package/dist/cjs/runtime/rsc/server.js +246 -0
  25. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +65 -0
  26. package/dist/cjs/runtime/rsc/symbols.js +72 -0
  27. package/dist/cjs/runtime/types.js +18 -0
  28. package/dist/cjs/runtime/utils.js +142 -0
  29. package/dist/cjs/runtime.js +58 -0
  30. package/dist/esm/cli/index.mjs +201 -0
  31. package/dist/esm/cli/tanstackTypes.mjs +341 -0
  32. package/dist/esm/cli.mjs +2 -0
  33. package/dist/esm/rslib-runtime.mjs +18 -0
  34. package/dist/esm/runtime/DefaultNotFound.mjs +13 -0
  35. package/dist/esm/runtime/basepathRewrite.mjs +28 -0
  36. package/dist/esm/runtime/dataMutation.mjs +305 -0
  37. package/dist/esm/runtime/hooks.mjs +8 -0
  38. package/dist/esm/runtime/index.mjs +6 -0
  39. package/dist/esm/runtime/lifecycle.mjs +82 -0
  40. package/dist/esm/runtime/plugin.mjs +214 -0
  41. package/dist/esm/runtime/plugin.node.mjs +268 -0
  42. package/dist/esm/runtime/prefetchLink.mjs +18 -0
  43. package/dist/esm/runtime/routeTree.mjs +452 -0
  44. package/dist/esm/runtime/rsc/ClientSlot.mjs +19 -0
  45. package/dist/esm/runtime/rsc/CompositeComponent.mjs +41 -0
  46. package/dist/esm/runtime/rsc/ReplayableStream.mjs +104 -0
  47. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +31 -0
  48. package/dist/esm/runtime/rsc/SlotContext.mjs +17 -0
  49. package/dist/esm/runtime/rsc/client.mjs +53 -0
  50. package/dist/esm/runtime/rsc/createRscProxy.mjs +107 -0
  51. package/dist/esm/runtime/rsc/index.mjs +1 -0
  52. package/dist/esm/runtime/rsc/payloadRouter.mjs +162 -0
  53. package/dist/esm/runtime/rsc/server.mjs +200 -0
  54. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +31 -0
  55. package/dist/esm/runtime/rsc/symbols.mjs +17 -0
  56. package/dist/esm/runtime/types.mjs +0 -0
  57. package/dist/esm/runtime/utils.mjs +89 -0
  58. package/dist/esm/runtime.mjs +1 -0
  59. package/dist/esm-node/cli/index.mjs +205 -0
  60. package/dist/esm-node/cli/tanstackTypes.mjs +342 -0
  61. package/dist/esm-node/cli.mjs +3 -0
  62. package/dist/esm-node/rslib-runtime.mjs +19 -0
  63. package/dist/esm-node/runtime/DefaultNotFound.mjs +14 -0
  64. package/dist/esm-node/runtime/basepathRewrite.mjs +29 -0
  65. package/dist/esm-node/runtime/dataMutation.mjs +306 -0
  66. package/dist/esm-node/runtime/hooks.mjs +9 -0
  67. package/dist/esm-node/runtime/index.mjs +7 -0
  68. package/dist/esm-node/runtime/lifecycle.mjs +83 -0
  69. package/dist/esm-node/runtime/plugin.mjs +215 -0
  70. package/dist/esm-node/runtime/plugin.node.mjs +269 -0
  71. package/dist/esm-node/runtime/prefetchLink.mjs +19 -0
  72. package/dist/esm-node/runtime/routeTree.mjs +453 -0
  73. package/dist/esm-node/runtime/rsc/ClientSlot.mjs +20 -0
  74. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +42 -0
  75. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +105 -0
  76. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +32 -0
  77. package/dist/esm-node/runtime/rsc/SlotContext.mjs +18 -0
  78. package/dist/esm-node/runtime/rsc/client.mjs +54 -0
  79. package/dist/esm-node/runtime/rsc/createRscProxy.mjs +108 -0
  80. package/dist/esm-node/runtime/rsc/index.mjs +2 -0
  81. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +163 -0
  82. package/dist/esm-node/runtime/rsc/server.mjs +201 -0
  83. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +32 -0
  84. package/dist/esm-node/runtime/rsc/symbols.mjs +18 -0
  85. package/dist/esm-node/runtime/types.mjs +1 -0
  86. package/dist/esm-node/runtime/utils.mjs +90 -0
  87. package/dist/esm-node/runtime.mjs +2 -0
  88. package/dist/types/cli/index.d.ts +20 -0
  89. package/dist/types/cli/tanstackTypes.d.ts +11 -0
  90. package/dist/types/cli.d.ts +2 -0
  91. package/dist/types/runtime/DefaultNotFound.d.ts +2 -0
  92. package/dist/types/runtime/basepathRewrite.d.ts +8 -0
  93. package/dist/types/runtime/dataMutation.d.ts +29 -0
  94. package/dist/types/runtime/hooks.d.ts +18 -0
  95. package/dist/types/runtime/index.d.ts +9 -0
  96. package/dist/types/runtime/lifecycle.d.ts +22 -0
  97. package/dist/types/runtime/plugin.d.ts +17 -0
  98. package/dist/types/runtime/plugin.node.d.ts +17 -0
  99. package/dist/types/runtime/prefetchLink.d.ts +11 -0
  100. package/dist/types/runtime/routeTree.d.ts +11 -0
  101. package/dist/types/runtime/rsc/ClientSlot.d.ts +5 -0
  102. package/dist/types/runtime/rsc/CompositeComponent.d.ts +3 -0
  103. package/dist/types/runtime/rsc/ReplayableStream.d.ts +24 -0
  104. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +5 -0
  105. package/dist/types/runtime/rsc/SlotContext.d.ts +11 -0
  106. package/dist/types/runtime/rsc/client.d.ts +11 -0
  107. package/dist/types/runtime/rsc/createRscProxy.d.ts +7 -0
  108. package/dist/types/runtime/rsc/index.d.ts +2 -0
  109. package/dist/types/runtime/rsc/payloadRouter.d.ts +24 -0
  110. package/dist/types/runtime/rsc/server.d.ts +14 -0
  111. package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
  112. package/dist/types/runtime/rsc/symbols.d.ts +46 -0
  113. package/dist/types/runtime/types.d.ts +68 -0
  114. package/dist/types/runtime/utils.d.ts +36 -0
  115. package/dist/types/runtime.d.ts +1 -0
  116. package/dist/types-direct/cli/index.d.ts +20 -0
  117. package/dist/types-direct/cli/tanstackTypes.d.ts +11 -0
  118. package/dist/types-direct/cli.d.ts +2 -0
  119. package/dist/types-direct/runtime/DefaultNotFound.d.ts +2 -0
  120. package/dist/types-direct/runtime/basepathRewrite.d.ts +8 -0
  121. package/dist/types-direct/runtime/dataMutation.d.ts +29 -0
  122. package/dist/types-direct/runtime/hooks.d.ts +18 -0
  123. package/dist/types-direct/runtime/index.d.ts +9 -0
  124. package/dist/types-direct/runtime/lifecycle.d.ts +22 -0
  125. package/dist/types-direct/runtime/plugin.d.ts +17 -0
  126. package/dist/types-direct/runtime/plugin.node.d.ts +17 -0
  127. package/dist/types-direct/runtime/prefetchLink.d.ts +11 -0
  128. package/dist/types-direct/runtime/routeTree.d.ts +11 -0
  129. package/dist/types-direct/runtime/rsc/ClientSlot.d.ts +5 -0
  130. package/dist/types-direct/runtime/rsc/CompositeComponent.d.ts +3 -0
  131. package/dist/types-direct/runtime/rsc/ReplayableStream.d.ts +24 -0
  132. package/dist/types-direct/runtime/rsc/RscNodeRenderer.d.ts +5 -0
  133. package/dist/types-direct/runtime/rsc/SlotContext.d.ts +11 -0
  134. package/dist/types-direct/runtime/rsc/client.d.ts +11 -0
  135. package/dist/types-direct/runtime/rsc/createRscProxy.d.ts +7 -0
  136. package/dist/types-direct/runtime/rsc/index.d.ts +2 -0
  137. package/dist/types-direct/runtime/rsc/payloadRouter.d.ts +24 -0
  138. package/dist/types-direct/runtime/rsc/server.d.ts +14 -0
  139. package/dist/types-direct/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
  140. package/dist/types-direct/runtime/rsc/symbols.d.ts +46 -0
  141. package/dist/types-direct/runtime/types.d.ts +68 -0
  142. package/dist/types-direct/runtime/utils.d.ts +36 -0
  143. package/dist/types-direct/runtime.d.ts +1 -0
  144. package/package.json +126 -0
  145. package/rslib.config.mts +4 -0
  146. package/rstest.config.mts +43 -0
  147. package/src/cli/index.ts +388 -0
  148. package/src/cli/tanstackTypes.ts +503 -0
  149. package/src/cli.ts +2 -0
  150. package/src/runtime/DefaultNotFound.tsx +15 -0
  151. package/src/runtime/basepathRewrite.ts +59 -0
  152. package/src/runtime/dataMutation.tsx +517 -0
  153. package/src/runtime/hooks.ts +34 -0
  154. package/src/runtime/index.tsx +30 -0
  155. package/src/runtime/lifecycle.ts +150 -0
  156. package/src/runtime/plugin.node.tsx +534 -0
  157. package/src/runtime/plugin.tsx +395 -0
  158. package/src/runtime/prefetchLink.tsx +87 -0
  159. package/src/runtime/routeTree.ts +942 -0
  160. package/src/runtime/rsc/ClientSlot.tsx +25 -0
  161. package/src/runtime/rsc/CompositeComponent.tsx +65 -0
  162. package/src/runtime/rsc/ReplayableStream.ts +155 -0
  163. package/src/runtime/rsc/RscNodeRenderer.tsx +45 -0
  164. package/src/runtime/rsc/SlotContext.tsx +31 -0
  165. package/src/runtime/rsc/client.tsx +90 -0
  166. package/src/runtime/rsc/createRscProxy.tsx +189 -0
  167. package/src/runtime/rsc/index.ts +10 -0
  168. package/src/runtime/rsc/payloadRouter.ts +318 -0
  169. package/src/runtime/rsc/server.tsx +303 -0
  170. package/src/runtime/rsc/slotUsageSanitizer.ts +76 -0
  171. package/src/runtime/rsc/symbols.ts +106 -0
  172. package/src/runtime/ssr-shim.d.ts +12 -0
  173. package/src/runtime/types.ts +83 -0
  174. package/src/runtime/utils.tsx +161 -0
  175. package/src/runtime.ts +1 -0
  176. package/tests/router/cli.test.ts +386 -0
  177. package/tests/router/dataMutation.test.tsx +396 -0
  178. package/tests/router/prefetchLink.test.tsx +43 -0
  179. package/tests/router/routeTree.test.ts +502 -0
  180. package/tests/router/rsc.test.tsx +256 -0
  181. package/tests/router/tanstackTypes.test.ts +62 -0
  182. package/tsconfig.json +12 -0
  183. package/tsconfig.tsgo.json +6 -0
@@ -0,0 +1,503 @@
1
+ import type { AppToolsContext } from '@modern-js/app-tools';
2
+ import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
3
+ import { findExists, formatImportPath, fs, slash } from '@modern-js/utils';
4
+ import path from 'path';
5
+
6
+ const reservedWords =
7
+ 'break case class catch const continue debugger default delete do else export extends finally for function if import in instanceof let new return super switch this throw try typeof var void while with yield enum await implements package protected static interface private public';
8
+ const builtins =
9
+ 'arguments Infinity NaN undefined null true false eval uneval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Symbol Error EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError Number Math Date String RegExp Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Float32Array Float64Array Map Set WeakMap WeakSet SIMD ArrayBuffer DataView JSON Promise Generator GeneratorFunction Reflect Proxy Intl';
10
+ const forbidList = new Set<string>(`${reservedWords} ${builtins}`.split(' '));
11
+
12
+ function makeLegalIdentifier(str: string) {
13
+ const identifier = str
14
+ .replace(/-(\w)/g, (_, letter) => letter.toUpperCase())
15
+ .replace(/[^$_a-zA-Z0-9]/g, '_');
16
+
17
+ if (/\d/.test(identifier[0]) || forbidList.has(identifier)) {
18
+ return `_${identifier}`;
19
+ }
20
+ return identifier || '_';
21
+ }
22
+
23
+ function getPathWithoutExt(filename: string) {
24
+ const extname = path.extname(filename);
25
+ return extname ? filename.slice(0, -extname.length) : filename;
26
+ }
27
+
28
+ const JS_OR_TS_EXTS = [
29
+ '.js',
30
+ '.jsx',
31
+ '.ts',
32
+ '.tsx',
33
+ '.mjs',
34
+ '.mts',
35
+ '.cjs',
36
+ '.cts',
37
+ ] as const;
38
+
39
+ function toTanstackPath(pathname: string): string {
40
+ return pathname
41
+ .split('/')
42
+ .map(segment => {
43
+ if (!segment) {
44
+ return segment;
45
+ }
46
+ if (segment === '*') {
47
+ return '$';
48
+ }
49
+ if (segment.startsWith(':')) {
50
+ const name = segment.slice(1);
51
+ if (name.endsWith('?')) {
52
+ return `{-$${name.slice(0, -1)}}`;
53
+ }
54
+ return `$${name}`;
55
+ }
56
+ return segment;
57
+ })
58
+ .join('/');
59
+ }
60
+
61
+ async function resolveFileNoExt(inputNoExtPath: string) {
62
+ const file = findExists(JS_OR_TS_EXTS.map(ext => `${inputNoExtPath}${ext}`));
63
+ return file ? getPathWithoutExt(file) : null;
64
+ }
65
+
66
+ function quote(str: string) {
67
+ return JSON.stringify(str);
68
+ }
69
+
70
+ function normalizeRelativeImport(p: string) {
71
+ const normalized = formatImportPath(slash(p));
72
+ if (normalized.startsWith('.')) {
73
+ return normalized;
74
+ }
75
+ return `./${normalized}`;
76
+ }
77
+
78
+ function pickModernLoaderModule(route: NestedRouteForCli | PageRoute) {
79
+ const loaderPath = (route as any).data || (route as any).loader;
80
+ if (!loaderPath || typeof loaderPath !== 'string') {
81
+ return null;
82
+ }
83
+
84
+ const inline = Boolean((route as any).data);
85
+ return { loaderPath, inline };
86
+ }
87
+
88
+ function isPathlessLayout(route: NestedRouteForCli | PageRoute) {
89
+ return (
90
+ (route as any).type === 'nested' &&
91
+ typeof (route as any).index !== 'boolean' &&
92
+ typeof (route as any).path === 'undefined'
93
+ );
94
+ }
95
+
96
+ function isIndexRoute(route: NestedRouteForCli | PageRoute) {
97
+ return (route as any).type === 'nested' && Boolean((route as any).index);
98
+ }
99
+
100
+ function createRouteStaticDataSnippet(opts: {
101
+ modernRouteId?: string;
102
+ loaderName?: string | null;
103
+ actionName?: string | null;
104
+ }) {
105
+ const staticDataLines: string[] = [];
106
+
107
+ if (opts.modernRouteId) {
108
+ staticDataLines.push(`modernRouteId: ${quote(opts.modernRouteId)},`);
109
+ }
110
+
111
+ if (opts.loaderName) {
112
+ staticDataLines.push(`modernRouteLoader: ${opts.loaderName},`);
113
+ }
114
+
115
+ if (opts.actionName) {
116
+ staticDataLines.push(`modernRouteAction: ${opts.actionName},`);
117
+ }
118
+
119
+ if (!staticDataLines.length) {
120
+ return null;
121
+ }
122
+
123
+ return `staticData: createRouteStaticData({\n ${staticDataLines.join(
124
+ '\n ',
125
+ )}\n }),`;
126
+ }
127
+
128
+ export async function isTanstackRouterFrameworkEnabled(
129
+ appContext: AppToolsContext,
130
+ ): Promise<boolean> {
131
+ const runtimeConfigBase = path.join(
132
+ appContext.srcDirectory,
133
+ appContext.runtimeConfigFile,
134
+ );
135
+ const runtimeConfigFile = findExists(
136
+ JS_OR_TS_EXTS.map(ext => `${runtimeConfigBase}${ext}`),
137
+ );
138
+ if (!runtimeConfigFile) {
139
+ return false;
140
+ }
141
+
142
+ try {
143
+ const content = await fs.readFile(runtimeConfigFile, 'utf-8');
144
+ // Heuristic: allow both single and double quotes, tolerate whitespace/newlines.
145
+ return /framework\s*:\s*['"]tanstack['"]/.test(content);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ export async function generateTanstackRouterTypesSourceForEntry(opts: {
152
+ appContext: AppToolsContext;
153
+ entryName: string;
154
+ generatedDirName?: string;
155
+ routes: (NestedRouteForCli | PageRoute)[];
156
+ }): Promise<{
157
+ routerGenTs: string;
158
+ }> {
159
+ const {
160
+ appContext,
161
+ entryName,
162
+ generatedDirName = 'modern-tanstack',
163
+ routes,
164
+ } = opts;
165
+ const outDir = path.join(
166
+ appContext.srcDirectory,
167
+ generatedDirName,
168
+ entryName,
169
+ );
170
+
171
+ const rootModern = routes.find(
172
+ r => r && (r as any).type === 'nested' && (r as any).isRoot,
173
+ ) as NestedRouteForCli | undefined;
174
+
175
+ const topLevel = rootModern
176
+ ? ((rootModern as any).children as Array<NestedRouteForCli | PageRoute>) ||
177
+ []
178
+ : routes;
179
+
180
+ const imports: string[] = [];
181
+ const statements: string[] = [];
182
+
183
+ const loaderImportMap = new Map<string, string>();
184
+ let loaderIndex = 0;
185
+ let routeIndex = 0;
186
+
187
+ const getImportNamesForLoader = async (
188
+ aliasedNoExtPath: string,
189
+ inline: boolean,
190
+ hasAction: boolean,
191
+ ) => {
192
+ const key = `${
193
+ inline ? 'inline' : 'default'
194
+ }:${hasAction ? 'action' : 'loader'}:${aliasedNoExtPath}`;
195
+ const existing = loaderImportMap.get(key);
196
+ if (existing) {
197
+ return {
198
+ loaderName: existing,
199
+ actionName: hasAction ? existing.replace(/^loader_/, 'action_') : null,
200
+ };
201
+ }
202
+
203
+ const prefix = `${appContext.internalSrcAlias}/`;
204
+ let absNoExt: string;
205
+ if (aliasedNoExtPath.startsWith(prefix)) {
206
+ const rel = aliasedNoExtPath.slice(prefix.length);
207
+ absNoExt = path.join(appContext.srcDirectory, rel);
208
+ } else if (path.isAbsolute(aliasedNoExtPath)) {
209
+ absNoExt = aliasedNoExtPath;
210
+ } else {
211
+ // Unknown format; treat as already relative to src.
212
+ absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
213
+ }
214
+
215
+ const resolvedNoExt = await resolveFileNoExt(absNoExt);
216
+ if (!resolvedNoExt) {
217
+ return null;
218
+ }
219
+
220
+ const relImport = normalizeRelativeImport(
221
+ path.relative(outDir, resolvedNoExt),
222
+ );
223
+
224
+ const importName = `loader_${loaderIndex++}`;
225
+ const actionName = hasAction
226
+ ? importName.replace(/^loader_/, 'action_')
227
+ : null;
228
+ if (inline) {
229
+ const specifiers = [`loader as ${importName}`];
230
+ if (actionName) {
231
+ specifiers.push(`action as ${actionName}`);
232
+ }
233
+ imports.push(
234
+ `import { ${specifiers.join(', ')} } from ${quote(relImport)};`,
235
+ );
236
+ } else {
237
+ imports.push(`import ${importName} from ${quote(relImport)};`);
238
+ }
239
+
240
+ loaderImportMap.set(key, importName);
241
+ return { loaderName: importName, actionName };
242
+ };
243
+
244
+ const createRouteVarName = (route: NestedRouteForCli | PageRoute) => {
245
+ const id = (route as any).id as string | undefined;
246
+ const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
247
+ return `route_${base}`;
248
+ };
249
+
250
+ const buildRoute = async (opts: {
251
+ parentVar: string;
252
+ route: NestedRouteForCli | PageRoute;
253
+ }): Promise<string> => {
254
+ const { parentVar, route } = opts;
255
+
256
+ const varName = createRouteVarName(route);
257
+
258
+ const loaderInfo = pickModernLoaderModule(route);
259
+ const routeAction = (route as any).action;
260
+ const loaderImports = loaderInfo
261
+ ? await getImportNamesForLoader(
262
+ loaderInfo.loaderPath,
263
+ loaderInfo.inline,
264
+ Boolean(loaderInfo.inline && routeAction === loaderInfo.loaderPath),
265
+ )
266
+ : null;
267
+ const loaderName = loaderImports?.loaderName || null;
268
+ const actionName = loaderImports?.actionName || null;
269
+
270
+ const rawPath = (route as any).path as string | undefined;
271
+ const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
272
+
273
+ const routeOpts: string[] = [`getParentRoute: () => ${parentVar},`];
274
+
275
+ if (isPathlessLayout(route)) {
276
+ const id = (route as any).id as string | undefined;
277
+ routeOpts.push(`id: ${quote(id || 'pathless')},`);
278
+ } else {
279
+ const p = isIndexRoute(route) ? '/' : toTanstackPath(rawPath || '');
280
+ routeOpts.push(`path: ${quote(p)},`);
281
+ }
282
+
283
+ if (loaderName) {
284
+ routeOpts.push(
285
+ `loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
286
+ );
287
+ }
288
+
289
+ const staticDataSnippet = createRouteStaticDataSnippet({
290
+ modernRouteId: (route as any).id as string | undefined,
291
+ loaderName,
292
+ actionName,
293
+ });
294
+ if (staticDataSnippet) {
295
+ routeOpts.push(staticDataSnippet);
296
+ }
297
+
298
+ statements.push(
299
+ `const ${varName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
300
+ );
301
+
302
+ const children = (route as any).children as
303
+ | Array<NestedRouteForCli | PageRoute>
304
+ | undefined;
305
+ if (children && children.length > 0) {
306
+ const childVars = await Promise.all(
307
+ children.map(child => buildRoute({ parentVar: varName, route: child })),
308
+ );
309
+ statements.push(`${varName}.addChildren([${childVars.join(', ')}]);`);
310
+ }
311
+
312
+ return varName;
313
+ };
314
+
315
+ const rootLoaderInfo = rootModern ? pickModernLoaderModule(rootModern) : null;
316
+ const rootAction = (rootModern as any)?.action;
317
+ const rootLoaderImports = rootLoaderInfo?.loaderPath
318
+ ? await getImportNamesForLoader(
319
+ rootLoaderInfo.loaderPath,
320
+ rootLoaderInfo.inline,
321
+ Boolean(
322
+ rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath,
323
+ ),
324
+ )
325
+ : null;
326
+ const rootLoaderName = rootLoaderImports?.loaderName || null;
327
+ const rootActionName = rootLoaderImports?.actionName || null;
328
+
329
+ const topLevelVars = await Promise.all(
330
+ topLevel.map(route => buildRoute({ parentVar: 'rootRoute', route })),
331
+ );
332
+
333
+ const rootOpts: string[] = [];
334
+ if (rootLoaderName) {
335
+ rootOpts.push(
336
+ `loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
337
+ );
338
+ }
339
+
340
+ const routerGenTs = `/* eslint-disable */
341
+ // This file is auto-generated by Modern.js. Do not edit manually.
342
+
343
+ import {
344
+ createMemoryHistory,
345
+ createRootRouteWithContext,
346
+ createRoute,
347
+ createRouter,
348
+ notFound,
349
+ redirect,
350
+ } from '@modern-js/plugin-tanstack/runtime';
351
+
352
+ type ModernRouterContext = {
353
+ request?: Request;
354
+ requestContext?: unknown;
355
+ };
356
+
357
+ function isResponse(value: unknown): value is Response {
358
+ return (
359
+ value != null &&
360
+ typeof value === 'object' &&
361
+ typeof (value as any).status === 'number' &&
362
+ typeof (value as any).headers === 'object'
363
+ );
364
+ }
365
+
366
+ const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
367
+ function isRedirectResponse(res: Response) {
368
+ return redirectStatusCodes.has(res.status);
369
+ }
370
+
371
+ function throwTanstackRedirect(location: string) {
372
+ const target = location || '/';
373
+ try {
374
+ void new URL(target);
375
+ throw redirect({ href: target });
376
+ } catch {
377
+ throw redirect({ to: target });
378
+ }
379
+ }
380
+
381
+ function mapParamsForModernLoader(params: Record<string, string>, hasSplat: boolean) {
382
+ if (!hasSplat) {
383
+ return params;
384
+ }
385
+
386
+ const { _splat, ...rest } = params as any;
387
+ if (typeof _splat !== 'undefined') {
388
+ return { ...rest, '*': _splat };
389
+ }
390
+ return rest;
391
+ }
392
+
393
+ function createRouteStaticData(opts: {
394
+ modernRouteId?: string;
395
+ modernRouteAction?: unknown;
396
+ modernRouteLoader?: unknown;
397
+ }) {
398
+ const staticData: Record<string, unknown> = {};
399
+
400
+ if (opts.modernRouteId) {
401
+ staticData.modernRouteId = opts.modernRouteId;
402
+ }
403
+
404
+ if (opts.modernRouteLoader) {
405
+ staticData.modernRouteLoader = opts.modernRouteLoader;
406
+ }
407
+
408
+ if (opts.modernRouteAction) {
409
+ staticData.modernRouteAction = opts.modernRouteAction;
410
+ }
411
+
412
+ return Object.keys(staticData).length > 0 ? staticData : undefined;
413
+ }
414
+
415
+ function modernLoaderToTanstack<TLoader extends (args: any) => any>(
416
+ opts: { hasSplat: boolean },
417
+ modernLoader: TLoader,
418
+ ) {
419
+ type LoaderResult = Awaited<ReturnType<TLoader>>;
420
+
421
+ return async (ctx: any): Promise<LoaderResult> => {
422
+ try {
423
+ const signal: AbortSignal =
424
+ ctx?.abortController?.signal ||
425
+ ctx?.signal ||
426
+ new AbortController().signal;
427
+ const baseRequest: Request | undefined =
428
+ ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
429
+
430
+ const href =
431
+ typeof ctx?.location === 'string'
432
+ ? ctx.location
433
+ : ctx?.location?.publicHref ||
434
+ ctx?.location?.href ||
435
+ ctx?.location?.url?.href ||
436
+ '';
437
+
438
+ const request = baseRequest
439
+ ? new Request(baseRequest, { signal })
440
+ : new Request(href, { signal });
441
+
442
+ const params = mapParamsForModernLoader(ctx?.params || {}, opts.hasSplat);
443
+
444
+ const result = await (modernLoader as any)({
445
+ request,
446
+ params,
447
+ context: ctx?.context?.requestContext,
448
+ });
449
+
450
+ if (isResponse(result)) {
451
+ if (isRedirectResponse(result)) {
452
+ const location = result.headers.get('Location') || '/';
453
+ throwTanstackRedirect(location);
454
+ }
455
+ if (result.status === 404) {
456
+ throw notFound();
457
+ }
458
+ }
459
+
460
+ return result as LoaderResult;
461
+ } catch (err) {
462
+ if (isResponse(err)) {
463
+ if (isRedirectResponse(err)) {
464
+ const location = err.headers.get('Location') || '/';
465
+ throwTanstackRedirect(location);
466
+ }
467
+ if (err.status === 404) {
468
+ throw notFound();
469
+ }
470
+ }
471
+ throw err;
472
+ }
473
+ };
474
+ }
475
+
476
+ ${imports.join('\n')}
477
+
478
+ export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
479
+ ${rootOpts.join('\n ')}
480
+ ${
481
+ createRouteStaticDataSnippet({
482
+ modernRouteId: (rootModern as any)?.id as string | undefined,
483
+ loaderName: rootLoaderName,
484
+ actionName: rootActionName,
485
+ }) || ''
486
+ }
487
+ });
488
+
489
+ ${statements.join('\n\n')}
490
+
491
+ export const routeTree = rootRoute.addChildren([${topLevelVars.join(', ')}]);
492
+
493
+ export const router = createRouter({
494
+ routeTree,
495
+ history: createMemoryHistory({
496
+ initialEntries: ['/'],
497
+ }),
498
+ context: {} as ModernRouterContext,
499
+ });
500
+ `;
501
+
502
+ return { routerGenTs };
503
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './cli/index';
2
+ export { default } from './cli/index';
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ export const DefaultNotFound = () => (
4
+ <div
5
+ style={{
6
+ margin: '150px auto',
7
+ textAlign: 'center',
8
+ display: 'flex',
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ }}
12
+ >
13
+ 404
14
+ </div>
15
+ );
@@ -0,0 +1,59 @@
1
+ function normalizeBasepath(basepath: string): string {
2
+ if (!basepath) {
3
+ return '/';
4
+ }
5
+
6
+ let normalized = basepath.startsWith('/') ? basepath : `/${basepath}`;
7
+ if (normalized.length > 1 && normalized.endsWith('/')) {
8
+ normalized = normalized.slice(0, -1);
9
+ }
10
+
11
+ return normalized || '/';
12
+ }
13
+
14
+ export function createModernBasepathRewrite(
15
+ basepath: string,
16
+ caseSensitive = false,
17
+ ) {
18
+ const normalizedBasepath = normalizeBasepath(basepath);
19
+ if (normalizedBasepath === '/') {
20
+ return undefined;
21
+ }
22
+
23
+ const normalizedBasepathWithSlash = `${normalizedBasepath}/`;
24
+ const checkBasepath = caseSensitive
25
+ ? normalizedBasepath
26
+ : normalizedBasepath.toLowerCase();
27
+ const checkBasepathWithSlash = caseSensitive
28
+ ? normalizedBasepathWithSlash
29
+ : normalizedBasepathWithSlash.toLowerCase();
30
+
31
+ return {
32
+ input: ({ url }: { url: URL }) => {
33
+ const pathname = caseSensitive
34
+ ? url.pathname
35
+ : url.pathname.toLowerCase();
36
+
37
+ if (pathname === checkBasepath) {
38
+ url.pathname = '/';
39
+ } else if (pathname.startsWith(checkBasepathWithSlash)) {
40
+ url.pathname = url.pathname.slice(normalizedBasepath.length) || '/';
41
+ }
42
+
43
+ return url;
44
+ },
45
+ output: ({ url }: { url: URL }) => {
46
+ const pathname = url.pathname || '/';
47
+
48
+ // Unlike TanStack Router's built-in `basepath` rewrite, avoid adding an
49
+ // extra trailing slash for the base-path root.
50
+ if (pathname === '/') {
51
+ url.pathname = normalizedBasepath;
52
+ } else {
53
+ url.pathname = `${normalizedBasepath}${pathname.startsWith('/') ? '' : '/'}${pathname}`;
54
+ }
55
+
56
+ return url;
57
+ },
58
+ };
59
+ }