@tanstack/router-generator 1.52.0 → 1.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cjs/config.cjs +21 -19
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/config.d.cts +3 -0
  4. package/dist/cjs/filesystem/physical/getRouteNodes.cjs +125 -0
  5. package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -0
  6. package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +3 -0
  7. package/dist/cjs/filesystem/physical/rootPathId.cjs +5 -0
  8. package/dist/cjs/filesystem/physical/rootPathId.cjs.map +1 -0
  9. package/dist/cjs/filesystem/physical/rootPathId.d.cts +1 -0
  10. package/dist/cjs/filesystem/virtual/config.cjs +37 -0
  11. package/dist/cjs/filesystem/virtual/config.cjs.map +1 -0
  12. package/dist/cjs/filesystem/virtual/config.d.cts +3 -0
  13. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs +119 -0
  14. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -0
  15. package/dist/cjs/filesystem/virtual/getRouteNodes.d.cts +5 -0
  16. package/dist/cjs/generator.cjs +43 -168
  17. package/dist/cjs/generator.cjs.map +1 -1
  18. package/dist/cjs/generator.d.cts +1 -26
  19. package/dist/cjs/types.d.cts +27 -0
  20. package/dist/cjs/utils.cjs +34 -0
  21. package/dist/cjs/utils.cjs.map +1 -1
  22. package/dist/cjs/utils.d.cts +8 -0
  23. package/dist/esm/config.d.ts +3 -0
  24. package/dist/esm/config.js +2 -0
  25. package/dist/esm/config.js.map +1 -1
  26. package/dist/esm/filesystem/physical/getRouteNodes.d.ts +3 -0
  27. package/dist/esm/filesystem/physical/getRouteNodes.js +108 -0
  28. package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -0
  29. package/dist/esm/filesystem/physical/rootPathId.d.ts +1 -0
  30. package/dist/esm/filesystem/physical/rootPathId.js +5 -0
  31. package/dist/esm/filesystem/physical/rootPathId.js.map +1 -0
  32. package/dist/esm/filesystem/virtual/config.d.ts +3 -0
  33. package/dist/esm/filesystem/virtual/config.js +37 -0
  34. package/dist/esm/filesystem/virtual/config.js.map +1 -0
  35. package/dist/esm/filesystem/virtual/getRouteNodes.d.ts +5 -0
  36. package/dist/esm/filesystem/virtual/getRouteNodes.js +119 -0
  37. package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -0
  38. package/dist/esm/generator.d.ts +1 -26
  39. package/dist/esm/generator.js +29 -154
  40. package/dist/esm/generator.js.map +1 -1
  41. package/dist/esm/types.d.ts +27 -0
  42. package/dist/esm/utils.d.ts +8 -0
  43. package/dist/esm/utils.js +34 -0
  44. package/dist/esm/utils.js.map +1 -1
  45. package/package.json +3 -2
  46. package/src/config.ts +2 -0
  47. package/src/filesystem/physical/getRouteNodes.ts +151 -0
  48. package/src/filesystem/physical/rootPathId.ts +1 -0
  49. package/src/filesystem/virtual/config.ts +45 -0
  50. package/src/filesystem/virtual/getRouteNodes.ts +141 -0
  51. package/src/generator.ts +45 -239
  52. package/src/types.ts +28 -0
  53. package/src/utils.ts +43 -0
package/src/generator.ts CHANGED
@@ -2,175 +2,25 @@ 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 * as prettier from 'prettier'
5
- import { cleanPath, logging, trimPathLeft } from './utils'
5
+ import {
6
+ determineInitialRoutePath,
7
+ logging,
8
+ removeExt,
9
+ removeTrailingSlash,
10
+ removeUnderscores,
11
+ replaceBackslash,
12
+ routePathToVariable,
13
+ trimPathLeft,
14
+ } from './utils'
15
+ import { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes'
16
+ import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
17
+ import { rootPathId } from './filesystem/physical/rootPathId'
18
+ import type { GetRouteNodesResult, RouteNode } from './types'
6
19
  import type { Config } from './config'
7
20
 
8
21
  let latestTask = 0
9
- export const rootPathId = '__root'
10
22
  const routeGroupPatternRegex = /\(.+\)/g
11
23
  const possiblyNestedRouteGroupPatternRegex = /\([^/]+\)\/?/g
12
- const disallowedRouteGroupConfiguration = /\(([^)]+)\).(ts|js|tsx|jsx)/
13
-
14
- export type RouteNode = {
15
- filePath: string
16
- fullPath: string
17
- variableName: string
18
- routePath?: string
19
- cleanedPath?: string
20
- path?: string
21
- isNonPath?: boolean
22
- isNonLayout?: boolean
23
- isLayout?: boolean
24
- isVirtualParentRequired?: boolean
25
- isVirtualParentRoute?: boolean
26
- isRoute?: boolean
27
- isAPIRoute?: boolean
28
- isLoader?: boolean
29
- isComponent?: boolean
30
- isErrorComponent?: boolean
31
- isPendingComponent?: boolean
32
- isVirtual?: boolean
33
- isLazy?: boolean
34
- isRoot?: boolean
35
- children?: Array<RouteNode>
36
- parent?: RouteNode
37
- }
38
-
39
- async function getRouteNodes(config: Config) {
40
- const { routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern } =
41
- config
42
- const logger = logging({ disabled: config.disableLogging })
43
- const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')
44
-
45
- const routeNodes: Array<RouteNode> = []
46
-
47
- async function recurse(dir: string) {
48
- const fullDir = path.resolve(config.routesDirectory, dir)
49
- let dirList = await fsp.readdir(fullDir, { withFileTypes: true })
50
-
51
- dirList = dirList.filter((d) => {
52
- if (
53
- d.name.startsWith('.') ||
54
- (routeFileIgnorePrefix && d.name.startsWith(routeFileIgnorePrefix))
55
- ) {
56
- return false
57
- }
58
-
59
- if (routeFilePrefix) {
60
- return d.name.startsWith(routeFilePrefix)
61
- }
62
-
63
- if (routeFileIgnorePattern) {
64
- return !d.name.match(routeFileIgnoreRegExp)
65
- }
66
-
67
- return true
68
- })
69
-
70
- await Promise.all(
71
- dirList.map(async (dirent) => {
72
- const fullPath = path.join(fullDir, dirent.name)
73
- const relativePath = path.join(dir, dirent.name)
74
-
75
- if (dirent.isDirectory()) {
76
- await recurse(relativePath)
77
- } else if (fullPath.match(/\.(tsx|ts|jsx|js)$/)) {
78
- const filePath = replaceBackslash(path.join(dir, dirent.name))
79
- const filePathNoExt = removeExt(filePath)
80
- let routePath = determineInitialRoutePath(filePathNoExt)
81
-
82
- if (routeFilePrefix) {
83
- routePath = routePath.replaceAll(routeFilePrefix, '')
84
- }
85
-
86
- if (disallowedRouteGroupConfiguration.test(dirent.name)) {
87
- const errorMessage = `A route configuration for a route group was found at \`${filePath}\`. This is not supported. Did you mean to use a layout/pathless route instead?`
88
- logger.error(`ERROR: ${errorMessage}`)
89
- throw new Error(errorMessage)
90
- }
91
-
92
- const variableName = routePathToVariable(routePath)
93
-
94
- // Remove the index from the route path and
95
- // if the route path is empty, use `/'
96
-
97
- const isLazy = routePath.endsWith('/lazy')
98
-
99
- if (isLazy) {
100
- routePath = routePath.replace(/\/lazy$/, '')
101
- }
102
-
103
- const isRoute = routePath.endsWith(`/${config.routeToken}`)
104
- const isComponent = routePath.endsWith('/component')
105
- const isErrorComponent = routePath.endsWith('/errorComponent')
106
- const isPendingComponent = routePath.endsWith('/pendingComponent')
107
- const isLoader = routePath.endsWith('/loader')
108
- const isAPIRoute = routePath.startsWith(
109
- `${removeTrailingSlash(config.apiBase)}/`,
110
- )
111
-
112
- const segments = routePath.split('/')
113
- const lastRouteSegment = segments[segments.length - 1]
114
- const isLayout =
115
- (lastRouteSegment !== config.indexToken &&
116
- lastRouteSegment !== config.routeToken &&
117
- lastRouteSegment?.startsWith('_')) ||
118
- false
119
-
120
- ;(
121
- [
122
- [isComponent, 'component'],
123
- [isErrorComponent, 'errorComponent'],
124
- [isPendingComponent, 'pendingComponent'],
125
- [isLoader, 'loader'],
126
- ] as const
127
- ).forEach(([isType, type]) => {
128
- if (isType) {
129
- logger.warn(
130
- `WARNING: The \`.${type}.tsx\` suffix used for the ${filePath} file is deprecated. Use the new \`.lazy.tsx\` suffix instead.`,
131
- )
132
- }
133
- })
134
-
135
- routePath = routePath.replace(
136
- new RegExp(
137
- `/(component|errorComponent|pendingComponent|loader|${config.routeToken}|lazy)$`,
138
- ),
139
- '',
140
- )
141
-
142
- if (routePath === config.indexToken) {
143
- routePath = '/'
144
- }
145
-
146
- routePath =
147
- routePath.replace(new RegExp(`/${config.indexToken}$`), '/') || '/'
148
-
149
- routeNodes.push({
150
- filePath,
151
- fullPath,
152
- routePath,
153
- variableName,
154
- isRoute,
155
- isComponent,
156
- isErrorComponent,
157
- isPendingComponent,
158
- isLoader,
159
- isLazy,
160
- isLayout,
161
- isAPIRoute,
162
- })
163
- }
164
- }),
165
- )
166
-
167
- return routeNodes
168
- }
169
-
170
- await recurse('./')
171
-
172
- return routeNodes
173
- }
174
24
 
175
25
  let isFirst = false
176
26
  let skipMessage = false
@@ -216,12 +66,18 @@ export async function generator(config: Config) {
216
66
  parser: 'typescript',
217
67
  }
218
68
 
219
- const routePathIdPrefix = config.routeFilePrefix ?? ''
220
- const beforeRouteNodes = await getRouteNodes(config)
221
- const rootRouteNode = beforeRouteNodes.find(
222
- (d) => d.routePath === `/${rootPathId}`,
223
- )
69
+ let getRouteNodesResult: GetRouteNodesResult
70
+
71
+ if (config.virtualRouteConfig) {
72
+ getRouteNodesResult = await virtualGetRouteNodes(config)
73
+ } else {
74
+ getRouteNodesResult = await physicalGetRouteNodes(config)
75
+ }
224
76
 
77
+ const { rootRouteNode, routeNodes: beforeRouteNodes } = getRouteNodesResult
78
+ if (rootRouteNode === undefined) {
79
+ throw new Error(`rootRouteNode must not be undefined`)
80
+ }
225
81
  const preRouteNodes = multiSortBy(beforeRouteNodes, [
226
82
  (d) => (d.routePath === '/' ? -1 : 1),
227
83
  (d) => d.routePath?.split('/').length,
@@ -307,13 +163,11 @@ export const Route = createRootRoute({
307
163
  const trimmedPath = trimPathLeft(node.path ?? '')
308
164
 
309
165
  const split = trimmedPath.split('/')
310
- const first = split[0] ?? trimmedPath
311
166
  const lastRouteSegment = split[split.length - 1] ?? trimmedPath
312
167
 
313
168
  node.isNonPath =
314
169
  lastRouteSegment.startsWith('_') ||
315
170
  routeGroupPatternRegex.test(lastRouteSegment)
316
- node.isNonLayout = first.endsWith('_')
317
171
 
318
172
  node.cleanedPath = removeGroups(
319
173
  removeUnderscores(removeLayoutSegments(node.path)) ?? '',
@@ -571,11 +425,18 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
571
425
  .map((d) => d[0])
572
426
 
573
427
  const virtualRouteNodes = sortedRouteNodes.filter((d) => d.isVirtual)
574
- const rootPathIdExtension =
575
- config.addExtensions && rootRouteNode
576
- ? path.extname(rootRouteNode.filePath)
577
- : ''
578
428
 
429
+ function getImportPath(node: RouteNode) {
430
+ return replaceBackslash(
431
+ removeExt(
432
+ path.relative(
433
+ path.dirname(config.generatedRouteTree),
434
+ path.resolve(config.routesDirectory, node.filePath),
435
+ ),
436
+ config.addExtensions,
437
+ ),
438
+ )
439
+ }
579
440
  const routeImports = [
580
441
  ...config.routeTreeFileHeader,
581
442
  '// This file is auto-generated by TanStack Router',
@@ -584,29 +445,13 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
584
445
  : '',
585
446
  '// Import Routes',
586
447
  [
587
- `import { Route as rootRoute } from './${replaceBackslash(
588
- path.relative(
589
- path.dirname(config.generatedRouteTree),
590
- path.resolve(
591
- config.routesDirectory,
592
- `${routePathIdPrefix}${rootPathId}${rootPathIdExtension}`,
593
- ),
594
- ),
595
- )}'`,
448
+ `import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
596
449
  ...sortedRouteNodes
597
450
  .filter((d) => !d.isVirtual)
598
451
  .map((node) => {
599
452
  return `import { Route as ${
600
453
  node.variableName
601
- }Import } from './${replaceBackslash(
602
- removeExt(
603
- path.relative(
604
- path.dirname(config.generatedRouteTree),
605
- path.resolve(config.routesDirectory, node.filePath),
606
- ),
607
- config.addExtensions,
608
- ),
609
- )}'`
454
+ }Import } from './${getImportPath(node)}'`
610
455
  }),
611
456
  ].join('\n'),
612
457
  virtualRouteNodes.length ? '// Create Virtual Routes' : '',
@@ -704,7 +549,7 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
704
549
  ${routeNodes
705
550
  .map((routeNode) => {
706
551
  const [filePathId, routeId] = getFilePathIdAndRouteIdFromPath(
707
- routeNode.routePath!,
552
+ routeNode.routePath,
708
553
  )
709
554
 
710
555
  return `'${filePathId}': {
@@ -735,14 +580,14 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
735
580
  const createRouteManifest = () => {
736
581
  const routesManifest = {
737
582
  __root__: {
738
- filePath: rootRouteNode?.filePath,
583
+ filePath: rootRouteNode.filePath,
739
584
  children: routeTree.map(
740
- (d) => getFilePathIdAndRouteIdFromPath(d.routePath!)[1],
585
+ (d) => getFilePathIdAndRouteIdFromPath(d.routePath)[1],
741
586
  ),
742
587
  },
743
588
  ...Object.fromEntries(
744
589
  routeNodes.map((d) => {
745
- const [_, routeId] = getFilePathIdAndRouteIdFromPath(d.routePath!)
590
+ const [_, routeId] = getFilePathIdAndRouteIdFromPath(d.routePath)
746
591
 
747
592
  return [
748
593
  routeId,
@@ -753,7 +598,7 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
753
598
  : undefined,
754
599
  children: d.children?.map(
755
600
  (childRoute) =>
756
- getFilePathIdAndRouteIdFromPath(childRoute.routePath!)[1],
601
+ getFilePathIdAndRouteIdFromPath(childRoute.routePath)[1],
757
602
  ),
758
603
  },
759
604
  ]
@@ -820,24 +665,6 @@ export const Route = createAPIFileRoute('${escapedRoutePath}')({
820
665
  )
821
666
  }
822
667
 
823
- function routePathToVariable(routePath: string): string {
824
- return (
825
- removeUnderscores(routePath)
826
- ?.replace(/\/\$\//g, '/splat/')
827
- .replace(/\$$/g, 'splat')
828
- .replace(/\$/g, '')
829
- .split(/[/-]/g)
830
- .map((d, i) => (i > 0 ? capitalize(d) : d))
831
- .join('')
832
- .replace(/([^a-zA-Z0-9]|[.])/gm, '')
833
- .replace(/^(\d)/g, 'R$1') ?? ''
834
- )
835
- }
836
-
837
- export function removeExt(d: string, keepExtension: boolean = false) {
838
- return keepExtension ? d : d.substring(0, d.lastIndexOf('.')) || d
839
- }
840
-
841
668
  function spaces(d: number): string {
842
669
  return Array.from({ length: d })
843
670
  .map(() => ' ')
@@ -874,35 +701,14 @@ export function multiSortBy<T>(
874
701
  .map(([d]) => d)
875
702
  }
876
703
 
877
- function capitalize(s: string) {
878
- if (typeof s !== 'string') return ''
879
- return s.charAt(0).toUpperCase() + s.slice(1)
880
- }
881
-
882
- function removeUnderscores(s?: string) {
883
- return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/')
884
- }
885
-
886
704
  function removeTrailingUnderscores(s?: string) {
887
705
  return s?.replaceAll(/(_$)/gi, '').replaceAll(/(_\/)/gi, '/')
888
706
  }
889
707
 
890
- function replaceBackslash(s: string) {
891
- return s.replaceAll(/\\/gi, '/')
892
- }
893
-
894
708
  function removeGroups(s: string) {
895
709
  return s.replace(possiblyNestedRouteGroupPatternRegex, '')
896
710
  }
897
711
 
898
- function removeTrailingSlash(s: string) {
899
- return s.replace(/\/$/, '')
900
- }
901
-
902
- function determineInitialRoutePath(routePath: string) {
903
- return cleanPath(`/${routePath.split('.').join('/')}`) || ''
904
- }
905
-
906
712
  /**
907
713
  * The `node.path` is used as the `id` in the route definition.
908
714
  * This function checks if the given node has a parent and if so, it determines the correct path for the given node.
@@ -911,7 +717,7 @@ function determineInitialRoutePath(routePath: string) {
911
717
  */
912
718
  function determineNodePath(node: RouteNode) {
913
719
  return (node.path = node.parent
914
- ? node.routePath?.replace(node.parent.routePath!, '') || '/'
720
+ ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'
915
721
  : node.routePath)
916
722
  }
917
723
 
@@ -995,7 +801,7 @@ export const inferPath = (routeNode: RouteNode): string => {
995
801
  : (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
996
802
  }
997
803
 
998
- function getFilePathIdAndRouteIdFromPath(pathname: string) {
804
+ function getFilePathIdAndRouteIdFromPath(pathname?: string) {
999
805
  const filePathId = removeTrailingUnderscores(pathname)
1000
806
  const id = removeGroups(filePathId ?? '')
1001
807
 
package/src/types.ts ADDED
@@ -0,0 +1,28 @@
1
+ export type RouteNode = {
2
+ filePath: string
3
+ fullPath: string
4
+ variableName: string
5
+ routePath?: string
6
+ cleanedPath?: string
7
+ path?: string
8
+ isNonPath?: boolean
9
+ isLayout?: boolean
10
+ isVirtualParentRequired?: boolean
11
+ isVirtualParentRoute?: boolean
12
+ isRoute?: boolean
13
+ isAPIRoute?: boolean
14
+ isLoader?: boolean
15
+ isComponent?: boolean
16
+ isErrorComponent?: boolean
17
+ isPendingComponent?: boolean
18
+ isVirtual?: boolean
19
+ isLazy?: boolean
20
+ isRoot?: boolean
21
+ children?: Array<RouteNode>
22
+ parent?: RouteNode
23
+ }
24
+
25
+ export interface GetRouteNodesResult {
26
+ rootRouteNode?: RouteNode
27
+ routeNodes: Array<RouteNode>
28
+ }
package/src/utils.ts CHANGED
@@ -26,3 +26,46 @@ export function logging(config: { disabled: boolean }) {
26
26
  },
27
27
  }
28
28
  }
29
+
30
+ export function removeLeadingSlash(path: string): string {
31
+ return path.replace(/^\//, '')
32
+ }
33
+
34
+ export function removeTrailingSlash(s: string) {
35
+ return s.replace(/\/$/, '')
36
+ }
37
+
38
+ export function determineInitialRoutePath(routePath: string) {
39
+ return cleanPath(`/${routePath.split('.').join('/')}`) || ''
40
+ }
41
+
42
+ export function replaceBackslash(s: string) {
43
+ return s.replaceAll(/\\/gi, '/')
44
+ }
45
+
46
+ export function routePathToVariable(routePath: string): string {
47
+ return (
48
+ removeUnderscores(routePath)
49
+ ?.replace(/\/\$\//g, '/splat/')
50
+ .replace(/\$$/g, 'splat')
51
+ .replace(/\$/g, '')
52
+ .split(/[/-]/g)
53
+ .map((d, i) => (i > 0 ? capitalize(d) : d))
54
+ .join('')
55
+ .replace(/([^a-zA-Z0-9]|[.])/gm, '')
56
+ .replace(/^(\d)/g, 'R$1') ?? ''
57
+ )
58
+ }
59
+
60
+ export function removeUnderscores(s?: string) {
61
+ return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/')
62
+ }
63
+
64
+ export function capitalize(s: string) {
65
+ if (typeof s !== 'string') return ''
66
+ return s.charAt(0).toUpperCase() + s.slice(1)
67
+ }
68
+
69
+ export function removeExt(d: string, keepExtension: boolean = false) {
70
+ return keepExtension ? d : d.substring(0, d.lastIndexOf('.')) || d
71
+ }