@tanstack/router-generator 1.133.20 → 1.133.27
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 +3 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/config.d.cts +11 -6
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs +9 -2
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +1 -1
- package/dist/cjs/generator.cjs +24 -12
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/types.d.cts +2 -0
- package/dist/cjs/utils.cjs +112 -16
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +20 -6
- package/dist/esm/config.d.ts +11 -6
- package/dist/esm/config.js +3 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/filesystem/physical/getRouteNodes.d.ts +1 -1
- package/dist/esm/filesystem/physical/getRouteNodes.js +9 -2
- package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
- package/dist/esm/generator.js +25 -13
- package/dist/esm/generator.js.map +1 -1
- package/dist/esm/types.d.ts +2 -0
- package/dist/esm/utils.d.ts +20 -6
- package/dist/esm/utils.js +112 -16
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/config.ts +2 -0
- package/src/filesystem/physical/getRouteNodes.ts +10 -1
- package/src/generator.ts +32 -9
- package/src/types.ts +2 -0
- package/src/utils.ts +205 -13
package/src/utils.ts
CHANGED
|
@@ -53,8 +53,12 @@ export function removeTrailingSlash(s: string) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const BRACKET_CONTENT_RE = /\[(.*?)\]/g
|
|
56
|
+
const SPLIT_REGEX = /(?<!\[)\.(?!\])/g
|
|
56
57
|
|
|
57
|
-
export function determineInitialRoutePath(
|
|
58
|
+
export function determineInitialRoutePath(
|
|
59
|
+
routePath: string,
|
|
60
|
+
config?: Pick<Config, 'experimental' | 'routeToken' | 'indexToken'>,
|
|
61
|
+
) {
|
|
58
62
|
const DISALLOWED_ESCAPE_CHARS = new Set([
|
|
59
63
|
'/',
|
|
60
64
|
'\\',
|
|
@@ -70,9 +74,39 @@ export function determineInitialRoutePath(routePath: string) {
|
|
|
70
74
|
'%',
|
|
71
75
|
])
|
|
72
76
|
|
|
73
|
-
const
|
|
77
|
+
const originalRoutePath =
|
|
78
|
+
cleanPath(
|
|
79
|
+
`/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,
|
|
80
|
+
) || ''
|
|
81
|
+
|
|
82
|
+
// check if the route path is a valid non-nested path,
|
|
83
|
+
// TODO with new major rename to reflect not experimental anymore
|
|
84
|
+
const isExperimentalNonNestedRoute = isValidNonNestedRoute(
|
|
85
|
+
originalRoutePath,
|
|
86
|
+
config,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
let cleanedRoutePath = routePath
|
|
90
|
+
|
|
91
|
+
// we already identified the path as non-nested and can now remove the trailing underscores
|
|
92
|
+
// we need to do this now before we encounter any escaped trailing underscores
|
|
93
|
+
// this way we can be sure any remaining trailing underscores should remain
|
|
94
|
+
// TODO with new major we can remove check and always remove leading underscores
|
|
95
|
+
if (config?.experimental?.nonNestedRoutes) {
|
|
96
|
+
// we should leave trailing underscores if the route path is the root path
|
|
97
|
+
if (originalRoutePath !== `/${rootPathId}`) {
|
|
98
|
+
// remove trailing underscores on various path segments
|
|
99
|
+
cleanedRoutePath = removeTrailingUnderscores(
|
|
100
|
+
originalRoutePath,
|
|
101
|
+
config.routeToken,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parts = cleanedRoutePath.split(SPLIT_REGEX)
|
|
74
107
|
|
|
75
108
|
// Escape any characters that in square brackets
|
|
109
|
+
// we keep the original path untouched
|
|
76
110
|
const escapedParts = parts.map((part) => {
|
|
77
111
|
// Check if any disallowed characters are used in brackets
|
|
78
112
|
|
|
@@ -102,7 +136,11 @@ export function determineInitialRoutePath(routePath: string) {
|
|
|
102
136
|
|
|
103
137
|
const final = cleanPath(`/${escapedParts.join('/')}`) || ''
|
|
104
138
|
|
|
105
|
-
return
|
|
139
|
+
return {
|
|
140
|
+
routePath: final,
|
|
141
|
+
isExperimentalNonNestedRoute,
|
|
142
|
+
originalRoutePath,
|
|
143
|
+
}
|
|
106
144
|
}
|
|
107
145
|
|
|
108
146
|
export function replaceBackslash(s: string) {
|
|
@@ -155,6 +193,46 @@ export function removeUnderscores(s?: string) {
|
|
|
155
193
|
return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/')
|
|
156
194
|
}
|
|
157
195
|
|
|
196
|
+
function escapeRegExp(s: string): string {
|
|
197
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function removeLeadingUnderscores(s: string, routeToken: string) {
|
|
201
|
+
if (!s) return s
|
|
202
|
+
|
|
203
|
+
const hasLeadingUnderscore = routeToken[0] === '_'
|
|
204
|
+
|
|
205
|
+
const routeTokenToExclude = hasLeadingUnderscore
|
|
206
|
+
? routeToken.slice(1)
|
|
207
|
+
: routeToken
|
|
208
|
+
|
|
209
|
+
const escapedRouteToken = escapeRegExp(routeTokenToExclude)
|
|
210
|
+
|
|
211
|
+
const leadingUnderscoreRegex = hasLeadingUnderscore
|
|
212
|
+
? new RegExp(`(?<=^|\\/)_(?!${escapedRouteToken})`, 'g')
|
|
213
|
+
: new RegExp(`(?<=^|\\/)_`, 'g')
|
|
214
|
+
|
|
215
|
+
return s.replaceAll(leadingUnderscoreRegex, '')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function removeTrailingUnderscores(s: string, routeToken: string) {
|
|
219
|
+
if (!s) return s
|
|
220
|
+
|
|
221
|
+
const hasTrailingUnderscore = routeToken.slice(-1) === '_'
|
|
222
|
+
|
|
223
|
+
const routeTokenToExclude = hasTrailingUnderscore
|
|
224
|
+
? routeToken.slice(0, -1)
|
|
225
|
+
: routeToken
|
|
226
|
+
|
|
227
|
+
const escapedRouteToken = escapeRegExp(routeTokenToExclude)
|
|
228
|
+
|
|
229
|
+
const trailingUnderscoreRegex = hasTrailingUnderscore
|
|
230
|
+
? new RegExp(`(?<!${escapedRouteToken})_(?=\\/|$)`, 'g')
|
|
231
|
+
: new RegExp(`_(?=\\/)|_$`, 'g')
|
|
232
|
+
|
|
233
|
+
return s.replaceAll(trailingUnderscoreRegex, '')
|
|
234
|
+
}
|
|
235
|
+
|
|
158
236
|
export function capitalize(s: string) {
|
|
159
237
|
if (typeof s !== 'string') return ''
|
|
160
238
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
@@ -287,7 +365,22 @@ export function hasParentRoute(
|
|
|
287
365
|
routes: Array<RouteNode>,
|
|
288
366
|
node: RouteNode,
|
|
289
367
|
routePathToCheck: string | undefined,
|
|
368
|
+
originalRoutePathToCheck?: string,
|
|
290
369
|
): RouteNode | null {
|
|
370
|
+
const getNonNestedSegments = (routePath: string) => {
|
|
371
|
+
const regex = /_(?=\/|$)/g
|
|
372
|
+
|
|
373
|
+
return [...routePath.matchAll(regex)]
|
|
374
|
+
.filter((match) => {
|
|
375
|
+
const beforeStr = routePath.substring(0, match.index)
|
|
376
|
+
const openBrackets = (beforeStr.match(/\[/g) || []).length
|
|
377
|
+
const closeBrackets = (beforeStr.match(/\]/g) || []).length
|
|
378
|
+
return openBrackets === closeBrackets
|
|
379
|
+
})
|
|
380
|
+
.map((match) => routePath.substring(0, match.index + 1))
|
|
381
|
+
.reverse()
|
|
382
|
+
}
|
|
383
|
+
|
|
291
384
|
if (!routePathToCheck || routePathToCheck === '/') {
|
|
292
385
|
return null
|
|
293
386
|
}
|
|
@@ -297,7 +390,42 @@ export function hasParentRoute(
|
|
|
297
390
|
(d) => d.variableName,
|
|
298
391
|
]).filter((d) => d.routePath !== `/${rootPathId}`)
|
|
299
392
|
|
|
300
|
-
|
|
393
|
+
const filteredNodes = node._isExperimentalNonNestedRoute
|
|
394
|
+
? []
|
|
395
|
+
: [...sortedNodes]
|
|
396
|
+
|
|
397
|
+
if (node._isExperimentalNonNestedRoute) {
|
|
398
|
+
const nonNestedSegments = getNonNestedSegments(
|
|
399
|
+
originalRoutePathToCheck ?? '',
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
for (const route of sortedNodes) {
|
|
403
|
+
if (route.routePath === '/') continue
|
|
404
|
+
|
|
405
|
+
if (
|
|
406
|
+
routePathToCheck.startsWith(`${route.routePath}/`) &&
|
|
407
|
+
route._isExperimentalNonNestedRoute &&
|
|
408
|
+
route.routePath !== routePathToCheck
|
|
409
|
+
) {
|
|
410
|
+
return route
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (
|
|
414
|
+
nonNestedSegments.find((seg) => seg === `${route.routePath}_`) ||
|
|
415
|
+
!(
|
|
416
|
+
route._fsRouteType === 'pathless_layout' ||
|
|
417
|
+
route._fsRouteType === 'layout' ||
|
|
418
|
+
route._fsRouteType === '__root'
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
filteredNodes.push(route)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
for (const route of filteredNodes) {
|
|
301
429
|
if (route.routePath === '/') continue
|
|
302
430
|
|
|
303
431
|
if (
|
|
@@ -312,7 +440,7 @@ export function hasParentRoute(
|
|
|
312
440
|
segments.pop() // Remove the last segment
|
|
313
441
|
const parentRoutePath = segments.join('/')
|
|
314
442
|
|
|
315
|
-
return hasParentRoute(routes, node, parentRoutePath)
|
|
443
|
+
return hasParentRoute(routes, node, parentRoutePath, originalRoutePathToCheck)
|
|
316
444
|
}
|
|
317
445
|
|
|
318
446
|
/**
|
|
@@ -354,9 +482,16 @@ export const inferPath = (routeNode: RouteNode): string => {
|
|
|
354
482
|
/**
|
|
355
483
|
* Infers the full path for use by TS
|
|
356
484
|
*/
|
|
357
|
-
export const inferFullPath = (
|
|
485
|
+
export const inferFullPath = (
|
|
486
|
+
routeNode: RouteNode,
|
|
487
|
+
config?: Pick<Config, 'experimental' | 'routeToken'>,
|
|
488
|
+
): string => {
|
|
489
|
+
// with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain
|
|
490
|
+
// TODO with new major we can remove check and only remove leading underscores
|
|
358
491
|
const fullPath = removeGroups(
|
|
359
|
-
|
|
492
|
+
(config?.experimental?.nonNestedRoutes
|
|
493
|
+
? removeLayoutSegments(routeNode.routePath)
|
|
494
|
+
: removeUnderscores(removeLayoutSegments(routeNode.routePath))) ?? '',
|
|
360
495
|
)
|
|
361
496
|
|
|
362
497
|
return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '')
|
|
@@ -367,9 +502,13 @@ export const inferFullPath = (routeNode: RouteNode): string => {
|
|
|
367
502
|
*/
|
|
368
503
|
export const createRouteNodesByFullPath = (
|
|
369
504
|
routeNodes: Array<RouteNode>,
|
|
505
|
+
config?: Pick<Config, 'experimental' | 'routeToken'>,
|
|
370
506
|
): Map<string, RouteNode> => {
|
|
371
507
|
return new Map(
|
|
372
|
-
routeNodes.map((routeNode) => [
|
|
508
|
+
routeNodes.map((routeNode) => [
|
|
509
|
+
inferFullPath(routeNode, config),
|
|
510
|
+
routeNode,
|
|
511
|
+
]),
|
|
373
512
|
)
|
|
374
513
|
}
|
|
375
514
|
|
|
@@ -378,10 +517,11 @@ export const createRouteNodesByFullPath = (
|
|
|
378
517
|
*/
|
|
379
518
|
export const createRouteNodesByTo = (
|
|
380
519
|
routeNodes: Array<RouteNode>,
|
|
520
|
+
config?: Pick<Config, 'experimental' | 'routeToken'>,
|
|
381
521
|
): Map<string, RouteNode> => {
|
|
382
522
|
return new Map(
|
|
383
523
|
dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [
|
|
384
|
-
inferTo(routeNode),
|
|
524
|
+
inferTo(routeNode, config),
|
|
385
525
|
routeNode,
|
|
386
526
|
]),
|
|
387
527
|
)
|
|
@@ -404,8 +544,11 @@ export const createRouteNodesById = (
|
|
|
404
544
|
/**
|
|
405
545
|
* Infers to path
|
|
406
546
|
*/
|
|
407
|
-
export const inferTo = (
|
|
408
|
-
|
|
547
|
+
export const inferTo = (
|
|
548
|
+
routeNode: RouteNode,
|
|
549
|
+
config?: Pick<Config, 'experimental' | 'routeToken'>,
|
|
550
|
+
): string => {
|
|
551
|
+
const fullPath = inferFullPath(routeNode, config)
|
|
409
552
|
|
|
410
553
|
if (fullPath === '/') return fullPath
|
|
411
554
|
|
|
@@ -444,7 +587,7 @@ export function checkRouteFullPathUniqueness(
|
|
|
444
587
|
config: Config,
|
|
445
588
|
) {
|
|
446
589
|
const routes = _routes.map((d) => {
|
|
447
|
-
const inferredFullPath = inferFullPath(d)
|
|
590
|
+
const inferredFullPath = inferFullPath(d, config)
|
|
448
591
|
return { ...d, inferredFullPath }
|
|
449
592
|
})
|
|
450
593
|
|
|
@@ -581,6 +724,7 @@ export function buildFileRoutesByPathInterface(opts: {
|
|
|
581
724
|
routeNodes: Array<RouteNode>
|
|
582
725
|
module: string
|
|
583
726
|
interfaceName: string
|
|
727
|
+
config?: Pick<Config, 'experimental' | 'routeToken'>
|
|
584
728
|
}): string {
|
|
585
729
|
return `declare module '${opts.module}' {
|
|
586
730
|
interface ${opts.interfaceName} {
|
|
@@ -594,7 +738,7 @@ export function buildFileRoutesByPathInterface(opts: {
|
|
|
594
738
|
return `'${filePathId}': {
|
|
595
739
|
id: '${filePathId}'
|
|
596
740
|
path: '${inferPath(routeNode)}'
|
|
597
|
-
fullPath: '${inferFullPath(routeNode)}'
|
|
741
|
+
fullPath: '${inferFullPath(routeNode, opts.config)}'
|
|
598
742
|
preLoaderRoute: ${preloaderRoute}
|
|
599
743
|
parentRoute: typeof ${parent}
|
|
600
744
|
}`
|
|
@@ -647,3 +791,51 @@ export function getImportForRouteNode(
|
|
|
647
791
|
],
|
|
648
792
|
} satisfies ImportDeclaration
|
|
649
793
|
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Used to validate if a route is a pathless layout route
|
|
797
|
+
* @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`
|
|
798
|
+
* @param config The `router-generator` configuration object
|
|
799
|
+
* @returns Boolean indicating if the route is a pathless layout route
|
|
800
|
+
*/
|
|
801
|
+
export function isValidNonNestedRoute(
|
|
802
|
+
normalizedRoutePath: string,
|
|
803
|
+
config?: Pick<Config, 'experimental' | 'routeToken' | 'indexToken'>,
|
|
804
|
+
): boolean {
|
|
805
|
+
if (!config?.experimental?.nonNestedRoutes) {
|
|
806
|
+
return false
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const segments = normalizedRoutePath.split('/').filter(Boolean)
|
|
810
|
+
|
|
811
|
+
if (segments.length === 0) {
|
|
812
|
+
return false
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const lastRouteSegment = segments[segments.length - 1]!
|
|
816
|
+
|
|
817
|
+
// If segment === __root, then exit as false
|
|
818
|
+
if (lastRouteSegment === rootPathId) {
|
|
819
|
+
return false
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (
|
|
823
|
+
lastRouteSegment !== config.indexToken &&
|
|
824
|
+
lastRouteSegment !== config.routeToken &&
|
|
825
|
+
lastRouteSegment.endsWith('_')
|
|
826
|
+
) {
|
|
827
|
+
return true
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
for (const segment of segments.slice(0, -1).reverse()) {
|
|
831
|
+
if (segment === config.routeToken) {
|
|
832
|
+
return false
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (segment.endsWith('_')) {
|
|
836
|
+
return true
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return false
|
|
841
|
+
}
|