@tanstack/router-generator 1.120.7 → 1.121.0-alpha.11
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.
- package/dist/cjs/config.cjs +14 -14
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/config.d.cts +68 -31
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs +1 -5
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +2 -2
- package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/virtual/getRouteNodes.d.cts +2 -2
- package/dist/cjs/generator.cjs +160 -172
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/generator.d.cts +0 -59
- package/dist/cjs/index.cjs +23 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +8 -4
- package/dist/cjs/template.cjs +4 -12
- package/dist/cjs/template.cjs.map +1 -1
- package/dist/cjs/template.d.cts +0 -1
- package/dist/cjs/types.d.cts +1 -1
- package/dist/cjs/utils.cjs +64 -5
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +11 -2
- package/dist/esm/config.d.ts +68 -31
- package/dist/esm/config.js +14 -14
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/filesystem/physical/getRouteNodes.d.ts +2 -2
- package/dist/esm/filesystem/physical/getRouteNodes.js +2 -6
- package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
- package/dist/esm/filesystem/virtual/getRouteNodes.d.ts +2 -2
- package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -1
- package/dist/esm/generator.d.ts +0 -59
- package/dist/esm/generator.js +163 -175
- package/dist/esm/generator.js.map +1 -1
- package/dist/esm/index.d.ts +8 -4
- package/dist/esm/index.js +25 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/template.d.ts +0 -1
- package/dist/esm/template.js +4 -12
- package/dist/esm/template.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/utils.d.ts +11 -2
- package/dist/esm/utils.js +63 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/config.ts +14 -11
- package/src/filesystem/physical/getRouteNodes.ts +13 -14
- package/src/filesystem/virtual/getRouteNodes.ts +18 -3
- package/src/generator.ts +242 -221
- package/src/index.ts +32 -7
- package/src/template.ts +4 -15
- package/src/types.ts +0 -1
- package/src/utils.ts +102 -6
package/src/generator.ts
CHANGED
|
@@ -2,12 +2,10 @@ import path from 'node:path'
|
|
|
2
2
|
import * as fs from 'node:fs'
|
|
3
3
|
import * as fsp from 'node:fs/promises'
|
|
4
4
|
import {
|
|
5
|
-
determineInitialRoutePath,
|
|
6
5
|
format,
|
|
7
6
|
logging,
|
|
8
7
|
multiSortBy,
|
|
9
8
|
removeExt,
|
|
10
|
-
removeTrailingSlash,
|
|
11
9
|
removeUnderscores,
|
|
12
10
|
replaceBackslash,
|
|
13
11
|
resetRegex,
|
|
@@ -18,18 +16,12 @@ import {
|
|
|
18
16
|
import { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes'
|
|
19
17
|
import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
|
|
20
18
|
import { rootPathId } from './filesystem/physical/rootPathId'
|
|
21
|
-
import {
|
|
22
|
-
defaultAPIRouteTemplate,
|
|
23
|
-
fillTemplate,
|
|
24
|
-
getTargetTemplate,
|
|
25
|
-
} from './template'
|
|
19
|
+
import { fillTemplate, getTargetTemplate } from './template'
|
|
26
20
|
import type { FsRouteType, GetRouteNodesResult, RouteNode } from './types'
|
|
27
21
|
import type { Config } from './config'
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
APIRouteExportVariable: 'APIRoute',
|
|
32
|
-
}
|
|
23
|
+
// Maybe import this from `@tanstack/router-core` in the future???
|
|
24
|
+
const rootRouteId = '__root__'
|
|
33
25
|
|
|
34
26
|
let latestTask = 0
|
|
35
27
|
const routeGroupPatternRegex = /\(.+\)/g
|
|
@@ -76,10 +68,6 @@ export async function generator(config: Config, root: string) {
|
|
|
76
68
|
|
|
77
69
|
const TYPES_DISABLED = config.disableTypes
|
|
78
70
|
|
|
79
|
-
// Controls whether API Routes are generated for TanStack Start
|
|
80
|
-
const ENABLED_API_ROUTES_GENERATION =
|
|
81
|
-
config.__enableAPIRoutesGeneration ?? false
|
|
82
|
-
|
|
83
71
|
let getRouteNodesResult: GetRouteNodesResult
|
|
84
72
|
|
|
85
73
|
if (config.virtualRouteConfig) {
|
|
@@ -117,30 +105,6 @@ export async function generator(config: Config, root: string) {
|
|
|
117
105
|
const routeTree: Array<RouteNode> = []
|
|
118
106
|
const routePiecesByPath: Record<string, RouteSubNode> = {}
|
|
119
107
|
|
|
120
|
-
// Filtered API Route nodes
|
|
121
|
-
const onlyAPIRouteNodes = preRouteNodes.filter((d) => {
|
|
122
|
-
if (!ENABLED_API_ROUTES_GENERATION) {
|
|
123
|
-
return false
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (d._fsRouteType !== 'api') {
|
|
127
|
-
return false
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return true
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
// Filtered Generator Route nodes
|
|
134
|
-
const onlyGeneratorRouteNodes = preRouteNodes.filter((d) => {
|
|
135
|
-
if (ENABLED_API_ROUTES_GENERATION) {
|
|
136
|
-
if (d._fsRouteType === 'api') {
|
|
137
|
-
return false
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return true
|
|
142
|
-
})
|
|
143
|
-
|
|
144
108
|
// Loop over the flat list of routeNodes and
|
|
145
109
|
// build up a tree based on the routeNodes' routePath
|
|
146
110
|
const routeNodes: Array<RouteNode> = []
|
|
@@ -243,7 +207,7 @@ export async function generator(config: Config, root: string) {
|
|
|
243
207
|
tLazyRouteTemplate.template(),
|
|
244
208
|
{
|
|
245
209
|
tsrImports: tLazyRouteTemplate.imports.tsrImports(),
|
|
246
|
-
tsrPath: escapedRoutePath,
|
|
210
|
+
tsrPath: escapedRoutePath.replaceAll(/\{(.+)\}/gm, '$1'),
|
|
247
211
|
tsrExportStart:
|
|
248
212
|
tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
249
213
|
tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd(),
|
|
@@ -269,20 +233,104 @@ export async function generator(config: Config, root: string) {
|
|
|
269
233
|
tRouteTemplate.template(),
|
|
270
234
|
{
|
|
271
235
|
tsrImports: tRouteTemplate.imports.tsrImports(),
|
|
272
|
-
tsrPath: escapedRoutePath,
|
|
236
|
+
tsrPath: escapedRoutePath.replaceAll(/\{(.+)\}/gm, '$1'),
|
|
273
237
|
tsrExportStart:
|
|
274
238
|
tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
275
239
|
tsrExportEnd: tRouteTemplate.imports.tsrExportEnd(),
|
|
276
240
|
},
|
|
277
241
|
)
|
|
278
242
|
}
|
|
243
|
+
} else if (config.verboseFileRoutes === false) {
|
|
244
|
+
// Check if the route file has a Route export
|
|
245
|
+
if (
|
|
246
|
+
!routeCode
|
|
247
|
+
.split('\n')
|
|
248
|
+
.some((line) => line.trim().startsWith('export const Route'))
|
|
249
|
+
) {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Update the existing route file
|
|
254
|
+
replaced = routeCode
|
|
255
|
+
.replace(
|
|
256
|
+
/(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
257
|
+
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
258
|
+
)
|
|
259
|
+
.replace(
|
|
260
|
+
new RegExp(
|
|
261
|
+
`(import\\s*\\{)(.*)(create(Lazy)?FileRoute)(.*)(\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
262
|
+
'gs',
|
|
263
|
+
),
|
|
264
|
+
(_, p1, p2, ___, ____, p5, p6) => {
|
|
265
|
+
const beforeCreateFileRoute = () => {
|
|
266
|
+
if (!p2) return ''
|
|
267
|
+
|
|
268
|
+
let trimmed = p2.trim()
|
|
269
|
+
|
|
270
|
+
if (trimmed.endsWith(',')) {
|
|
271
|
+
trimmed = trimmed.slice(0, -1)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return trimmed
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const afterCreateFileRoute = () => {
|
|
278
|
+
if (!p5) return ''
|
|
279
|
+
|
|
280
|
+
let trimmed = p5.trim()
|
|
281
|
+
|
|
282
|
+
if (trimmed.startsWith(',')) {
|
|
283
|
+
trimmed = trimmed.slice(1)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return trimmed
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const newImport = () => {
|
|
290
|
+
const before = beforeCreateFileRoute()
|
|
291
|
+
const after = afterCreateFileRoute()
|
|
292
|
+
|
|
293
|
+
if (!before) return after
|
|
294
|
+
|
|
295
|
+
if (!after) return before
|
|
296
|
+
|
|
297
|
+
return `${before},${after}`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const middle = newImport()
|
|
301
|
+
|
|
302
|
+
if (middle === '') return ''
|
|
303
|
+
|
|
304
|
+
return `${p1} ${newImport()} ${p6}`
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
.replace(
|
|
308
|
+
/create(Lazy)?FileRoute(\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
309
|
+
(_, __, p2, ___, p4) =>
|
|
310
|
+
`${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}`,
|
|
311
|
+
)
|
|
279
312
|
} else {
|
|
313
|
+
// Check if the route file has a Route export
|
|
314
|
+
if (
|
|
315
|
+
!routeCode
|
|
316
|
+
.split('\n')
|
|
317
|
+
.some((line) => line.trim().startsWith('export const Route'))
|
|
318
|
+
) {
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
280
322
|
// Update the existing route file
|
|
281
323
|
replaced = routeCode
|
|
324
|
+
// fix wrong ids
|
|
282
325
|
.replace(
|
|
283
326
|
/(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
284
327
|
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
285
328
|
)
|
|
329
|
+
// fix missing ids
|
|
330
|
+
.replace(
|
|
331
|
+
/((FileRoute)(\s*)(\({))/g,
|
|
332
|
+
(_, __, p2, p3, p4) => `${p2}('${escapedRoutePath}')${p3}${p4}`,
|
|
333
|
+
)
|
|
286
334
|
.replace(
|
|
287
335
|
new RegExp(
|
|
288
336
|
`(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
@@ -296,6 +344,18 @@ export async function generator(config: Config, root: string) {
|
|
|
296
344
|
(_, __, p2, ___, p4) =>
|
|
297
345
|
`${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`,
|
|
298
346
|
)
|
|
347
|
+
|
|
348
|
+
// check whether the import statement is already present
|
|
349
|
+
const regex = new RegExp(
|
|
350
|
+
`(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
351
|
+
'gm',
|
|
352
|
+
)
|
|
353
|
+
if (!replaced.match(regex)) {
|
|
354
|
+
replaced = [
|
|
355
|
+
`import { ${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'} } from '@tanstack/${ROUTE_TEMPLATE.subPkg}'`,
|
|
356
|
+
...replaced.split('\n'),
|
|
357
|
+
].join('\n')
|
|
358
|
+
}
|
|
299
359
|
}
|
|
300
360
|
|
|
301
361
|
await writeIfDifferent(node.fullPath, routeCode, replaced, {
|
|
@@ -406,77 +466,22 @@ export async function generator(config: Config, root: string) {
|
|
|
406
466
|
routeNodes.push(node)
|
|
407
467
|
}
|
|
408
468
|
|
|
409
|
-
for (const node of
|
|
469
|
+
for (const node of preRouteNodes) {
|
|
410
470
|
await handleNode(node)
|
|
411
471
|
}
|
|
472
|
+
|
|
473
|
+
// This is run against the `preRouteNodes` array since it
|
|
474
|
+
// has the flattened Route nodes and not the full tree
|
|
475
|
+
// Since TSR allows multiple way of defining a route,
|
|
476
|
+
// we need to ensure that a user hasn't defined the
|
|
477
|
+
// same route in multiple ways (i.e. `flat`, `nested`, `virtual`)
|
|
412
478
|
checkRouteFullPathUniqueness(
|
|
413
479
|
preRouteNodes.filter(
|
|
414
|
-
(d) =>
|
|
415
|
-
d.children === undefined &&
|
|
416
|
-
(['api', 'lazy'] satisfies Array<FsRouteType>).every(
|
|
417
|
-
(type) => type !== d._fsRouteType,
|
|
418
|
-
),
|
|
480
|
+
(d) => d.children === undefined && 'lazy' !== d._fsRouteType,
|
|
419
481
|
),
|
|
420
482
|
config,
|
|
421
483
|
)
|
|
422
484
|
|
|
423
|
-
const startAPIRouteNodes: Array<RouteNode> = checkStartAPIRoutes(
|
|
424
|
-
onlyAPIRouteNodes,
|
|
425
|
-
config,
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
const handleAPINode = async (node: RouteNode) => {
|
|
429
|
-
const routeCode = fs.readFileSync(node.fullPath, 'utf-8')
|
|
430
|
-
|
|
431
|
-
const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? ''
|
|
432
|
-
|
|
433
|
-
if (!routeCode) {
|
|
434
|
-
const replaced = await fillTemplate(
|
|
435
|
-
config,
|
|
436
|
-
config.customScaffolding?.apiTemplate ?? defaultAPIRouteTemplate,
|
|
437
|
-
{
|
|
438
|
-
tsrImports:
|
|
439
|
-
"import { createAPIFileRoute } from '@tanstack/react-start/api';",
|
|
440
|
-
tsrPath: escapedRoutePath,
|
|
441
|
-
tsrExportStart: `export const ${CONSTANTS.APIRouteExportVariable} = createAPIFileRoute('${escapedRoutePath}')(`,
|
|
442
|
-
tsrExportEnd: ');',
|
|
443
|
-
},
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
await writeIfDifferent(
|
|
447
|
-
node.fullPath,
|
|
448
|
-
'', // Empty string because the file doesn't exist yet
|
|
449
|
-
replaced,
|
|
450
|
-
{
|
|
451
|
-
beforeWrite: () => {
|
|
452
|
-
logger.log(`🟡 Creating ${node.fullPath}`)
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
)
|
|
456
|
-
} else {
|
|
457
|
-
await writeIfDifferent(
|
|
458
|
-
node.fullPath,
|
|
459
|
-
routeCode,
|
|
460
|
-
routeCode.replace(
|
|
461
|
-
/(createAPIFileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
462
|
-
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
463
|
-
),
|
|
464
|
-
{
|
|
465
|
-
beforeWrite: () => {
|
|
466
|
-
logger.log(`🟡 Updating ${node.fullPath}`)
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Handle the API routes for TanStack Start
|
|
474
|
-
if (ENABLED_API_ROUTES_GENERATION) {
|
|
475
|
-
for (const node of startAPIRouteNodes) {
|
|
476
|
-
await handleAPINode(node)
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
485
|
function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
|
|
481
486
|
const children = nodes.map((node) => {
|
|
482
487
|
if (node._fsRouteType === '__root') {
|
|
@@ -527,6 +532,30 @@ export async function generator(config: Config, root: string) {
|
|
|
527
532
|
(d) => d,
|
|
528
533
|
])
|
|
529
534
|
|
|
535
|
+
const typeImports = Object.entries({
|
|
536
|
+
// Used for augmentation of regular routes
|
|
537
|
+
CreateFileRoute:
|
|
538
|
+
config.verboseFileRoutes === false &&
|
|
539
|
+
sortedRouteNodes.some(
|
|
540
|
+
(d) => isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy',
|
|
541
|
+
),
|
|
542
|
+
// Used for augmentation of lazy (`.lazy`) routes
|
|
543
|
+
CreateLazyFileRoute:
|
|
544
|
+
config.verboseFileRoutes === false &&
|
|
545
|
+
sortedRouteNodes.some(
|
|
546
|
+
(node) =>
|
|
547
|
+
routePiecesByPath[node.routePath!]?.lazy &&
|
|
548
|
+
isRouteNodeValidForAugmentation(node),
|
|
549
|
+
),
|
|
550
|
+
// Used in the process of augmenting the routes
|
|
551
|
+
FileRoutesByPath:
|
|
552
|
+
config.verboseFileRoutes === false &&
|
|
553
|
+
sortedRouteNodes.some((d) => isRouteNodeValidForAugmentation(d)),
|
|
554
|
+
})
|
|
555
|
+
.filter((d) => d[1])
|
|
556
|
+
.map((d) => d[0])
|
|
557
|
+
.sort((a, b) => a.localeCompare(b))
|
|
558
|
+
|
|
530
559
|
const imports = Object.entries({
|
|
531
560
|
createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
|
|
532
561
|
lazyFn: sortedRouteNodes.some(
|
|
@@ -555,14 +584,22 @@ export async function generator(config: Config, root: string) {
|
|
|
555
584
|
),
|
|
556
585
|
)
|
|
557
586
|
}
|
|
587
|
+
|
|
558
588
|
const routeImports = [
|
|
559
589
|
...config.routeTreeFileHeader,
|
|
560
590
|
`// This file was automatically generated by TanStack Router.
|
|
561
591
|
// You should NOT make any changes in this file as it will be overwritten.
|
|
562
592
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
593
|
+
[
|
|
594
|
+
imports.length
|
|
595
|
+
? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
|
|
596
|
+
: '',
|
|
597
|
+
!TYPES_DISABLED && typeImports.length
|
|
598
|
+
? `import type { ${typeImports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
|
|
599
|
+
: '',
|
|
600
|
+
]
|
|
601
|
+
.filter(Boolean)
|
|
602
|
+
.join('\n'),
|
|
566
603
|
'// Import Routes',
|
|
567
604
|
[
|
|
568
605
|
`import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
|
|
@@ -571,7 +608,7 @@ export async function generator(config: Config, root: string) {
|
|
|
571
608
|
.map((node) => {
|
|
572
609
|
return `import { Route as ${
|
|
573
610
|
node.variableName
|
|
574
|
-
}
|
|
611
|
+
}RouteImport } from './${getImportPath(node)}'`
|
|
575
612
|
}),
|
|
576
613
|
].join('\n'),
|
|
577
614
|
virtualRouteNodes.length ? '// Create Virtual Routes' : '',
|
|
@@ -579,7 +616,7 @@ export async function generator(config: Config, root: string) {
|
|
|
579
616
|
.map((node) => {
|
|
580
617
|
return `const ${
|
|
581
618
|
node.variableName
|
|
582
|
-
}
|
|
619
|
+
}RouteImport = createFileRoute('${node.routePath}')()`
|
|
583
620
|
})
|
|
584
621
|
.join('\n'),
|
|
585
622
|
'// Create/Update Routes',
|
|
@@ -594,7 +631,8 @@ export async function generator(config: Config, root: string) {
|
|
|
594
631
|
const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
|
|
595
632
|
|
|
596
633
|
return [
|
|
597
|
-
|
|
634
|
+
[
|
|
635
|
+
`const ${node.variableName}Route = ${node.variableName}RouteImport.update({
|
|
598
636
|
${[
|
|
599
637
|
`id: '${node.path}'`,
|
|
600
638
|
!node.isNonPath ? `path: '${node.cleanedPath}'` : undefined,
|
|
@@ -603,19 +641,19 @@ export async function generator(config: Config, root: string) {
|
|
|
603
641
|
.filter(Boolean)
|
|
604
642
|
.join(',')}
|
|
605
643
|
}${TYPES_DISABLED ? '' : 'as any'})`,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
644
|
+
loaderNode
|
|
645
|
+
? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
|
|
646
|
+
removeExt(
|
|
647
|
+
path.relative(
|
|
648
|
+
path.dirname(config.generatedRouteTree),
|
|
649
|
+
path.resolve(config.routesDirectory, loaderNode.filePath),
|
|
650
|
+
),
|
|
651
|
+
config.addExtensions,
|
|
612
652
|
),
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
componentNode || errorComponentNode || pendingComponentNode
|
|
618
|
-
? `.update({
|
|
653
|
+
)}'), 'loader') })`
|
|
654
|
+
: '',
|
|
655
|
+
componentNode || errorComponentNode || pendingComponentNode
|
|
656
|
+
? `.update({
|
|
619
657
|
${(
|
|
620
658
|
[
|
|
621
659
|
['component', componentNode],
|
|
@@ -639,22 +677,23 @@ export async function generator(config: Config, root: string) {
|
|
|
639
677
|
})
|
|
640
678
|
.join('\n,')}
|
|
641
679
|
})`
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
680
|
+
: '',
|
|
681
|
+
lazyComponentNode
|
|
682
|
+
? `.lazy(() => import('./${replaceBackslash(
|
|
683
|
+
removeExt(
|
|
684
|
+
path.relative(
|
|
685
|
+
path.dirname(config.generatedRouteTree),
|
|
686
|
+
path.resolve(
|
|
687
|
+
config.routesDirectory,
|
|
688
|
+
lazyComponentNode.filePath,
|
|
689
|
+
),
|
|
651
690
|
),
|
|
691
|
+
config.addExtensions,
|
|
652
692
|
),
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
].join('')
|
|
693
|
+
)}').then((d) => d.Route))`
|
|
694
|
+
: '',
|
|
695
|
+
].join(''),
|
|
696
|
+
].join('\n\n')
|
|
658
697
|
})
|
|
659
698
|
.join('\n\n'),
|
|
660
699
|
...(TYPES_DISABLED
|
|
@@ -671,12 +710,12 @@ export async function generator(config: Config, root: string) {
|
|
|
671
710
|
id: '${filePathId}'
|
|
672
711
|
path: '${inferPath(routeNode)}'
|
|
673
712
|
fullPath: '${inferFullPath(routeNode)}'
|
|
674
|
-
preLoaderRoute: typeof ${routeNode.variableName}
|
|
713
|
+
preLoaderRoute: typeof ${routeNode.variableName}RouteImport
|
|
675
714
|
parentRoute: typeof ${
|
|
676
715
|
routeNode.isVirtualParentRequired
|
|
677
716
|
? `${routeNode.parent?.variableName}Route`
|
|
678
717
|
: routeNode.parent?.variableName
|
|
679
|
-
? `${routeNode.parent.variableName}
|
|
718
|
+
? `${routeNode.parent.variableName}RouteImport`
|
|
680
719
|
: 'rootRoute'
|
|
681
720
|
}
|
|
682
721
|
}`
|
|
@@ -685,6 +724,41 @@ export async function generator(config: Config, root: string) {
|
|
|
685
724
|
}
|
|
686
725
|
}`,
|
|
687
726
|
]),
|
|
727
|
+
...(TYPES_DISABLED
|
|
728
|
+
? []
|
|
729
|
+
: config.verboseFileRoutes !== false
|
|
730
|
+
? []
|
|
731
|
+
: [
|
|
732
|
+
`// Add type-safety to the createFileRoute function across the route tree`,
|
|
733
|
+
routeNodes
|
|
734
|
+
.map((routeNode) => {
|
|
735
|
+
function getModuleDeclaration(routeNode?: RouteNode) {
|
|
736
|
+
if (!isRouteNodeValidForAugmentation(routeNode)) {
|
|
737
|
+
return ''
|
|
738
|
+
}
|
|
739
|
+
return `declare module './${getImportPath(routeNode)}' {
|
|
740
|
+
const ${routeNode._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}: ${
|
|
741
|
+
routeNode._fsRouteType === 'lazy'
|
|
742
|
+
? `CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>}`
|
|
743
|
+
: `CreateFileRoute<
|
|
744
|
+
'${routeNode.routePath}',
|
|
745
|
+
FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
|
|
746
|
+
FileRoutesByPath['${routeNode.routePath}']['id'],
|
|
747
|
+
FileRoutesByPath['${routeNode.routePath}']['path'],
|
|
748
|
+
FileRoutesByPath['${routeNode.routePath}']['fullPath']
|
|
749
|
+
>
|
|
750
|
+
}`
|
|
751
|
+
}`
|
|
752
|
+
}
|
|
753
|
+
return (
|
|
754
|
+
getModuleDeclaration(routeNode) +
|
|
755
|
+
getModuleDeclaration(
|
|
756
|
+
routePiecesByPath[routeNode.routePath!]?.lazy,
|
|
757
|
+
)
|
|
758
|
+
)
|
|
759
|
+
})
|
|
760
|
+
.join('\n'),
|
|
761
|
+
]),
|
|
688
762
|
'// Create and export the route tree',
|
|
689
763
|
routeConfigChildrenText,
|
|
690
764
|
...(TYPES_DISABLED
|
|
@@ -703,7 +777,7 @@ export async function generator(config: Config, root: string) {
|
|
|
703
777
|
})}
|
|
704
778
|
}`,
|
|
705
779
|
`export interface FileRoutesById {
|
|
706
|
-
'
|
|
780
|
+
'${rootRouteId}': typeof rootRoute,
|
|
707
781
|
${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => {
|
|
708
782
|
return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`
|
|
709
783
|
})}
|
|
@@ -713,7 +787,7 @@ export async function generator(config: Config, root: string) {
|
|
|
713
787
|
fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'}
|
|
714
788
|
fileRoutesByTo: FileRoutesByTo
|
|
715
789
|
to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'}
|
|
716
|
-
id: ${[`'
|
|
790
|
+
id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
|
|
717
791
|
fileRoutesById: FileRoutesById
|
|
718
792
|
}`,
|
|
719
793
|
`export interface RootRouteChildren {
|
|
@@ -731,7 +805,7 @@ export async function generator(config: Config, root: string) {
|
|
|
731
805
|
|
|
732
806
|
const createRouteManifest = () => {
|
|
733
807
|
const routesManifest = {
|
|
734
|
-
|
|
808
|
+
[rootRouteId]: {
|
|
735
809
|
filePath: rootRouteNode.filePath,
|
|
736
810
|
children: routeTree.map((d) => d.routePath),
|
|
737
811
|
},
|
|
@@ -827,6 +901,22 @@ function removeGroups(s: string) {
|
|
|
827
901
|
return s.replace(possiblyNestedRouteGroupPatternRegex, '')
|
|
828
902
|
}
|
|
829
903
|
|
|
904
|
+
/**
|
|
905
|
+
* Checks if a given RouteNode is valid for augmenting it with typing based on conditions.
|
|
906
|
+
* Also asserts that the RouteNode is defined.
|
|
907
|
+
*
|
|
908
|
+
* @param routeNode - The RouteNode to check.
|
|
909
|
+
* @returns A boolean indicating whether the RouteNode is defined.
|
|
910
|
+
*/
|
|
911
|
+
function isRouteNodeValidForAugmentation(
|
|
912
|
+
routeNode?: RouteNode,
|
|
913
|
+
): routeNode is RouteNode {
|
|
914
|
+
if (!routeNode || routeNode.isVirtual) {
|
|
915
|
+
return false
|
|
916
|
+
}
|
|
917
|
+
return true
|
|
918
|
+
}
|
|
919
|
+
|
|
830
920
|
/**
|
|
831
921
|
* The `node.path` is used as the `id` in the route definition.
|
|
832
922
|
* This function checks if the given node has a parent and if so, it determines the correct path for the given node.
|
|
@@ -847,7 +937,7 @@ function determineNodePath(node: RouteNode) {
|
|
|
847
937
|
* @example
|
|
848
938
|
* removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
|
|
849
939
|
*/
|
|
850
|
-
|
|
940
|
+
function removeLastSegmentFromPath(routePath: string = '/'): string {
|
|
851
941
|
const segments = routePath.split('/')
|
|
852
942
|
segments.pop() // Remove the last segment
|
|
853
943
|
return segments.join('/')
|
|
@@ -867,7 +957,7 @@ function removeLayoutSegments(routePath: string = '/'): string {
|
|
|
867
957
|
return newSegments.join('/')
|
|
868
958
|
}
|
|
869
959
|
|
|
870
|
-
|
|
960
|
+
function hasParentRoute(
|
|
871
961
|
routes: Array<RouteNode>,
|
|
872
962
|
node: RouteNode,
|
|
873
963
|
routePathToCheck: string | undefined,
|
|
@@ -902,9 +992,7 @@ export function hasParentRoute(
|
|
|
902
992
|
/**
|
|
903
993
|
* Gets the final variable name for a route
|
|
904
994
|
*/
|
|
905
|
-
|
|
906
|
-
routeNode: RouteNode,
|
|
907
|
-
): string => {
|
|
995
|
+
const getResolvedRouteNodeVariableName = (routeNode: RouteNode): string => {
|
|
908
996
|
return routeNode.children?.length
|
|
909
997
|
? `${routeNode.variableName}RouteWithChildren`
|
|
910
998
|
: `${routeNode.variableName}Route`
|
|
@@ -913,7 +1001,7 @@ export const getResolvedRouteNodeVariableName = (
|
|
|
913
1001
|
/**
|
|
914
1002
|
* Creates a map from fullPath to routeNode
|
|
915
1003
|
*/
|
|
916
|
-
|
|
1004
|
+
const createRouteNodesByFullPath = (
|
|
917
1005
|
routeNodes: Array<RouteNode>,
|
|
918
1006
|
): Map<string, RouteNode> => {
|
|
919
1007
|
return new Map(
|
|
@@ -924,7 +1012,7 @@ export const createRouteNodesByFullPath = (
|
|
|
924
1012
|
/**
|
|
925
1013
|
* Create a map from 'to' to a routeNode
|
|
926
1014
|
*/
|
|
927
|
-
|
|
1015
|
+
const createRouteNodesByTo = (
|
|
928
1016
|
routeNodes: Array<RouteNode>,
|
|
929
1017
|
): Map<string, RouteNode> => {
|
|
930
1018
|
return new Map(
|
|
@@ -938,7 +1026,7 @@ export const createRouteNodesByTo = (
|
|
|
938
1026
|
/**
|
|
939
1027
|
* Create a map from 'id' to a routeNode
|
|
940
1028
|
*/
|
|
941
|
-
|
|
1029
|
+
const createRouteNodesById = (
|
|
942
1030
|
routeNodes: Array<RouteNode>,
|
|
943
1031
|
): Map<string, RouteNode> => {
|
|
944
1032
|
return new Map(
|
|
@@ -952,7 +1040,7 @@ export const createRouteNodesById = (
|
|
|
952
1040
|
/**
|
|
953
1041
|
* Infers the full path for use by TS
|
|
954
1042
|
*/
|
|
955
|
-
|
|
1043
|
+
const inferFullPath = (routeNode: RouteNode): string => {
|
|
956
1044
|
const fullPath = removeGroups(
|
|
957
1045
|
removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
|
|
958
1046
|
)
|
|
@@ -963,7 +1051,7 @@ export const inferFullPath = (routeNode: RouteNode): string => {
|
|
|
963
1051
|
/**
|
|
964
1052
|
* Infers the path for use by TS
|
|
965
1053
|
*/
|
|
966
|
-
|
|
1054
|
+
const inferPath = (routeNode: RouteNode): string => {
|
|
967
1055
|
return routeNode.cleanedPath === '/'
|
|
968
1056
|
? routeNode.cleanedPath
|
|
969
1057
|
: (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
|
|
@@ -972,7 +1060,7 @@ export const inferPath = (routeNode: RouteNode): string => {
|
|
|
972
1060
|
/**
|
|
973
1061
|
* Infers to path
|
|
974
1062
|
*/
|
|
975
|
-
|
|
1063
|
+
const inferTo = (routeNode: RouteNode): string => {
|
|
976
1064
|
const fullPath = inferFullPath(routeNode)
|
|
977
1065
|
|
|
978
1066
|
if (fullPath === '/') return fullPath
|
|
@@ -983,7 +1071,7 @@ export const inferTo = (routeNode: RouteNode): string => {
|
|
|
983
1071
|
/**
|
|
984
1072
|
* Dedupes branches and index routes
|
|
985
1073
|
*/
|
|
986
|
-
|
|
1074
|
+
const dedupeBranchesAndIndexRoutes = (
|
|
987
1075
|
routes: Array<RouteNode>,
|
|
988
1076
|
): Array<RouteNode> => {
|
|
989
1077
|
return routes.filter((route) => {
|
|
@@ -1022,75 +1110,8 @@ function checkRouteFullPathUniqueness(
|
|
|
1022
1110
|
const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
|
|
1023
1111
|
.map((p) => `"${p.inferredFullPath}"`)
|
|
1024
1112
|
.join(', ')}.
|
|
1025
|
-
Please ensure each
|
|
1026
|
-
Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
|
|
1027
|
-
throw new Error(errorMessage)
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
function checkStartAPIRoutes(_routes: Array<RouteNode>, config: Config) {
|
|
1032
|
-
if (_routes.length === 0) {
|
|
1033
|
-
return []
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Make sure these are valid URLs
|
|
1037
|
-
// Route Groups and Layout Routes aren't being removed since
|
|
1038
|
-
// you may want to have an API route that starts with an underscore
|
|
1039
|
-
// or be wrapped in parentheses
|
|
1040
|
-
const routes = _routes.map((d) => {
|
|
1041
|
-
const routePath = removeTrailingSlash(d.routePath ?? '')
|
|
1042
|
-
return { ...d, routePath }
|
|
1043
|
-
})
|
|
1044
|
-
|
|
1045
|
-
const conflictingFiles = checkUnique(routes, 'routePath')
|
|
1046
|
-
|
|
1047
|
-
if (conflictingFiles !== undefined) {
|
|
1048
|
-
const errorMessage = `Conflicting configuration paths were found for the following API route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
|
|
1049
|
-
.map((p) => `"${p}"`)
|
|
1050
|
-
.join(', ')}.
|
|
1051
|
-
Please ensure each API route has a unique route path.
|
|
1113
|
+
Please ensure each Route has a unique full path.
|
|
1052
1114
|
Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
|
|
1053
1115
|
throw new Error(errorMessage)
|
|
1054
1116
|
}
|
|
1055
|
-
|
|
1056
|
-
return routes
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
export type StartAPIRoutePathSegment = {
|
|
1060
|
-
value: string
|
|
1061
|
-
type: 'path' | 'param' | 'splat'
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* This function takes in a path in the format accepted by TanStack Router
|
|
1066
|
-
* and returns an array of path segments that can be used to generate
|
|
1067
|
-
* the pathname of the TanStack Start API route.
|
|
1068
|
-
*
|
|
1069
|
-
* @param src
|
|
1070
|
-
* @returns
|
|
1071
|
-
*/
|
|
1072
|
-
export function startAPIRouteSegmentsFromTSRFilePath(
|
|
1073
|
-
src: string,
|
|
1074
|
-
config: Config,
|
|
1075
|
-
): Array<StartAPIRoutePathSegment> {
|
|
1076
|
-
const routePath = determineInitialRoutePath(src)
|
|
1077
|
-
|
|
1078
|
-
const parts = routePath
|
|
1079
|
-
.replaceAll('.', '/')
|
|
1080
|
-
.split('/')
|
|
1081
|
-
.filter((p) => !!p && p !== config.indexToken)
|
|
1082
|
-
const segments: Array<StartAPIRoutePathSegment> = parts.map((part) => {
|
|
1083
|
-
if (part.startsWith('$')) {
|
|
1084
|
-
if (part === '$') {
|
|
1085
|
-
return { value: part, type: 'splat' }
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
part.replaceAll('$', '')
|
|
1089
|
-
return { value: part, type: 'param' }
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
return { value: part, type: 'path' }
|
|
1093
|
-
})
|
|
1094
|
-
|
|
1095
|
-
return segments
|
|
1096
1117
|
}
|