@tanstack/router-generator 1.120.3 → 1.120.4-alpha.1
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 +157 -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 +61 -2
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +4 -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 +160 -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 +4 -2
- package/dist/esm/utils.js +61 -2
- 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 +233 -222
- package/src/index.ts +32 -7
- package/src/template.ts +4 -15
- package/src/types.ts +0 -1
- package/src/utils.ts +85 -4
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,94 @@ 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
|
}
|
|
279
|
-
} else {
|
|
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
|
+
|
|
280
253
|
// Update the existing route file
|
|
281
254
|
replaced = routeCode
|
|
282
255
|
.replace(
|
|
283
256
|
/(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
284
257
|
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
285
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
|
+
)
|
|
312
|
+
} else {
|
|
313
|
+
replaced = routeCode
|
|
314
|
+
// fix wrong ids
|
|
315
|
+
.replace(
|
|
316
|
+
/(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
317
|
+
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
318
|
+
)
|
|
319
|
+
// fix missing ids
|
|
320
|
+
.replace(
|
|
321
|
+
/((FileRoute)(\s*)(\({))/g,
|
|
322
|
+
(_, __, p2, p3, p4) => `${p2}('${escapedRoutePath}')${p3}${p4}`,
|
|
323
|
+
)
|
|
286
324
|
.replace(
|
|
287
325
|
new RegExp(
|
|
288
326
|
`(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
@@ -296,6 +334,18 @@ export async function generator(config: Config, root: string) {
|
|
|
296
334
|
(_, __, p2, ___, p4) =>
|
|
297
335
|
`${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`,
|
|
298
336
|
)
|
|
337
|
+
|
|
338
|
+
// check whether the import statement is already present
|
|
339
|
+
const regex = new RegExp(
|
|
340
|
+
`(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
341
|
+
'gm',
|
|
342
|
+
)
|
|
343
|
+
if (!replaced.match(regex)) {
|
|
344
|
+
replaced = [
|
|
345
|
+
`import { ${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'} } from '@tanstack/${ROUTE_TEMPLATE.subPkg}'`,
|
|
346
|
+
...replaced.split('\n'),
|
|
347
|
+
].join('\n')
|
|
348
|
+
}
|
|
299
349
|
}
|
|
300
350
|
|
|
301
351
|
await writeIfDifferent(node.fullPath, routeCode, replaced, {
|
|
@@ -406,77 +456,22 @@ export async function generator(config: Config, root: string) {
|
|
|
406
456
|
routeNodes.push(node)
|
|
407
457
|
}
|
|
408
458
|
|
|
409
|
-
for (const node of
|
|
459
|
+
for (const node of preRouteNodes) {
|
|
410
460
|
await handleNode(node)
|
|
411
461
|
}
|
|
462
|
+
|
|
463
|
+
// This is run against the `preRouteNodes` array since it
|
|
464
|
+
// has the flattened Route nodes and not the full tree
|
|
465
|
+
// Since TSR allows multiple way of defining a route,
|
|
466
|
+
// we need to ensure that a user hasn't defined the
|
|
467
|
+
// same route in multiple ways (i.e. `flat`, `nested`, `virtual`)
|
|
412
468
|
checkRouteFullPathUniqueness(
|
|
413
469
|
preRouteNodes.filter(
|
|
414
|
-
(d) =>
|
|
415
|
-
d.children === undefined &&
|
|
416
|
-
(['api', 'lazy'] satisfies Array<FsRouteType>).every(
|
|
417
|
-
(type) => type !== d._fsRouteType,
|
|
418
|
-
),
|
|
470
|
+
(d) => d.children === undefined && 'lazy' !== d._fsRouteType,
|
|
419
471
|
),
|
|
420
472
|
config,
|
|
421
473
|
)
|
|
422
474
|
|
|
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
475
|
function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
|
|
481
476
|
const children = nodes.map((node) => {
|
|
482
477
|
if (node._fsRouteType === '__root') {
|
|
@@ -527,6 +522,30 @@ export async function generator(config: Config, root: string) {
|
|
|
527
522
|
(d) => d,
|
|
528
523
|
])
|
|
529
524
|
|
|
525
|
+
const typeImports = Object.entries({
|
|
526
|
+
// Used for augmentation of regular routes
|
|
527
|
+
CreateFileRoute:
|
|
528
|
+
config.verboseFileRoutes === false &&
|
|
529
|
+
sortedRouteNodes.some(
|
|
530
|
+
(d) => isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy',
|
|
531
|
+
),
|
|
532
|
+
// Used for augmentation of lazy (`.lazy`) routes
|
|
533
|
+
CreateLazyFileRoute:
|
|
534
|
+
config.verboseFileRoutes === false &&
|
|
535
|
+
sortedRouteNodes.some(
|
|
536
|
+
(node) =>
|
|
537
|
+
routePiecesByPath[node.routePath!]?.lazy &&
|
|
538
|
+
isRouteNodeValidForAugmentation(node),
|
|
539
|
+
),
|
|
540
|
+
// Used in the process of augmenting the routes
|
|
541
|
+
FileRoutesByPath:
|
|
542
|
+
config.verboseFileRoutes === false &&
|
|
543
|
+
sortedRouteNodes.some((d) => isRouteNodeValidForAugmentation(d)),
|
|
544
|
+
})
|
|
545
|
+
.filter((d) => d[1])
|
|
546
|
+
.map((d) => d[0])
|
|
547
|
+
.sort((a, b) => a.localeCompare(b))
|
|
548
|
+
|
|
530
549
|
const imports = Object.entries({
|
|
531
550
|
createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
|
|
532
551
|
lazyFn: sortedRouteNodes.some(
|
|
@@ -555,14 +574,22 @@ export async function generator(config: Config, root: string) {
|
|
|
555
574
|
),
|
|
556
575
|
)
|
|
557
576
|
}
|
|
577
|
+
|
|
558
578
|
const routeImports = [
|
|
559
579
|
...config.routeTreeFileHeader,
|
|
560
580
|
`// This file was automatically generated by TanStack Router.
|
|
561
581
|
// You should NOT make any changes in this file as it will be overwritten.
|
|
562
582
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
583
|
+
[
|
|
584
|
+
imports.length
|
|
585
|
+
? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
|
|
586
|
+
: '',
|
|
587
|
+
!TYPES_DISABLED && typeImports.length
|
|
588
|
+
? `import type { ${typeImports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
|
|
589
|
+
: '',
|
|
590
|
+
]
|
|
591
|
+
.filter(Boolean)
|
|
592
|
+
.join('\n'),
|
|
566
593
|
'// Import Routes',
|
|
567
594
|
[
|
|
568
595
|
`import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
|
|
@@ -571,7 +598,7 @@ export async function generator(config: Config, root: string) {
|
|
|
571
598
|
.map((node) => {
|
|
572
599
|
return `import { Route as ${
|
|
573
600
|
node.variableName
|
|
574
|
-
}
|
|
601
|
+
}RouteImport } from './${getImportPath(node)}'`
|
|
575
602
|
}),
|
|
576
603
|
].join('\n'),
|
|
577
604
|
virtualRouteNodes.length ? '// Create Virtual Routes' : '',
|
|
@@ -579,7 +606,7 @@ export async function generator(config: Config, root: string) {
|
|
|
579
606
|
.map((node) => {
|
|
580
607
|
return `const ${
|
|
581
608
|
node.variableName
|
|
582
|
-
}
|
|
609
|
+
}RouteImport = createFileRoute('${node.routePath}')()`
|
|
583
610
|
})
|
|
584
611
|
.join('\n'),
|
|
585
612
|
'// Create/Update Routes',
|
|
@@ -594,7 +621,8 @@ export async function generator(config: Config, root: string) {
|
|
|
594
621
|
const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
|
|
595
622
|
|
|
596
623
|
return [
|
|
597
|
-
|
|
624
|
+
[
|
|
625
|
+
`const ${node.variableName}Route = ${node.variableName}RouteImport.update({
|
|
598
626
|
${[
|
|
599
627
|
`id: '${node.path}'`,
|
|
600
628
|
!node.isNonPath ? `path: '${node.cleanedPath}'` : undefined,
|
|
@@ -603,19 +631,19 @@ export async function generator(config: Config, root: string) {
|
|
|
603
631
|
.filter(Boolean)
|
|
604
632
|
.join(',')}
|
|
605
633
|
}${TYPES_DISABLED ? '' : 'as any'})`,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
634
|
+
loaderNode
|
|
635
|
+
? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
|
|
636
|
+
removeExt(
|
|
637
|
+
path.relative(
|
|
638
|
+
path.dirname(config.generatedRouteTree),
|
|
639
|
+
path.resolve(config.routesDirectory, loaderNode.filePath),
|
|
640
|
+
),
|
|
641
|
+
config.addExtensions,
|
|
612
642
|
),
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
componentNode || errorComponentNode || pendingComponentNode
|
|
618
|
-
? `.update({
|
|
643
|
+
)}'), 'loader') })`
|
|
644
|
+
: '',
|
|
645
|
+
componentNode || errorComponentNode || pendingComponentNode
|
|
646
|
+
? `.update({
|
|
619
647
|
${(
|
|
620
648
|
[
|
|
621
649
|
['component', componentNode],
|
|
@@ -639,22 +667,23 @@ export async function generator(config: Config, root: string) {
|
|
|
639
667
|
})
|
|
640
668
|
.join('\n,')}
|
|
641
669
|
})`
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
670
|
+
: '',
|
|
671
|
+
lazyComponentNode
|
|
672
|
+
? `.lazy(() => import('./${replaceBackslash(
|
|
673
|
+
removeExt(
|
|
674
|
+
path.relative(
|
|
675
|
+
path.dirname(config.generatedRouteTree),
|
|
676
|
+
path.resolve(
|
|
677
|
+
config.routesDirectory,
|
|
678
|
+
lazyComponentNode.filePath,
|
|
679
|
+
),
|
|
651
680
|
),
|
|
681
|
+
config.addExtensions,
|
|
652
682
|
),
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
].join('')
|
|
683
|
+
)}').then((d) => d.Route))`
|
|
684
|
+
: '',
|
|
685
|
+
].join(''),
|
|
686
|
+
].join('\n\n')
|
|
658
687
|
})
|
|
659
688
|
.join('\n\n'),
|
|
660
689
|
...(TYPES_DISABLED
|
|
@@ -671,12 +700,12 @@ export async function generator(config: Config, root: string) {
|
|
|
671
700
|
id: '${filePathId}'
|
|
672
701
|
path: '${inferPath(routeNode)}'
|
|
673
702
|
fullPath: '${inferFullPath(routeNode)}'
|
|
674
|
-
preLoaderRoute: typeof ${routeNode.variableName}
|
|
703
|
+
preLoaderRoute: typeof ${routeNode.variableName}RouteImport
|
|
675
704
|
parentRoute: typeof ${
|
|
676
705
|
routeNode.isVirtualParentRequired
|
|
677
706
|
? `${routeNode.parent?.variableName}Route`
|
|
678
707
|
: routeNode.parent?.variableName
|
|
679
|
-
? `${routeNode.parent.variableName}
|
|
708
|
+
? `${routeNode.parent.variableName}RouteImport`
|
|
680
709
|
: 'rootRoute'
|
|
681
710
|
}
|
|
682
711
|
}`
|
|
@@ -685,6 +714,41 @@ export async function generator(config: Config, root: string) {
|
|
|
685
714
|
}
|
|
686
715
|
}`,
|
|
687
716
|
]),
|
|
717
|
+
...(TYPES_DISABLED
|
|
718
|
+
? []
|
|
719
|
+
: config.verboseFileRoutes !== false
|
|
720
|
+
? []
|
|
721
|
+
: [
|
|
722
|
+
`// Add type-safety to the createFileRoute function across the route tree`,
|
|
723
|
+
routeNodes
|
|
724
|
+
.map((routeNode) => {
|
|
725
|
+
function getModuleDeclaration(routeNode?: RouteNode) {
|
|
726
|
+
if (!isRouteNodeValidForAugmentation(routeNode)) {
|
|
727
|
+
return ''
|
|
728
|
+
}
|
|
729
|
+
return `declare module './${getImportPath(routeNode)}' {
|
|
730
|
+
const ${routeNode._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}: ${
|
|
731
|
+
routeNode._fsRouteType === 'lazy'
|
|
732
|
+
? `CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>}`
|
|
733
|
+
: `CreateFileRoute<
|
|
734
|
+
'${routeNode.routePath}',
|
|
735
|
+
FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
|
|
736
|
+
FileRoutesByPath['${routeNode.routePath}']['id'],
|
|
737
|
+
FileRoutesByPath['${routeNode.routePath}']['path'],
|
|
738
|
+
FileRoutesByPath['${routeNode.routePath}']['fullPath']
|
|
739
|
+
>
|
|
740
|
+
}`
|
|
741
|
+
}`
|
|
742
|
+
}
|
|
743
|
+
return (
|
|
744
|
+
getModuleDeclaration(routeNode) +
|
|
745
|
+
getModuleDeclaration(
|
|
746
|
+
routePiecesByPath[routeNode.routePath!]?.lazy,
|
|
747
|
+
)
|
|
748
|
+
)
|
|
749
|
+
})
|
|
750
|
+
.join('\n'),
|
|
751
|
+
]),
|
|
688
752
|
'// Create and export the route tree',
|
|
689
753
|
routeConfigChildrenText,
|
|
690
754
|
...(TYPES_DISABLED
|
|
@@ -703,7 +767,7 @@ export async function generator(config: Config, root: string) {
|
|
|
703
767
|
})}
|
|
704
768
|
}`,
|
|
705
769
|
`export interface FileRoutesById {
|
|
706
|
-
'
|
|
770
|
+
'${rootRouteId}': typeof rootRoute,
|
|
707
771
|
${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => {
|
|
708
772
|
return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`
|
|
709
773
|
})}
|
|
@@ -713,7 +777,7 @@ export async function generator(config: Config, root: string) {
|
|
|
713
777
|
fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'}
|
|
714
778
|
fileRoutesByTo: FileRoutesByTo
|
|
715
779
|
to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'}
|
|
716
|
-
id: ${[`'
|
|
780
|
+
id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
|
|
717
781
|
fileRoutesById: FileRoutesById
|
|
718
782
|
}`,
|
|
719
783
|
`export interface RootRouteChildren {
|
|
@@ -731,7 +795,7 @@ export async function generator(config: Config, root: string) {
|
|
|
731
795
|
|
|
732
796
|
const createRouteManifest = () => {
|
|
733
797
|
const routesManifest = {
|
|
734
|
-
|
|
798
|
+
[rootRouteId]: {
|
|
735
799
|
filePath: rootRouteNode.filePath,
|
|
736
800
|
children: routeTree.map((d) => d.routePath),
|
|
737
801
|
},
|
|
@@ -827,6 +891,22 @@ function removeGroups(s: string) {
|
|
|
827
891
|
return s.replace(possiblyNestedRouteGroupPatternRegex, '')
|
|
828
892
|
}
|
|
829
893
|
|
|
894
|
+
/**
|
|
895
|
+
* Checks if a given RouteNode is valid for augmenting it with typing based on conditions.
|
|
896
|
+
* Also asserts that the RouteNode is defined.
|
|
897
|
+
*
|
|
898
|
+
* @param routeNode - The RouteNode to check.
|
|
899
|
+
* @returns A boolean indicating whether the RouteNode is defined.
|
|
900
|
+
*/
|
|
901
|
+
function isRouteNodeValidForAugmentation(
|
|
902
|
+
routeNode?: RouteNode,
|
|
903
|
+
): routeNode is RouteNode {
|
|
904
|
+
if (!routeNode || routeNode.isVirtual) {
|
|
905
|
+
return false
|
|
906
|
+
}
|
|
907
|
+
return true
|
|
908
|
+
}
|
|
909
|
+
|
|
830
910
|
/**
|
|
831
911
|
* The `node.path` is used as the `id` in the route definition.
|
|
832
912
|
* This function checks if the given node has a parent and if so, it determines the correct path for the given node.
|
|
@@ -847,7 +927,7 @@ function determineNodePath(node: RouteNode) {
|
|
|
847
927
|
* @example
|
|
848
928
|
* removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
|
|
849
929
|
*/
|
|
850
|
-
|
|
930
|
+
function removeLastSegmentFromPath(routePath: string = '/'): string {
|
|
851
931
|
const segments = routePath.split('/')
|
|
852
932
|
segments.pop() // Remove the last segment
|
|
853
933
|
return segments.join('/')
|
|
@@ -867,7 +947,7 @@ function removeLayoutSegments(routePath: string = '/'): string {
|
|
|
867
947
|
return newSegments.join('/')
|
|
868
948
|
}
|
|
869
949
|
|
|
870
|
-
|
|
950
|
+
function hasParentRoute(
|
|
871
951
|
routes: Array<RouteNode>,
|
|
872
952
|
node: RouteNode,
|
|
873
953
|
routePathToCheck: string | undefined,
|
|
@@ -902,9 +982,7 @@ export function hasParentRoute(
|
|
|
902
982
|
/**
|
|
903
983
|
* Gets the final variable name for a route
|
|
904
984
|
*/
|
|
905
|
-
|
|
906
|
-
routeNode: RouteNode,
|
|
907
|
-
): string => {
|
|
985
|
+
const getResolvedRouteNodeVariableName = (routeNode: RouteNode): string => {
|
|
908
986
|
return routeNode.children?.length
|
|
909
987
|
? `${routeNode.variableName}RouteWithChildren`
|
|
910
988
|
: `${routeNode.variableName}Route`
|
|
@@ -913,7 +991,7 @@ export const getResolvedRouteNodeVariableName = (
|
|
|
913
991
|
/**
|
|
914
992
|
* Creates a map from fullPath to routeNode
|
|
915
993
|
*/
|
|
916
|
-
|
|
994
|
+
const createRouteNodesByFullPath = (
|
|
917
995
|
routeNodes: Array<RouteNode>,
|
|
918
996
|
): Map<string, RouteNode> => {
|
|
919
997
|
return new Map(
|
|
@@ -924,7 +1002,7 @@ export const createRouteNodesByFullPath = (
|
|
|
924
1002
|
/**
|
|
925
1003
|
* Create a map from 'to' to a routeNode
|
|
926
1004
|
*/
|
|
927
|
-
|
|
1005
|
+
const createRouteNodesByTo = (
|
|
928
1006
|
routeNodes: Array<RouteNode>,
|
|
929
1007
|
): Map<string, RouteNode> => {
|
|
930
1008
|
return new Map(
|
|
@@ -938,7 +1016,7 @@ export const createRouteNodesByTo = (
|
|
|
938
1016
|
/**
|
|
939
1017
|
* Create a map from 'id' to a routeNode
|
|
940
1018
|
*/
|
|
941
|
-
|
|
1019
|
+
const createRouteNodesById = (
|
|
942
1020
|
routeNodes: Array<RouteNode>,
|
|
943
1021
|
): Map<string, RouteNode> => {
|
|
944
1022
|
return new Map(
|
|
@@ -952,7 +1030,7 @@ export const createRouteNodesById = (
|
|
|
952
1030
|
/**
|
|
953
1031
|
* Infers the full path for use by TS
|
|
954
1032
|
*/
|
|
955
|
-
|
|
1033
|
+
const inferFullPath = (routeNode: RouteNode): string => {
|
|
956
1034
|
const fullPath = removeGroups(
|
|
957
1035
|
removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
|
|
958
1036
|
)
|
|
@@ -963,7 +1041,7 @@ export const inferFullPath = (routeNode: RouteNode): string => {
|
|
|
963
1041
|
/**
|
|
964
1042
|
* Infers the path for use by TS
|
|
965
1043
|
*/
|
|
966
|
-
|
|
1044
|
+
const inferPath = (routeNode: RouteNode): string => {
|
|
967
1045
|
return routeNode.cleanedPath === '/'
|
|
968
1046
|
? routeNode.cleanedPath
|
|
969
1047
|
: (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
|
|
@@ -972,7 +1050,7 @@ export const inferPath = (routeNode: RouteNode): string => {
|
|
|
972
1050
|
/**
|
|
973
1051
|
* Infers to path
|
|
974
1052
|
*/
|
|
975
|
-
|
|
1053
|
+
const inferTo = (routeNode: RouteNode): string => {
|
|
976
1054
|
const fullPath = inferFullPath(routeNode)
|
|
977
1055
|
|
|
978
1056
|
if (fullPath === '/') return fullPath
|
|
@@ -983,7 +1061,7 @@ export const inferTo = (routeNode: RouteNode): string => {
|
|
|
983
1061
|
/**
|
|
984
1062
|
* Dedupes branches and index routes
|
|
985
1063
|
*/
|
|
986
|
-
|
|
1064
|
+
const dedupeBranchesAndIndexRoutes = (
|
|
987
1065
|
routes: Array<RouteNode>,
|
|
988
1066
|
): Array<RouteNode> => {
|
|
989
1067
|
return routes.filter((route) => {
|
|
@@ -1022,75 +1100,8 @@ function checkRouteFullPathUniqueness(
|
|
|
1022
1100
|
const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
|
|
1023
1101
|
.map((p) => `"${p.inferredFullPath}"`)
|
|
1024
1102
|
.join(', ')}.
|
|
1025
|
-
Please ensure each
|
|
1103
|
+
Please ensure each Route has a unique full path.
|
|
1026
1104
|
Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
|
|
1027
1105
|
throw new Error(errorMessage)
|
|
1028
1106
|
}
|
|
1029
1107
|
}
|
|
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.
|
|
1052
|
-
Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
|
|
1053
|
-
throw new Error(errorMessage)
|
|
1054
|
-
}
|
|
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
|
-
}
|