Skip to content

Commit fc926bf

Browse files
fix: router-plugins correctly handle virtual file routes regardless of their location (#4407)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a0d9811 commit fc926bf

File tree

11 files changed

+297
-235
lines changed

11 files changed

+297
-235
lines changed

packages/router-generator/src/filesystem/physical/getRouteNodes.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import type { Config } from '../../config'
1919

2020
const disallowedRouteGroupConfiguration = /\(([^)]+)\).(ts|js|tsx|jsx)/
2121

22+
const virtualConfigFileRegExp = /__virtual\.[mc]?[jt]s$/
23+
export function isVirtualConfigFile(fileName: string): boolean {
24+
return virtualConfigFileRegExp.test(fileName)
25+
}
26+
2227
export async function getRouteNodes(
2328
config: Pick<
2429
Config,
@@ -38,6 +43,7 @@ export async function getRouteNodes(
3843
const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')
3944

4045
const routeNodes: Array<RouteNode> = []
46+
const allPhysicalDirectories: Array<string> = []
4147

4248
async function recurse(dir: string) {
4349
const fullDir = path.resolve(config.routesDirectory, dir)
@@ -63,7 +69,7 @@ export async function getRouteNodes(
6369
})
6470

6571
const virtualConfigFile = dirList.find((dirent) => {
66-
return dirent.isFile() && dirent.name.match(/__virtual\.[mc]?[jt]s$/)
72+
return dirent.isFile() && isVirtualConfigFile(dirent.name)
6773
})
6874

6975
if (virtualConfigFile !== undefined) {
@@ -81,14 +87,16 @@ export async function getRouteNodes(
8187
file: '',
8288
children: virtualRouteSubtreeConfig,
8389
}
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)
92100
virtualRouteNodes.forEach((node) => {
93101
const filePath = replaceBackslash(path.join(dir, node.filePath))
94102
const routePath = `/${dir}${node.routePath}`
@@ -192,7 +200,11 @@ export async function getRouteNodes(
192200
rootRouteNode.variableName = 'root'
193201
}
194202

195-
return { rootRouteNode, routeNodes }
203+
return {
204+
rootRouteNode,
205+
routeNodes,
206+
physicalDirectories: allPhysicalDirectories,
207+
}
196208
}
197209

198210
/**

packages/router-generator/src/filesystem/virtual/getRouteNodes.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export async function getRouteNodes(
5353
throw new Error(`virtualRouteConfig is undefined`)
5454
}
5555
let virtualRouteConfig: VirtualRootRoute
56-
let children: Array<RouteNode> = []
5756
if (typeof tsrConfig.virtualRouteConfig === 'string') {
5857
virtualRouteConfig = await getVirtualRouteConfigFromFileExport(
5958
tsrConfig,
@@ -62,7 +61,7 @@ export async function getRouteNodes(
6261
} else {
6362
virtualRouteConfig = tsrConfig.virtualRouteConfig
6463
}
65-
children = await getRouteNodesRecursive(
64+
const { children, physicalDirectories } = await getRouteNodesRecursive(
6665
tsrConfig,
6766
root,
6867
fullDir,
@@ -80,7 +79,7 @@ export async function getRouteNodes(
8079
const rootRouteNode = allNodes[0]
8180
const routeNodes = allNodes.slice(1)
8281

83-
return { rootRouteNode, routeNodes }
82+
return { rootRouteNode, routeNodes, physicalDirectories }
8483
}
8584

8685
/**
@@ -135,20 +134,22 @@ export async function getRouteNodesRecursive(
135134
fullDir: string,
136135
nodes?: Array<VirtualRouteNode>,
137136
parent?: RouteNode,
138-
): Promise<Array<RouteNode>> {
137+
): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {
139138
if (nodes === undefined) {
140-
return []
139+
return { children: [], physicalDirectories: [] }
141140
}
141+
const allPhysicalDirectories: Array<string> = []
142142
const children = await Promise.all(
143143
nodes.map(async (node) => {
144144
if (node.type === 'physical') {
145-
const { routeNodes } = await getRouteNodesPhysical(
145+
const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(
146146
{
147147
...tsrConfig,
148148
routesDirectory: resolve(fullDir, node.directory),
149149
},
150150
root,
151151
)
152+
allPhysicalDirectories.push(node.directory)
152153
routeNodes.forEach((subtreeNode) => {
153154
subtreeNode.variableName = routePathToVariable(
154155
`${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,
@@ -206,14 +207,16 @@ export async function getRouteNodesRecursive(
206207
}
207208

208209
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+
)
216218
routeNode.children = children
219+
allPhysicalDirectories.push(...physicalDirectories)
217220

218221
// If the route has children, it should be a layout
219222
routeNode._fsRouteType = 'layout'
@@ -242,19 +245,24 @@ export async function getRouteNodesRecursive(
242245
}
243246

244247
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+
)
252256
routeNode.children = children
257+
allPhysicalDirectories.push(...physicalDirectories)
253258
}
254259
return routeNode
255260
}
256261
}
257262
}),
258263
)
259-
return children.flat()
264+
return {
265+
children: children.flat(),
266+
physicalDirectories: allPhysicalDirectories,
267+
}
260268
}

packages/router-generator/src/generator.ts

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { mkdtempSync } from 'node:fs'
44
import crypto from 'node:crypto'
55
import { deepEqual, rootRouteId } from '@tanstack/router-core'
66
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'
811
import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
912
import { rootPathId } from './filesystem/physical/rootPathId'
1013
import {
@@ -170,6 +173,7 @@ export class Generator {
170173
// this is just a cache for the transform plugins since we need them for each route file that is to be processed
171174
private transformPlugins: Array<TransformPlugin> = []
172175
private routeGroupPatternRegex = /\(.+\)/g
176+
private physicalDirectories: Array<string> = []
173177

174178
constructor(opts: { config: Config; root: string; fs?: fs }) {
175179
this.config = opts.config
@@ -203,17 +207,17 @@ export class Generator {
203207
: path.resolve(this.root, this.config.routesDirectory)
204208
}
205209

210+
public getRouteFileList(): Set<string> {
211+
return new Set(this.routeNodeCache.keys())
212+
}
213+
206214
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-
}
215+
if (
216+
event &&
217+
event.type !== 'rerun' &&
218+
!this.isFileRelevantForRouteTreeGeneration(event.path)
219+
) {
220+
return
217221
}
218222
this.fileEventQueue.push(event ?? { type: 'rerun' })
219223
// only allow a single run at a time
@@ -233,7 +237,7 @@ export class Generator {
233237
await Promise.all(
234238
tempQueue.map(async (e) => {
235239
if (e.type === 'update') {
236-
let cacheEntry
240+
let cacheEntry: GeneratorCacheEntry | undefined
237241
if (e.path === this.generatedRouteTreePath) {
238242
cacheEntry = this.routeTreeFileCache
239243
} else {
@@ -301,14 +305,19 @@ export class Generator {
301305
getRouteNodesResult = await physicalGetRouteNodes(this.config, this.root)
302306
}
303307

304-
const { rootRouteNode, routeNodes: beforeRouteNodes } = getRouteNodesResult
308+
const {
309+
rootRouteNode,
310+
routeNodes: beforeRouteNodes,
311+
physicalDirectories,
312+
} = getRouteNodesResult
305313
if (rootRouteNode === undefined) {
306314
let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.`
307315
if (!this.config.virtualRouteConfig) {
308316
errorMessage += `\nMake sure that you add a "${rootPathId}.${this.config.disableTypes ? 'js' : 'tsx'}" file to your routes directory.\nAdd the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.disableTypes ? 'js' : 'tsx'}"`
309317
}
310318
throw new Error(errorMessage)
311319
}
320+
this.physicalDirectories = physicalDirectories
312321

313322
writeRouteTreeFile = await this.handleRootNode(rootRouteNode)
314323

@@ -1292,4 +1301,42 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
12921301

12931302
acc.routeNodes.push(node)
12941303
}
1304+
1305+
// only process files that are relevant for the route tree generation
1306+
private isFileRelevantForRouteTreeGeneration(filePath: string): boolean {
1307+
// the generated route tree file
1308+
if (filePath === this.generatedRouteTreePath) {
1309+
return true
1310+
}
1311+
1312+
// files inside the routes folder
1313+
if (filePath.startsWith(this.routesDirectoryPath)) {
1314+
return true
1315+
}
1316+
1317+
// the virtual route config file passed into `virtualRouteConfig`
1318+
if (
1319+
typeof this.config.virtualRouteConfig === 'string' &&
1320+
filePath === this.config.virtualRouteConfig
1321+
) {
1322+
return true
1323+
}
1324+
1325+
// this covers all files that are mounted via `virtualRouteConfig` or any `__virtual.ts` files
1326+
if (this.routeNodeCache.has(filePath)) {
1327+
return true
1328+
}
1329+
1330+
// virtual config files such as`__virtual.ts`
1331+
if (isVirtualConfigFile(path.basename(filePath))) {
1332+
return true
1333+
}
1334+
1335+
// route files inside directories mounted via `physical()` inside a virtual route config
1336+
if (this.physicalDirectories.some((dir) => filePath.startsWith(dir))) {
1337+
return true
1338+
}
1339+
1340+
return false
1341+
}
12951342
}

packages/router-generator/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type RouteNode = {
1818
export interface GetRouteNodesResult {
1919
rootRouteNode?: RouteNode
2020
routeNodes: Array<RouteNode>
21+
physicalDirectories: Array<string>
2122
}
2223

2324
export type FsRouteType =

0 commit comments

Comments
 (0)