@tanstack/router-generator 1.120.4-alpha.4 → 1.120.5
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 +31 -68
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs +5 -1
- 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 +172 -160
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/generator.d.cts +59 -0
- package/dist/cjs/index.cjs +2 -23
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +4 -8
- package/dist/cjs/template.cjs +12 -4
- package/dist/cjs/template.cjs.map +1 -1
- package/dist/cjs/template.d.cts +1 -0
- package/dist/cjs/types.d.cts +1 -1
- package/dist/cjs/utils.cjs +2 -61
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -4
- package/dist/esm/config.d.ts +31 -68
- 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 +6 -2
- 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 +59 -0
- package/dist/esm/generator.js +175 -163
- package/dist/esm/generator.js.map +1 -1
- package/dist/esm/index.d.ts +4 -8
- package/dist/esm/index.js +4 -25
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/template.d.ts +1 -0
- package/dist/esm/template.js +12 -4
- package/dist/esm/template.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/utils.d.ts +2 -4
- package/dist/esm/utils.js +2 -61
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/config.ts +11 -14
- package/src/filesystem/physical/getRouteNodes.ts +14 -13
- package/src/filesystem/virtual/getRouteNodes.ts +3 -18
- package/src/generator.ts +221 -242
- package/src/index.ts +7 -32
- package/src/template.ts +15 -4
- package/src/types.ts +1 -0
- package/src/utils.ts +4 -85
package/src/generator.ts
CHANGED
|
@@ -2,10 +2,12 @@ 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,
|
|
5
6
|
format,
|
|
6
7
|
logging,
|
|
7
8
|
multiSortBy,
|
|
8
9
|
removeExt,
|
|
10
|
+
removeTrailingSlash,
|
|
9
11
|
removeUnderscores,
|
|
10
12
|
replaceBackslash,
|
|
11
13
|
resetRegex,
|
|
@@ -16,12 +18,18 @@ import {
|
|
|
16
18
|
import { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes'
|
|
17
19
|
import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
|
|
18
20
|
import { rootPathId } from './filesystem/physical/rootPathId'
|
|
19
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
defaultAPIRouteTemplate,
|
|
23
|
+
fillTemplate,
|
|
24
|
+
getTargetTemplate,
|
|
25
|
+
} from './template'
|
|
20
26
|
import type { FsRouteType, GetRouteNodesResult, RouteNode } from './types'
|
|
21
27
|
import type { Config } from './config'
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
export const CONSTANTS = {
|
|
30
|
+
// When changing this, you'll want to update the import in `start-api-routes/src/index.ts#defaultAPIFileRouteHandler`
|
|
31
|
+
APIRouteExportVariable: 'APIRoute',
|
|
32
|
+
}
|
|
25
33
|
|
|
26
34
|
let latestTask = 0
|
|
27
35
|
const routeGroupPatternRegex = /\(.+\)/g
|
|
@@ -68,6 +76,10 @@ export async function generator(config: Config, root: string) {
|
|
|
68
76
|
|
|
69
77
|
const TYPES_DISABLED = config.disableTypes
|
|
70
78
|
|
|
79
|
+
// Controls whether API Routes are generated for TanStack Start
|
|
80
|
+
const ENABLED_API_ROUTES_GENERATION =
|
|
81
|
+
config.__enableAPIRoutesGeneration ?? false
|
|
82
|
+
|
|
71
83
|
let getRouteNodesResult: GetRouteNodesResult
|
|
72
84
|
|
|
73
85
|
if (config.virtualRouteConfig) {
|
|
@@ -105,6 +117,30 @@ export async function generator(config: Config, root: string) {
|
|
|
105
117
|
const routeTree: Array<RouteNode> = []
|
|
106
118
|
const routePiecesByPath: Record<string, RouteSubNode> = {}
|
|
107
119
|
|
|
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
|
+
|
|
108
144
|
// Loop over the flat list of routeNodes and
|
|
109
145
|
// build up a tree based on the routeNodes' routePath
|
|
110
146
|
const routeNodes: Array<RouteNode> = []
|
|
@@ -207,7 +243,7 @@ export async function generator(config: Config, root: string) {
|
|
|
207
243
|
tLazyRouteTemplate.template(),
|
|
208
244
|
{
|
|
209
245
|
tsrImports: tLazyRouteTemplate.imports.tsrImports(),
|
|
210
|
-
tsrPath: escapedRoutePath
|
|
246
|
+
tsrPath: escapedRoutePath,
|
|
211
247
|
tsrExportStart:
|
|
212
248
|
tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
213
249
|
tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd(),
|
|
@@ -233,104 +269,20 @@ export async function generator(config: Config, root: string) {
|
|
|
233
269
|
tRouteTemplate.template(),
|
|
234
270
|
{
|
|
235
271
|
tsrImports: tRouteTemplate.imports.tsrImports(),
|
|
236
|
-
tsrPath: escapedRoutePath
|
|
272
|
+
tsrPath: escapedRoutePath,
|
|
237
273
|
tsrExportStart:
|
|
238
274
|
tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
|
|
239
275
|
tsrExportEnd: tRouteTemplate.imports.tsrExportEnd(),
|
|
240
276
|
},
|
|
241
277
|
)
|
|
242
278
|
}
|
|
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
|
-
)
|
|
312
279
|
} 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
|
-
|
|
322
280
|
// Update the existing route file
|
|
323
281
|
replaced = routeCode
|
|
324
|
-
// fix wrong ids
|
|
325
282
|
.replace(
|
|
326
283
|
/(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
|
|
327
284
|
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
|
|
328
285
|
)
|
|
329
|
-
// fix missing ids
|
|
330
|
-
.replace(
|
|
331
|
-
/((FileRoute)(\s*)(\({))/g,
|
|
332
|
-
(_, __, p2, p3, p4) => `${p2}('${escapedRoutePath}')${p3}${p4}`,
|
|
333
|
-
)
|
|
334
286
|
.replace(
|
|
335
287
|
new RegExp(
|
|
336
288
|
`(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
|
|
@@ -344,18 +296,6 @@ export async function generator(config: Config, root: string) {
|
|
|
344
296
|
(_, __, p2, ___, p4) =>
|
|
345
297
|
`${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`,
|
|
346
298
|
)
|
|
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
|
-
}
|
|
359
299
|
}
|
|
360
300
|
|
|
361
301
|
await writeIfDifferent(node.fullPath, routeCode, replaced, {
|
|
@@ -466,22 +406,77 @@ export async function generator(config: Config, root: string) {
|
|
|
466
406
|
routeNodes.push(node)
|
|
467
407
|
}
|
|
468
408
|
|
|
469
|
-
for (const node of
|
|
409
|
+
for (const node of onlyGeneratorRouteNodes) {
|
|
470
410
|
await handleNode(node)
|
|
471
411
|
}
|
|
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`)
|
|
478
412
|
checkRouteFullPathUniqueness(
|
|
479
413
|
preRouteNodes.filter(
|
|
480
|
-
(d) =>
|
|
414
|
+
(d) =>
|
|
415
|
+
d.children === undefined &&
|
|
416
|
+
(['api', 'lazy'] satisfies Array<FsRouteType>).every(
|
|
417
|
+
(type) => type !== d._fsRouteType,
|
|
418
|
+
),
|
|
481
419
|
),
|
|
482
420
|
config,
|
|
483
421
|
)
|
|
484
422
|
|
|
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
|
+
|
|
485
480
|
function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
|
|
486
481
|
const children = nodes.map((node) => {
|
|
487
482
|
if (node._fsRouteType === '__root') {
|
|
@@ -532,30 +527,6 @@ export async function generator(config: Config, root: string) {
|
|
|
532
527
|
(d) => d,
|
|
533
528
|
])
|
|
534
529
|
|
|
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
|
-
|
|
559
530
|
const imports = Object.entries({
|
|
560
531
|
createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
|
|
561
532
|
lazyFn: sortedRouteNodes.some(
|
|
@@ -584,22 +555,14 @@ export async function generator(config: Config, root: string) {
|
|
|
584
555
|
),
|
|
585
556
|
)
|
|
586
557
|
}
|
|
587
|
-
|
|
588
558
|
const routeImports = [
|
|
589
559
|
...config.routeTreeFileHeader,
|
|
590
560
|
`// This file was automatically generated by TanStack Router.
|
|
591
561
|
// You should NOT make any changes in this file as it will be overwritten.
|
|
592
562
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
|
|
593
|
-
|
|
594
|
-
imports.
|
|
595
|
-
|
|
596
|
-
: '',
|
|
597
|
-
!TYPES_DISABLED && typeImports.length
|
|
598
|
-
? `import type { ${typeImports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
|
|
599
|
-
: '',
|
|
600
|
-
]
|
|
601
|
-
.filter(Boolean)
|
|
602
|
-
.join('\n'),
|
|
563
|
+
imports.length
|
|
564
|
+
? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'\n`
|
|
565
|
+
: '',
|
|
603
566
|
'// Import Routes',
|
|
604
567
|
[
|
|
605
568
|
`import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
|
|
@@ -608,7 +571,7 @@ export async function generator(config: Config, root: string) {
|
|
|
608
571
|
.map((node) => {
|
|
609
572
|
return `import { Route as ${
|
|
610
573
|
node.variableName
|
|
611
|
-
}
|
|
574
|
+
}Import } from './${getImportPath(node)}'`
|
|
612
575
|
}),
|
|
613
576
|
].join('\n'),
|
|
614
577
|
virtualRouteNodes.length ? '// Create Virtual Routes' : '',
|
|
@@ -616,7 +579,7 @@ export async function generator(config: Config, root: string) {
|
|
|
616
579
|
.map((node) => {
|
|
617
580
|
return `const ${
|
|
618
581
|
node.variableName
|
|
619
|
-
}
|
|
582
|
+
}Import = createFileRoute('${node.routePath}')()`
|
|
620
583
|
})
|
|
621
584
|
.join('\n'),
|
|
622
585
|
'// Create/Update Routes',
|
|
@@ -631,8 +594,7 @@ export async function generator(config: Config, root: string) {
|
|
|
631
594
|
const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
|
|
632
595
|
|
|
633
596
|
return [
|
|
634
|
-
|
|
635
|
-
`const ${node.variableName}Route = ${node.variableName}RouteImport.update({
|
|
597
|
+
`const ${node.variableName}Route = ${node.variableName}Import.update({
|
|
636
598
|
${[
|
|
637
599
|
`id: '${node.path}'`,
|
|
638
600
|
!node.isNonPath ? `path: '${node.cleanedPath}'` : undefined,
|
|
@@ -641,19 +603,19 @@ export async function generator(config: Config, root: string) {
|
|
|
641
603
|
.filter(Boolean)
|
|
642
604
|
.join(',')}
|
|
643
605
|
}${TYPES_DISABLED ? '' : 'as any'})`,
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
),
|
|
651
|
-
config.addExtensions,
|
|
606
|
+
loaderNode
|
|
607
|
+
? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
|
|
608
|
+
removeExt(
|
|
609
|
+
path.relative(
|
|
610
|
+
path.dirname(config.generatedRouteTree),
|
|
611
|
+
path.resolve(config.routesDirectory, loaderNode.filePath),
|
|
652
612
|
),
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
613
|
+
config.addExtensions,
|
|
614
|
+
),
|
|
615
|
+
)}'), 'loader') })`
|
|
616
|
+
: '',
|
|
617
|
+
componentNode || errorComponentNode || pendingComponentNode
|
|
618
|
+
? `.update({
|
|
657
619
|
${(
|
|
658
620
|
[
|
|
659
621
|
['component', componentNode],
|
|
@@ -677,23 +639,22 @@ export async function generator(config: Config, root: string) {
|
|
|
677
639
|
})
|
|
678
640
|
.join('\n,')}
|
|
679
641
|
})`
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
),
|
|
642
|
+
: '',
|
|
643
|
+
lazyComponentNode
|
|
644
|
+
? `.lazy(() => import('./${replaceBackslash(
|
|
645
|
+
removeExt(
|
|
646
|
+
path.relative(
|
|
647
|
+
path.dirname(config.generatedRouteTree),
|
|
648
|
+
path.resolve(
|
|
649
|
+
config.routesDirectory,
|
|
650
|
+
lazyComponentNode.filePath,
|
|
690
651
|
),
|
|
691
|
-
config.addExtensions,
|
|
692
652
|
),
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
653
|
+
config.addExtensions,
|
|
654
|
+
),
|
|
655
|
+
)}').then((d) => d.Route))`
|
|
656
|
+
: '',
|
|
657
|
+
].join('')
|
|
697
658
|
})
|
|
698
659
|
.join('\n\n'),
|
|
699
660
|
...(TYPES_DISABLED
|
|
@@ -710,12 +671,12 @@ export async function generator(config: Config, root: string) {
|
|
|
710
671
|
id: '${filePathId}'
|
|
711
672
|
path: '${inferPath(routeNode)}'
|
|
712
673
|
fullPath: '${inferFullPath(routeNode)}'
|
|
713
|
-
preLoaderRoute: typeof ${routeNode.variableName}
|
|
674
|
+
preLoaderRoute: typeof ${routeNode.variableName}Import
|
|
714
675
|
parentRoute: typeof ${
|
|
715
676
|
routeNode.isVirtualParentRequired
|
|
716
677
|
? `${routeNode.parent?.variableName}Route`
|
|
717
678
|
: routeNode.parent?.variableName
|
|
718
|
-
? `${routeNode.parent.variableName}
|
|
679
|
+
? `${routeNode.parent.variableName}Import`
|
|
719
680
|
: 'rootRoute'
|
|
720
681
|
}
|
|
721
682
|
}`
|
|
@@ -724,41 +685,6 @@ export async function generator(config: Config, root: string) {
|
|
|
724
685
|
}
|
|
725
686
|
}`,
|
|
726
687
|
]),
|
|
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
|
-
]),
|
|
762
688
|
'// Create and export the route tree',
|
|
763
689
|
routeConfigChildrenText,
|
|
764
690
|
...(TYPES_DISABLED
|
|
@@ -777,7 +703,7 @@ export async function generator(config: Config, root: string) {
|
|
|
777
703
|
})}
|
|
778
704
|
}`,
|
|
779
705
|
`export interface FileRoutesById {
|
|
780
|
-
'
|
|
706
|
+
'__root__': typeof rootRoute,
|
|
781
707
|
${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => {
|
|
782
708
|
return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`
|
|
783
709
|
})}
|
|
@@ -787,7 +713,7 @@ export async function generator(config: Config, root: string) {
|
|
|
787
713
|
fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'}
|
|
788
714
|
fileRoutesByTo: FileRoutesByTo
|
|
789
715
|
to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'}
|
|
790
|
-
id: ${[`'
|
|
716
|
+
id: ${[`'__root__'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
|
|
791
717
|
fileRoutesById: FileRoutesById
|
|
792
718
|
}`,
|
|
793
719
|
`export interface RootRouteChildren {
|
|
@@ -805,7 +731,7 @@ export async function generator(config: Config, root: string) {
|
|
|
805
731
|
|
|
806
732
|
const createRouteManifest = () => {
|
|
807
733
|
const routesManifest = {
|
|
808
|
-
|
|
734
|
+
__root__: {
|
|
809
735
|
filePath: rootRouteNode.filePath,
|
|
810
736
|
children: routeTree.map((d) => d.routePath),
|
|
811
737
|
},
|
|
@@ -901,22 +827,6 @@ function removeGroups(s: string) {
|
|
|
901
827
|
return s.replace(possiblyNestedRouteGroupPatternRegex, '')
|
|
902
828
|
}
|
|
903
829
|
|
|
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
|
-
|
|
920
830
|
/**
|
|
921
831
|
* The `node.path` is used as the `id` in the route definition.
|
|
922
832
|
* This function checks if the given node has a parent and if so, it determines the correct path for the given node.
|
|
@@ -937,7 +847,7 @@ function determineNodePath(node: RouteNode) {
|
|
|
937
847
|
* @example
|
|
938
848
|
* removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
|
|
939
849
|
*/
|
|
940
|
-
function removeLastSegmentFromPath(routePath: string = '/'): string {
|
|
850
|
+
export function removeLastSegmentFromPath(routePath: string = '/'): string {
|
|
941
851
|
const segments = routePath.split('/')
|
|
942
852
|
segments.pop() // Remove the last segment
|
|
943
853
|
return segments.join('/')
|
|
@@ -957,7 +867,7 @@ function removeLayoutSegments(routePath: string = '/'): string {
|
|
|
957
867
|
return newSegments.join('/')
|
|
958
868
|
}
|
|
959
869
|
|
|
960
|
-
function hasParentRoute(
|
|
870
|
+
export function hasParentRoute(
|
|
961
871
|
routes: Array<RouteNode>,
|
|
962
872
|
node: RouteNode,
|
|
963
873
|
routePathToCheck: string | undefined,
|
|
@@ -992,7 +902,9 @@ function hasParentRoute(
|
|
|
992
902
|
/**
|
|
993
903
|
* Gets the final variable name for a route
|
|
994
904
|
*/
|
|
995
|
-
const getResolvedRouteNodeVariableName = (
|
|
905
|
+
export const getResolvedRouteNodeVariableName = (
|
|
906
|
+
routeNode: RouteNode,
|
|
907
|
+
): string => {
|
|
996
908
|
return routeNode.children?.length
|
|
997
909
|
? `${routeNode.variableName}RouteWithChildren`
|
|
998
910
|
: `${routeNode.variableName}Route`
|
|
@@ -1001,7 +913,7 @@ const getResolvedRouteNodeVariableName = (routeNode: RouteNode): string => {
|
|
|
1001
913
|
/**
|
|
1002
914
|
* Creates a map from fullPath to routeNode
|
|
1003
915
|
*/
|
|
1004
|
-
const createRouteNodesByFullPath = (
|
|
916
|
+
export const createRouteNodesByFullPath = (
|
|
1005
917
|
routeNodes: Array<RouteNode>,
|
|
1006
918
|
): Map<string, RouteNode> => {
|
|
1007
919
|
return new Map(
|
|
@@ -1012,7 +924,7 @@ const createRouteNodesByFullPath = (
|
|
|
1012
924
|
/**
|
|
1013
925
|
* Create a map from 'to' to a routeNode
|
|
1014
926
|
*/
|
|
1015
|
-
const createRouteNodesByTo = (
|
|
927
|
+
export const createRouteNodesByTo = (
|
|
1016
928
|
routeNodes: Array<RouteNode>,
|
|
1017
929
|
): Map<string, RouteNode> => {
|
|
1018
930
|
return new Map(
|
|
@@ -1026,7 +938,7 @@ const createRouteNodesByTo = (
|
|
|
1026
938
|
/**
|
|
1027
939
|
* Create a map from 'id' to a routeNode
|
|
1028
940
|
*/
|
|
1029
|
-
const createRouteNodesById = (
|
|
941
|
+
export const createRouteNodesById = (
|
|
1030
942
|
routeNodes: Array<RouteNode>,
|
|
1031
943
|
): Map<string, RouteNode> => {
|
|
1032
944
|
return new Map(
|
|
@@ -1040,7 +952,7 @@ const createRouteNodesById = (
|
|
|
1040
952
|
/**
|
|
1041
953
|
* Infers the full path for use by TS
|
|
1042
954
|
*/
|
|
1043
|
-
const inferFullPath = (routeNode: RouteNode): string => {
|
|
955
|
+
export const inferFullPath = (routeNode: RouteNode): string => {
|
|
1044
956
|
const fullPath = removeGroups(
|
|
1045
957
|
removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
|
|
1046
958
|
)
|
|
@@ -1051,7 +963,7 @@ const inferFullPath = (routeNode: RouteNode): string => {
|
|
|
1051
963
|
/**
|
|
1052
964
|
* Infers the path for use by TS
|
|
1053
965
|
*/
|
|
1054
|
-
const inferPath = (routeNode: RouteNode): string => {
|
|
966
|
+
export const inferPath = (routeNode: RouteNode): string => {
|
|
1055
967
|
return routeNode.cleanedPath === '/'
|
|
1056
968
|
? routeNode.cleanedPath
|
|
1057
969
|
: (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
|
|
@@ -1060,7 +972,7 @@ const inferPath = (routeNode: RouteNode): string => {
|
|
|
1060
972
|
/**
|
|
1061
973
|
* Infers to path
|
|
1062
974
|
*/
|
|
1063
|
-
const inferTo = (routeNode: RouteNode): string => {
|
|
975
|
+
export const inferTo = (routeNode: RouteNode): string => {
|
|
1064
976
|
const fullPath = inferFullPath(routeNode)
|
|
1065
977
|
|
|
1066
978
|
if (fullPath === '/') return fullPath
|
|
@@ -1071,7 +983,7 @@ const inferTo = (routeNode: RouteNode): string => {
|
|
|
1071
983
|
/**
|
|
1072
984
|
* Dedupes branches and index routes
|
|
1073
985
|
*/
|
|
1074
|
-
const dedupeBranchesAndIndexRoutes = (
|
|
986
|
+
export const dedupeBranchesAndIndexRoutes = (
|
|
1075
987
|
routes: Array<RouteNode>,
|
|
1076
988
|
): Array<RouteNode> => {
|
|
1077
989
|
return routes.filter((route) => {
|
|
@@ -1110,8 +1022,75 @@ function checkRouteFullPathUniqueness(
|
|
|
1110
1022
|
const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
|
|
1111
1023
|
.map((p) => `"${p.inferredFullPath}"`)
|
|
1112
1024
|
.join(', ')}.
|
|
1113
|
-
Please ensure each
|
|
1025
|
+
Please ensure each route has a unique full path.
|
|
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.
|
|
1114
1052
|
Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
|
|
1115
1053
|
throw new Error(errorMessage)
|
|
1116
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
|
|
1117
1096
|
}
|