@tanstack/router-generator 1.121.6 → 1.121.9

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.
@@ -4,7 +4,7 @@ export { Generator } from './generator.js';
4
4
  export type { FileEventType, FileEvent, GeneratorEvent } from './generator.js';
5
5
  export type { GeneratorPluginBase, GeneratorPlugin, GeneratorPluginWithTransform, } from './plugin/types.js';
6
6
  export { capitalize, cleanPath, trimPathLeft, removeLeadingSlash, removeTrailingSlash, determineInitialRoutePath, replaceBackslash, routePathToVariable, removeUnderscores, resetRegex, multiSortBy, writeIfDifferent, format, removeExt, checkRouteFullPathUniqueness, hasChildWithExport, } from './utils.js';
7
- export type { RouteNode, GetRouteNodesResult, ImportDeclaration, ImportSpecifier, } from './types.js';
7
+ export type { RouteNode, GetRouteNodesResult, GetRoutesByFileMapResult, GetRoutesByFileMapResultValue, ImportDeclaration, ImportSpecifier, } from './types.js';
8
8
  export { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes.js';
9
9
  export { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes.js';
10
10
  export { rootPathId } from './filesystem/physical/rootPathId.js';
@@ -17,6 +17,7 @@ export type RouteNode = {
17
17
  export interface GetRouteNodesResult {
18
18
  rootRouteNode?: RouteNode;
19
19
  routeNodes: Array<RouteNode>;
20
+ physicalDirectories: Array<string>;
20
21
  }
21
22
  export type FsRouteType = '__root' | 'static' | 'layout' | 'pathless_layout' | 'lazy' | 'loader' | 'component' | 'pendingComponent' | 'errorComponent';
22
23
  export type RouteSubNode = {
@@ -40,3 +41,7 @@ export type HandleNodeAccumulator = {
40
41
  routePiecesByPath: Record<string, RouteSubNode>;
41
42
  routeNodes: Array<RouteNode>;
42
43
  };
44
+ export type GetRoutesByFileMapResultValue = {
45
+ routePath: string;
46
+ };
47
+ export type GetRoutesByFileMapResult = Map<string, GetRoutesByFileMapResultValue>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-generator",
3
- "version": "1.121.6",
3
+ "version": "1.121.9",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -19,6 +19,11 @@ import type { Config } from '../../config'
19
19
 
20
20
  const disallowedRouteGroupConfiguration = /\(([^)]+)\).(ts|js|tsx|jsx)/
21
21
 
22
+ const virtualConfigFileRegExp = /__virtual\.[mc]?[jt]s$/
23
+ export function isVirtualConfigFile(fileName: string): boolean {
24
+ return virtualConfigFileRegExp.test(fileName)
25
+ }
26
+
22
27
  export async function getRouteNodes(
23
28
  config: Pick<
24
29
  Config,
@@ -38,6 +43,7 @@ export async function getRouteNodes(
38
43
  const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')
39
44
 
40
45
  const routeNodes: Array<RouteNode> = []
46
+ const allPhysicalDirectories: Array<string> = []
41
47
 
42
48
  async function recurse(dir: string) {
43
49
  const fullDir = path.resolve(config.routesDirectory, dir)
@@ -63,7 +69,7 @@ export async function getRouteNodes(
63
69
  })
64
70
 
65
71
  const virtualConfigFile = dirList.find((dirent) => {
66
- return dirent.isFile() && dirent.name.match(/__virtual\.[mc]?[jt]s$/)
72
+ return dirent.isFile() && isVirtualConfigFile(dirent.name)
67
73
  })
68
74
 
69
75
  if (virtualConfigFile !== undefined) {
@@ -81,14 +87,16 @@ export async function getRouteNodes(
81
87
  file: '',
82
88
  children: virtualRouteSubtreeConfig,
83
89
  }
84
- const { routeNodes: virtualRouteNodes } = await getRouteNodesVirtual(
85
- {
86
- ...config,
87
- routesDirectory: fullDir,
88
- virtualRouteConfig: dummyRoot,
89
- },
90
- root,
91
- )
90
+ const { routeNodes: virtualRouteNodes, physicalDirectories } =
91
+ await getRouteNodesVirtual(
92
+ {
93
+ ...config,
94
+ routesDirectory: fullDir,
95
+ virtualRouteConfig: dummyRoot,
96
+ },
97
+ root,
98
+ )
99
+ allPhysicalDirectories.push(...physicalDirectories)
92
100
  virtualRouteNodes.forEach((node) => {
93
101
  const filePath = replaceBackslash(path.join(dir, node.filePath))
94
102
  const routePath = `/${dir}${node.routePath}`
@@ -192,7 +200,11 @@ export async function getRouteNodes(
192
200
  rootRouteNode.variableName = 'root'
193
201
  }
194
202
 
195
- return { rootRouteNode, routeNodes }
203
+ return {
204
+ rootRouteNode,
205
+ routeNodes,
206
+ physicalDirectories: allPhysicalDirectories,
207
+ }
196
208
  }
197
209
 
198
210
  /**
@@ -53,7 +53,6 @@ export async function getRouteNodes(
53
53
  throw new Error(`virtualRouteConfig is undefined`)
54
54
  }
55
55
  let virtualRouteConfig: VirtualRootRoute
56
- let children: Array<RouteNode> = []
57
56
  if (typeof tsrConfig.virtualRouteConfig === 'string') {
58
57
  virtualRouteConfig = await getVirtualRouteConfigFromFileExport(
59
58
  tsrConfig,
@@ -62,7 +61,7 @@ export async function getRouteNodes(
62
61
  } else {
63
62
  virtualRouteConfig = tsrConfig.virtualRouteConfig
64
63
  }
65
- children = await getRouteNodesRecursive(
64
+ const { children, physicalDirectories } = await getRouteNodesRecursive(
66
65
  tsrConfig,
67
66
  root,
68
67
  fullDir,
@@ -80,7 +79,7 @@ export async function getRouteNodes(
80
79
  const rootRouteNode = allNodes[0]
81
80
  const routeNodes = allNodes.slice(1)
82
81
 
83
- return { rootRouteNode, routeNodes }
82
+ return { rootRouteNode, routeNodes, physicalDirectories }
84
83
  }
85
84
 
86
85
  /**
@@ -135,20 +134,22 @@ export async function getRouteNodesRecursive(
135
134
  fullDir: string,
136
135
  nodes?: Array<VirtualRouteNode>,
137
136
  parent?: RouteNode,
138
- ): Promise<Array<RouteNode>> {
137
+ ): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {
139
138
  if (nodes === undefined) {
140
- return []
139
+ return { children: [], physicalDirectories: [] }
141
140
  }
141
+ const allPhysicalDirectories: Array<string> = []
142
142
  const children = await Promise.all(
143
143
  nodes.map(async (node) => {
144
144
  if (node.type === 'physical') {
145
- const { routeNodes } = await getRouteNodesPhysical(
145
+ const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(
146
146
  {
147
147
  ...tsrConfig,
148
148
  routesDirectory: resolve(fullDir, node.directory),
149
149
  },
150
150
  root,
151
151
  )
152
+ allPhysicalDirectories.push(node.directory)
152
153
  routeNodes.forEach((subtreeNode) => {
153
154
  subtreeNode.variableName = routePathToVariable(
154
155
  `${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,
@@ -206,14 +207,16 @@ export async function getRouteNodesRecursive(
206
207
  }
207
208
 
208
209
  if (node.children !== undefined) {
209
- const children = await getRouteNodesRecursive(
210
- tsrConfig,
211
- root,
212
- fullDir,
213
- node.children,
214
- routeNode,
215
- )
210
+ const { children, physicalDirectories } =
211
+ await getRouteNodesRecursive(
212
+ tsrConfig,
213
+ root,
214
+ fullDir,
215
+ node.children,
216
+ routeNode,
217
+ )
216
218
  routeNode.children = children
219
+ allPhysicalDirectories.push(...physicalDirectories)
217
220
 
218
221
  // If the route has children, it should be a layout
219
222
  routeNode._fsRouteType = 'layout'
@@ -242,19 +245,24 @@ export async function getRouteNodesRecursive(
242
245
  }
243
246
 
244
247
  if (node.children !== undefined) {
245
- const children = await getRouteNodesRecursive(
246
- tsrConfig,
247
- root,
248
- fullDir,
249
- node.children,
250
- routeNode,
251
- )
248
+ const { children, physicalDirectories } =
249
+ await getRouteNodesRecursive(
250
+ tsrConfig,
251
+ root,
252
+ fullDir,
253
+ node.children,
254
+ routeNode,
255
+ )
252
256
  routeNode.children = children
257
+ allPhysicalDirectories.push(...physicalDirectories)
253
258
  }
254
259
  return routeNode
255
260
  }
256
261
  }
257
262
  }),
258
263
  )
259
- return children.flat()
264
+ return {
265
+ children: children.flat(),
266
+ physicalDirectories: allPhysicalDirectories,
267
+ }
260
268
  }
package/src/generator.ts CHANGED
@@ -4,7 +4,10 @@ import { mkdtempSync } from 'node:fs'
4
4
  import crypto from 'node:crypto'
5
5
  import { deepEqual, rootRouteId } from '@tanstack/router-core'
6
6
  import { logging } from './logger'
7
- import { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes'
7
+ import {
8
+ isVirtualConfigFile,
9
+ getRouteNodes as physicalGetRouteNodes,
10
+ } from './filesystem/physical/getRouteNodes'
8
11
  import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
9
12
  import { rootPathId } from './filesystem/physical/rootPathId'
10
13
  import {
@@ -45,6 +48,7 @@ import type { TargetTemplate } from './template'
45
48
  import type {
46
49
  FsRouteType,
47
50
  GetRouteNodesResult,
51
+ GetRoutesByFileMapResult,
48
52
  HandleNodeAccumulator,
49
53
  ImportDeclaration,
50
54
  RouteNode,
@@ -134,6 +138,7 @@ interface GeneratorCacheEntry {
134
138
 
135
139
  interface RouteNodeCacheEntry extends GeneratorCacheEntry {
136
140
  exports: Array<string>
141
+ routeId: string
137
142
  }
138
143
 
139
144
  type GeneratorRouteNodeCache = Map</** filePath **/ string, RouteNodeCacheEntry>
@@ -170,6 +175,7 @@ export class Generator {
170
175
  // this is just a cache for the transform plugins since we need them for each route file that is to be processed
171
176
  private transformPlugins: Array<TransformPlugin> = []
172
177
  private routeGroupPatternRegex = /\(.+\)/g
178
+ private physicalDirectories: Array<string> = []
173
179
 
174
180
  constructor(opts: { config: Config; root: string; fs?: fs }) {
175
181
  this.config = opts.config
@@ -203,17 +209,22 @@ export class Generator {
203
209
  : path.resolve(this.root, this.config.routesDirectory)
204
210
  }
205
211
 
212
+ public getRoutesByFileMap(): GetRoutesByFileMapResult {
213
+ return new Map(
214
+ [...this.routeNodeCache.entries()].map(([filePath, cacheEntry]) => [
215
+ filePath,
216
+ { routePath: cacheEntry.routeId },
217
+ ]),
218
+ )
219
+ }
220
+
206
221
  public async run(event?: GeneratorEvent): Promise<void> {
207
- // we are only interested in FileEvents that affect either the generated route tree or files inside the routes folder
208
- if (event && event.type !== 'rerun') {
209
- if (
210
- !(
211
- event.path === this.generatedRouteTreePath ||
212
- event.path.startsWith(this.routesDirectoryPath)
213
- )
214
- ) {
215
- return
216
- }
222
+ if (
223
+ event &&
224
+ event.type !== 'rerun' &&
225
+ !this.isFileRelevantForRouteTreeGeneration(event.path)
226
+ ) {
227
+ return
217
228
  }
218
229
  this.fileEventQueue.push(event ?? { type: 'rerun' })
219
230
  // only allow a single run at a time
@@ -233,7 +244,7 @@ export class Generator {
233
244
  await Promise.all(
234
245
  tempQueue.map(async (e) => {
235
246
  if (e.type === 'update') {
236
- let cacheEntry
247
+ let cacheEntry: GeneratorCacheEntry | undefined
237
248
  if (e.path === this.generatedRouteTreePath) {
238
249
  cacheEntry = this.routeTreeFileCache
239
250
  } else {
@@ -301,7 +312,11 @@ export class Generator {
301
312
  getRouteNodesResult = await physicalGetRouteNodes(this.config, this.root)
302
313
  }
303
314
 
304
- const { rootRouteNode, routeNodes: beforeRouteNodes } = getRouteNodesResult
315
+ const {
316
+ rootRouteNode,
317
+ routeNodes: beforeRouteNodes,
318
+ physicalDirectories,
319
+ } = getRouteNodesResult
305
320
  if (rootRouteNode === undefined) {
306
321
  let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.`
307
322
  if (!this.config.virtualRouteConfig) {
@@ -309,6 +324,7 @@ export class Generator {
309
324
  }
310
325
  throw new Error(errorMessage)
311
326
  }
327
+ this.physicalDirectories = physicalDirectories
312
328
 
313
329
  writeRouteTreeFile = await this.handleRootNode(rootRouteNode)
314
330
 
@@ -822,6 +838,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
822
838
  fileContent: existingRouteFile.fileContent,
823
839
  mtimeMs: existingRouteFile.stat.mtimeMs,
824
840
  exports: [],
841
+ routeId: node.routePath ?? '$$TSR_NO_ROUTE_PATH_ASSIGNED$$',
825
842
  }
826
843
 
827
844
  const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? ''
@@ -1101,6 +1118,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
1101
1118
  fileContent: rootNodeFile.fileContent,
1102
1119
  mtimeMs: rootNodeFile.stat.mtimeMs,
1103
1120
  exports: [],
1121
+ routeId: node.routePath ?? '$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$',
1104
1122
  }
1105
1123
 
1106
1124
  // scaffold the root route
@@ -1292,4 +1310,42 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
1292
1310
 
1293
1311
  acc.routeNodes.push(node)
1294
1312
  }
1313
+
1314
+ // only process files that are relevant for the route tree generation
1315
+ private isFileRelevantForRouteTreeGeneration(filePath: string): boolean {
1316
+ // the generated route tree file
1317
+ if (filePath === this.generatedRouteTreePath) {
1318
+ return true
1319
+ }
1320
+
1321
+ // files inside the routes folder
1322
+ if (filePath.startsWith(this.routesDirectoryPath)) {
1323
+ return true
1324
+ }
1325
+
1326
+ // the virtual route config file passed into `virtualRouteConfig`
1327
+ if (
1328
+ typeof this.config.virtualRouteConfig === 'string' &&
1329
+ filePath === this.config.virtualRouteConfig
1330
+ ) {
1331
+ return true
1332
+ }
1333
+
1334
+ // this covers all files that are mounted via `virtualRouteConfig` or any `__virtual.ts` files
1335
+ if (this.routeNodeCache.has(filePath)) {
1336
+ return true
1337
+ }
1338
+
1339
+ // virtual config files such as`__virtual.ts`
1340
+ if (isVirtualConfigFile(path.basename(filePath))) {
1341
+ return true
1342
+ }
1343
+
1344
+ // route files inside directories mounted via `physical()` inside a virtual route config
1345
+ if (this.physicalDirectories.some((dir) => filePath.startsWith(dir))) {
1346
+ return true
1347
+ }
1348
+
1349
+ return false
1350
+ }
1295
1351
  }
package/src/index.ts CHANGED
@@ -37,6 +37,8 @@ export {
37
37
  export type {
38
38
  RouteNode,
39
39
  GetRouteNodesResult,
40
+ GetRoutesByFileMapResult,
41
+ GetRoutesByFileMapResultValue,
40
42
  ImportDeclaration,
41
43
  ImportSpecifier,
42
44
  } from './types'
package/src/types.ts CHANGED
@@ -18,6 +18,7 @@ export type RouteNode = {
18
18
  export interface GetRouteNodesResult {
19
19
  rootRouteNode?: RouteNode
20
20
  routeNodes: Array<RouteNode>
21
+ physicalDirectories: Array<string>
21
22
  }
22
23
 
23
24
  export type FsRouteType =
@@ -54,3 +55,9 @@ export type HandleNodeAccumulator = {
54
55
  routePiecesByPath: Record<string, RouteSubNode>
55
56
  routeNodes: Array<RouteNode>
56
57
  }
58
+
59
+ export type GetRoutesByFileMapResultValue = { routePath: string }
60
+ export type GetRoutesByFileMapResult = Map<
61
+ string,
62
+ GetRoutesByFileMapResultValue
63
+ >