@tanstack/router-generator 1.132.0-alpha.9 → 1.132.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 (58) hide show
  1. package/dist/cjs/config.cjs +6 -2
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/config.d.cts +12 -9
  4. package/dist/cjs/generator.cjs +264 -316
  5. package/dist/cjs/generator.cjs.map +1 -1
  6. package/dist/cjs/generator.d.cts +20 -7
  7. package/dist/cjs/index.cjs +0 -1
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +4 -4
  10. package/dist/cjs/plugin/types.d.cts +10 -38
  11. package/dist/cjs/transform/transform.cjs +108 -40
  12. package/dist/cjs/transform/transform.cjs.map +1 -1
  13. package/dist/cjs/transform/transform.d.cts +1 -1
  14. package/dist/cjs/transform/types.d.cts +4 -18
  15. package/dist/cjs/types.d.cts +1 -1
  16. package/dist/cjs/utils.cjs +55 -39
  17. package/dist/cjs/utils.cjs.map +1 -1
  18. package/dist/cjs/utils.d.cts +5 -5
  19. package/dist/esm/config.d.ts +12 -9
  20. package/dist/esm/config.js +6 -2
  21. package/dist/esm/config.js.map +1 -1
  22. package/dist/esm/generator.d.ts +20 -7
  23. package/dist/esm/generator.js +266 -318
  24. package/dist/esm/generator.js.map +1 -1
  25. package/dist/esm/index.d.ts +4 -4
  26. package/dist/esm/index.js +1 -2
  27. package/dist/esm/plugin/types.d.ts +10 -38
  28. package/dist/esm/transform/transform.d.ts +1 -1
  29. package/dist/esm/transform/transform.js +106 -38
  30. package/dist/esm/transform/transform.js.map +1 -1
  31. package/dist/esm/transform/types.d.ts +4 -18
  32. package/dist/esm/types.d.ts +1 -1
  33. package/dist/esm/utils.d.ts +5 -5
  34. package/dist/esm/utils.js +55 -39
  35. package/dist/esm/utils.js.map +1 -1
  36. package/package.json +5 -5
  37. package/src/config.ts +7 -1
  38. package/src/generator.ts +306 -366
  39. package/src/index.ts +2 -7
  40. package/src/plugin/types.ts +11 -44
  41. package/src/transform/transform.ts +118 -53
  42. package/src/transform/types.ts +5 -18
  43. package/src/types.ts +1 -1
  44. package/src/utils.ts +85 -70
  45. package/dist/cjs/plugin/default-generator-plugin.cjs +0 -94
  46. package/dist/cjs/plugin/default-generator-plugin.cjs.map +0 -1
  47. package/dist/cjs/plugin/default-generator-plugin.d.cts +0 -2
  48. package/dist/cjs/transform/default-transform-plugin.cjs +0 -97
  49. package/dist/cjs/transform/default-transform-plugin.cjs.map +0 -1
  50. package/dist/cjs/transform/default-transform-plugin.d.cts +0 -2
  51. package/dist/esm/plugin/default-generator-plugin.d.ts +0 -2
  52. package/dist/esm/plugin/default-generator-plugin.js +0 -94
  53. package/dist/esm/plugin/default-generator-plugin.js.map +0 -1
  54. package/dist/esm/transform/default-transform-plugin.d.ts +0 -2
  55. package/dist/esm/transform/default-transform-plugin.js +0 -97
  56. package/dist/esm/transform/default-transform-plugin.js.map +0 -1
  57. package/src/plugin/default-generator-plugin.ts +0 -109
  58. package/src/transform/default-transform-plugin.ts +0 -106
@@ -2,15 +2,14 @@ import path from "node:path";
2
2
  import * as fsp from "node:fs/promises";
3
3
  import { existsSync, mkdirSync } from "node:fs";
4
4
  import crypto from "node:crypto";
5
- import { deepEqual, rootRouteId } from "@tanstack/router-core";
5
+ import { rootRouteId } from "@tanstack/router-core";
6
6
  import { logging } from "./logger.js";
7
7
  import { getRouteNodes as getRouteNodes$1, isVirtualConfigFile } from "./filesystem/physical/getRouteNodes.js";
8
8
  import { getRouteNodes } from "./filesystem/virtual/getRouteNodes.js";
9
9
  import { rootPathId } from "./filesystem/physical/rootPathId.js";
10
- import { multiSortBy, format, mergeImportDeclarations, buildImportString, replaceBackslash, removeExt, checkFileExists, resetRegex, hasParentRoute, determineNodePath, trimPathLeft, removeGroups, removeUnderscores, removeLayoutSegments, removeLastSegmentFromPath, routePathToVariable, buildRouteTreeConfig, findParent, createRouteNodesByFullPath, createRouteNodesByTo, createRouteNodesById, getResolvedRouteNodeVariableName, buildFileRoutesByPathInterface, lowerCaseFirstChar, isRouteNodeValidForAugmentation } from "./utils.js";
10
+ import { multiSortBy, format, getImportForRouteNode, isRouteNodeValidForAugmentation, buildRouteTreeConfig, findParent, replaceBackslash, removeExt, createRouteNodesByFullPath, createRouteNodesByTo, createRouteNodesById, getResolvedRouteNodeVariableName, buildFileRoutesByPathInterface, checkRouteFullPathUniqueness, mergeImportDeclarations, buildImportString, checkFileExists, resetRegex, hasParentRoute, determineNodePath, trimPathLeft, removeGroups, removeUnderscores, removeLayoutSegments, removeLastSegmentFromPath, routePathToVariable, getImportPath } from "./utils.js";
11
11
  import { getTargetTemplate, fillTemplate } from "./template.js";
12
12
  import { transform } from "./transform/transform.js";
13
- import { defaultGeneratorPlugin } from "./plugin/default-generator-plugin.js";
14
13
  const DefaultFileSystem = {
15
14
  stat: async (filePath) => {
16
15
  const res = await fsp.stat(filePath, { bigint: true });
@@ -49,15 +48,12 @@ function rerun(opts) {
49
48
  function isRerun(result) {
50
49
  return typeof result === "object" && result !== null && "rerun" in result && result.rerun === true;
51
50
  }
52
- class Generator {
51
+ const _Generator = class _Generator {
53
52
  constructor(opts) {
54
53
  this.routeNodeCache = /* @__PURE__ */ new Map();
55
54
  this.routeNodeShadowCache = /* @__PURE__ */ new Map();
56
55
  this.fileEventQueue = [];
57
- this.plugins = [defaultGeneratorPlugin()];
58
- this.pluginsWithTransform = [];
59
- this.transformPlugins = [];
60
- this.routeGroupPatternRegex = /\(.+\)/g;
56
+ this.plugins = [];
61
57
  this.physicalDirectories = [];
62
58
  this.config = opts.config;
63
59
  this.logger = logging({ disabled: this.config.disableLogging });
@@ -67,17 +63,9 @@ class Generator {
67
63
  this.targetTemplate = getTargetTemplate(this.config);
68
64
  this.routesDirectoryPath = this.getRoutesDirectoryPath();
69
65
  this.plugins.push(...opts.config.plugins || []);
70
- this.plugins.forEach((plugin) => {
71
- if ("transformPlugin" in plugin) {
72
- if (this.pluginsWithTransform.find((p) => p.name === plugin.name)) {
73
- throw new Error(
74
- `Plugin with name "${plugin.name}" is already registered for export ${plugin.transformPlugin.exportName}!`
75
- );
76
- }
77
- this.pluginsWithTransform.push(plugin);
78
- this.transformPlugins.push(plugin.transformPlugin);
79
- }
80
- });
66
+ for (const plugin of this.plugins) {
67
+ plugin.init?.({ generator: this });
68
+ }
81
69
  }
82
70
  getGeneratedRouteTreePath() {
83
71
  const generatedRouteTreePath = path.isAbsolute(
@@ -136,12 +124,7 @@ class Generator {
136
124
  break;
137
125
  }
138
126
  try {
139
- const start = performance.now();
140
127
  await this.generatorInternal();
141
- const end = performance.now();
142
- this.logger.info(
143
- `Generated route tree in ${Math.round(end - start)}ms`
144
- );
145
128
  } catch (err) {
146
129
  const errArray = !Array.isArray(err) ? [err] : err;
147
130
  const recoverableErrors = errArray.filter((e) => isRerun(e));
@@ -188,7 +171,7 @@ Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.dis
188
171
  throw new Error(errorMessage);
189
172
  }
190
173
  this.physicalDirectories = physicalDirectories;
191
- writeRouteTreeFile = await this.handleRootNode(rootRouteNode);
174
+ await this.handleRootNode(rootRouteNode);
192
175
  const preRouteNodes = multiSortBy(beforeRouteNodes, [
193
176
  (d) => d.routePath === "/" ? -1 : 1,
194
177
  (d) => d.routePath?.split("/").length,
@@ -211,20 +194,23 @@ Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.dis
211
194
  }
212
195
  const routeFileResult = routeFileAllResult.flatMap((result) => {
213
196
  if (result.status === "fulfilled" && result.value !== null) {
214
- return result.value;
197
+ if (result.value.shouldWriteTree) {
198
+ writeRouteTreeFile = true;
199
+ }
200
+ return result.value.node;
215
201
  }
216
202
  return [];
217
203
  });
218
- routeFileResult.forEach((result) => {
219
- if (!result.node.exports?.length) {
220
- this.logger.warn(
221
- `Route file "${result.cacheEntry.fileContent}" does not export any route piece. This is likely a mistake.`
222
- );
223
- }
224
- });
225
- if (routeFileResult.find((r) => r.shouldWriteTree)) {
226
- writeRouteTreeFile = true;
204
+ routeFileResult.forEach((r) => r.children = void 0);
205
+ const acc = {
206
+ routeTree: [],
207
+ routeNodes: [],
208
+ routePiecesByPath: {}
209
+ };
210
+ for (const node of routeFileResult) {
211
+ _Generator.handleNode(node, acc);
227
212
  }
213
+ this.crawlingResult = { rootRouteNode, routeFileResult, acc };
228
214
  if (!this.routeTreeFileCache) {
229
215
  const routeTreeFile = await this.fs.readFile(this.generatedRouteTreePath);
230
216
  if (routeTreeFile !== "file-not-existing") {
@@ -255,10 +241,14 @@ Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.dis
255
241
  }
256
242
  }
257
243
  if (!writeRouteTreeFile) {
258
- for (const fullPath of this.routeNodeCache.keys()) {
259
- if (!this.routeNodeShadowCache.has(fullPath)) {
260
- writeRouteTreeFile = true;
261
- break;
244
+ if (this.routeNodeCache.size !== this.routeNodeShadowCache.size) {
245
+ writeRouteTreeFile = true;
246
+ } else {
247
+ for (const fullPath of this.routeNodeCache.keys()) {
248
+ if (!this.routeNodeShadowCache.has(fullPath)) {
249
+ writeRouteTreeFile = true;
250
+ break;
251
+ }
262
252
  }
263
253
  }
264
254
  }
@@ -266,11 +256,12 @@ Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.dis
266
256
  this.swapCaches();
267
257
  return;
268
258
  }
269
- let routeTreeContent = this.buildRouteTreeFileContent(
259
+ const buildResult = this.buildRouteTree({
270
260
  rootRouteNode,
271
- preRouteNodes,
261
+ acc,
272
262
  routeFileResult
273
- );
263
+ });
264
+ let routeTreeContent = buildResult.routeTreeContent;
274
265
  routeTreeContent = this.config.enableRouteTreeFormatting ? await format(routeTreeContent, this.config) : routeTreeContent;
275
266
  let newMtimeMs;
276
267
  if (this.routeTreeFileCache) {
@@ -302,281 +293,258 @@ Add the file in: "${this.config.routesDirectory}/${rootPathId}.${this.config.dis
302
293
  mtimeMs: newMtimeMs
303
294
  };
304
295
  }
296
+ this.plugins.map((plugin) => {
297
+ return plugin.onRouteTreeChanged?.({
298
+ routeTree: buildResult.routeTree,
299
+ routeNodes: buildResult.routeNodes,
300
+ acc,
301
+ rootRouteNode
302
+ });
303
+ });
305
304
  this.swapCaches();
306
305
  }
307
306
  swapCaches() {
308
307
  this.routeNodeCache = this.routeNodeShadowCache;
309
308
  this.routeNodeShadowCache = /* @__PURE__ */ new Map();
310
309
  }
311
- buildRouteTreeFileContent(rootRouteNode, preRouteNodes, routeFileResult) {
312
- const getImportForRouteNode = (node, exportName) => {
313
- if (node.exports?.includes(exportName)) {
314
- return {
315
- source: `./${this.getImportPath(node)}`,
316
- specifiers: [
317
- {
318
- imported: exportName,
319
- local: `${node.variableName}${exportName}Import`
320
- }
321
- ]
322
- };
323
- }
324
- return void 0;
325
- };
326
- const buildRouteTreeForExport = (plugin) => {
327
- const exportName = plugin.transformPlugin.exportName;
328
- const acc = {
329
- routeTree: [],
330
- routeNodes: [],
331
- routePiecesByPath: {}
310
+ buildRouteTree(opts) {
311
+ const config = { ...this.config, ...opts.config || {} };
312
+ const { rootRouteNode, acc } = opts;
313
+ const sortedRouteNodes = multiSortBy(acc.routeNodes, [
314
+ (d) => d.routePath?.includes(`/${rootPathId}`) ? -1 : 1,
315
+ (d) => d.routePath?.split("/").length,
316
+ (d) => d.routePath?.endsWith(config.indexToken) ? -1 : 1,
317
+ (d) => d
318
+ ]);
319
+ const routeImports = sortedRouteNodes.filter((d) => !d.isVirtual).flatMap(
320
+ (node) => getImportForRouteNode(
321
+ node,
322
+ config,
323
+ this.generatedRouteTreePath,
324
+ this.root
325
+ )
326
+ );
327
+ const virtualRouteNodes = sortedRouteNodes.filter((d) => d.isVirtual).map((node) => {
328
+ return `const ${node.variableName}RouteImport = createFileRoute('${node.routePath}')()`;
329
+ });
330
+ const imports = [];
331
+ if (acc.routeNodes.some((n) => n.isVirtual)) {
332
+ imports.push({
333
+ specifiers: [{ imported: "createFileRoute" }],
334
+ source: this.targetTemplate.fullPkg
335
+ });
336
+ }
337
+ if (config.verboseFileRoutes === false) {
338
+ const typeImport = {
339
+ specifiers: [],
340
+ source: this.targetTemplate.fullPkg,
341
+ importKind: "type"
332
342
  };
333
- for (const node of preRouteNodes) {
334
- if (node.exports?.includes(plugin.transformPlugin.exportName)) {
335
- this.handleNode(node, acc);
336
- }
343
+ if (sortedRouteNodes.some(
344
+ (d) => isRouteNodeValidForAugmentation(d) && d._fsRouteType !== "lazy"
345
+ )) {
346
+ typeImport.specifiers.push({ imported: "CreateFileRoute" });
337
347
  }
338
- const sortedRouteNodes = multiSortBy(acc.routeNodes, [
339
- (d) => d.routePath?.includes(`/${rootPathId}`) ? -1 : 1,
340
- (d) => d.routePath?.split("/").length,
341
- (d) => d.routePath?.endsWith(this.config.indexToken) ? -1 : 1,
342
- (d) => d
343
- ]);
344
- const pluginConfig = plugin.config({
345
- generator: this,
346
- rootRouteNode,
347
- sortedRouteNodes
348
- });
349
- const routeImports2 = sortedRouteNodes.filter((d) => !d.isVirtual).flatMap((node) => getImportForRouteNode(node, exportName) ?? []);
350
- const hasMatchingRouteFiles = acc.routeNodes.length > 0 || rootRouteNode.exports?.includes(exportName);
351
- const virtualRouteNodes = sortedRouteNodes.filter((d) => d.isVirtual).map((node) => {
352
- return `const ${node.variableName}${exportName}Import = ${plugin.createVirtualRouteCode({ node })}`;
353
- });
354
- if (!rootRouteNode.exports?.includes(exportName) && pluginConfig.virtualRootRoute) {
355
- virtualRouteNodes.unshift(
356
- `const ${rootRouteNode.variableName}${exportName}Import = ${plugin.createRootRouteCode()}`
357
- );
348
+ if (sortedRouteNodes.some(
349
+ (node) => acc.routePiecesByPath[node.routePath]?.lazy && isRouteNodeValidForAugmentation(node)
350
+ )) {
351
+ typeImport.specifiers.push({ imported: "CreateLazyFileRoute" });
358
352
  }
359
- const imports = plugin.imports({
360
- sortedRouteNodes,
361
- acc,
362
- generator: this,
363
- rootRouteNode
364
- });
365
- const routeTreeConfig = buildRouteTreeConfig(
366
- acc.routeTree,
367
- exportName,
368
- this.config.disableTypes
369
- );
370
- const createUpdateRoutes = sortedRouteNodes.map((node) => {
371
- const loaderNode = acc.routePiecesByPath[node.routePath]?.loader;
372
- const componentNode = acc.routePiecesByPath[node.routePath]?.component;
373
- const errorComponentNode = acc.routePiecesByPath[node.routePath]?.errorComponent;
374
- const pendingComponentNode = acc.routePiecesByPath[node.routePath]?.pendingComponent;
375
- const lazyComponentNode = acc.routePiecesByPath[node.routePath]?.lazy;
376
- return [
377
- [
378
- `const ${node.variableName}${exportName} = ${node.variableName}${exportName}Import.update({
353
+ if (typeImport.specifiers.length > 0) {
354
+ typeImport.specifiers.push({ imported: "FileRoutesByPath" });
355
+ imports.push(typeImport);
356
+ }
357
+ }
358
+ const routeTreeConfig = buildRouteTreeConfig(
359
+ acc.routeTree,
360
+ config.disableTypes
361
+ );
362
+ const createUpdateRoutes = sortedRouteNodes.map((node) => {
363
+ const loaderNode = acc.routePiecesByPath[node.routePath]?.loader;
364
+ const componentNode = acc.routePiecesByPath[node.routePath]?.component;
365
+ const errorComponentNode = acc.routePiecesByPath[node.routePath]?.errorComponent;
366
+ const pendingComponentNode = acc.routePiecesByPath[node.routePath]?.pendingComponent;
367
+ const lazyComponentNode = acc.routePiecesByPath[node.routePath]?.lazy;
368
+ return [
369
+ [
370
+ `const ${node.variableName}Route = ${node.variableName}RouteImport.update({
379
371
  ${[
380
- `id: '${node.path}'`,
381
- !node.isNonPath ? `path: '${node.cleanedPath}'` : void 0,
382
- `getParentRoute: () => ${findParent(node, exportName)}`
383
- ].filter(Boolean).join(",")}
384
- }${this.config.disableTypes ? "" : "as any"})`,
385
- loaderNode ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
386
- removeExt(
387
- path.relative(
388
- path.dirname(this.config.generatedRouteTree),
389
- path.resolve(
390
- this.config.routesDirectory,
391
- loaderNode.filePath
392
- )
393
- ),
394
- this.config.addExtensions
395
- )
396
- )}'), 'loader') })` : "",
397
- componentNode || errorComponentNode || pendingComponentNode ? `.update({
372
+ `id: '${node.path}'`,
373
+ !node.isNonPath ? `path: '${node.cleanedPath}'` : void 0,
374
+ `getParentRoute: () => ${findParent(node)}`
375
+ ].filter(Boolean).join(",")}
376
+ }${config.disableTypes ? "" : "as any"})`,
377
+ loaderNode ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
378
+ removeExt(
379
+ path.relative(
380
+ path.dirname(config.generatedRouteTree),
381
+ path.resolve(config.routesDirectory, loaderNode.filePath)
382
+ ),
383
+ config.addExtensions
384
+ )
385
+ )}'), 'loader') })` : "",
386
+ componentNode || errorComponentNode || pendingComponentNode ? `.update({
398
387
  ${[
399
- ["component", componentNode],
400
- ["errorComponent", errorComponentNode],
401
- ["pendingComponent", pendingComponentNode]
402
- ].filter((d) => d[1]).map((d) => {
403
- return `${d[0]}: lazyRouteComponent(() => import('./${replaceBackslash(
404
- removeExt(
405
- path.relative(
406
- path.dirname(this.config.generatedRouteTree),
407
- path.resolve(
408
- this.config.routesDirectory,
409
- d[1].filePath
410
- )
411
- ),
412
- this.config.addExtensions
413
- )
414
- )}'), '${d[0]}')`;
415
- }).join("\n,")}
416
- })` : "",
417
- lazyComponentNode ? `.lazy(() => import('./${replaceBackslash(
388
+ ["component", componentNode],
389
+ ["errorComponent", errorComponentNode],
390
+ ["pendingComponent", pendingComponentNode]
391
+ ].filter((d) => d[1]).map((d) => {
392
+ return `${d[0]}: lazyRouteComponent(() => import('./${replaceBackslash(
418
393
  removeExt(
419
394
  path.relative(
420
- path.dirname(this.config.generatedRouteTree),
421
- path.resolve(
422
- this.config.routesDirectory,
423
- lazyComponentNode.filePath
424
- )
395
+ path.dirname(config.generatedRouteTree),
396
+ path.resolve(config.routesDirectory, d[1].filePath)
425
397
  ),
426
- this.config.addExtensions
398
+ config.addExtensions
427
399
  )
428
- )}').then((d) => d.${exportName}))` : ""
429
- ].join("")
430
- ].join("\n\n");
431
- });
432
- let fileRoutesByPathInterfacePerPlugin = "";
433
- let fileRoutesByFullPathPerPlugin = "";
434
- if (!this.config.disableTypes && hasMatchingRouteFiles) {
435
- fileRoutesByFullPathPerPlugin = [
436
- `export interface File${exportName}sByFullPath {
400
+ )}'), '${d[0]}')`;
401
+ }).join("\n,")}
402
+ })` : "",
403
+ lazyComponentNode ? `.lazy(() => import('./${replaceBackslash(
404
+ removeExt(
405
+ path.relative(
406
+ path.dirname(config.generatedRouteTree),
407
+ path.resolve(
408
+ config.routesDirectory,
409
+ lazyComponentNode.filePath
410
+ )
411
+ ),
412
+ config.addExtensions
413
+ )
414
+ )}').then((d) => d.Route))` : ""
415
+ ].join("")
416
+ ].join("\n\n");
417
+ });
418
+ let fileRoutesByPathInterface = "";
419
+ let fileRoutesByFullPath = "";
420
+ if (!config.disableTypes) {
421
+ fileRoutesByFullPath = [
422
+ `export interface FileRoutesByFullPath {
437
423
  ${[...createRouteNodesByFullPath(acc.routeNodes).entries()].filter(([fullPath]) => fullPath).map(([fullPath, routeNode]) => {
438
- return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}`;
439
- })}
424
+ return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
425
+ })}
440
426
  }`,
441
- `export interface File${exportName}sByTo {
427
+ `export interface FileRoutesByTo {
442
428
  ${[...createRouteNodesByTo(acc.routeNodes).entries()].filter(([to]) => to).map(([to, routeNode]) => {
443
- return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}`;
444
- })}
429
+ return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
430
+ })}
445
431
  }`,
446
- `export interface File${exportName}sById {
447
- '${rootRouteId}': typeof root${exportName}Import,
432
+ `export interface FileRoutesById {
433
+ '${rootRouteId}': typeof rootRouteImport,
448
434
  ${[...createRouteNodesById(acc.routeNodes).entries()].map(([id, routeNode]) => {
449
- return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}`;
450
- })}
435
+ return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`;
436
+ })}
451
437
  }`,
452
- `export interface File${exportName}Types {
453
- file${exportName}sByFullPath: File${exportName}sByFullPath
438
+ `export interface FileRouteTypes {
439
+ fileRoutesByFullPath: FileRoutesByFullPath
454
440
  fullPaths: ${acc.routeNodes.length > 0 ? [...createRouteNodesByFullPath(acc.routeNodes).keys()].filter((fullPath) => fullPath).map((fullPath) => `'${fullPath}'`).join("|") : "never"}
455
- file${exportName}sByTo: File${exportName}sByTo
441
+ fileRoutesByTo: FileRoutesByTo
456
442
  to: ${acc.routeNodes.length > 0 ? [...createRouteNodesByTo(acc.routeNodes).keys()].filter((to) => to).map((to) => `'${to}'`).join("|") : "never"}
457
443
  id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(acc.routeNodes).keys()].map((id) => `'${id}'`)].join("|")}
458
- file${exportName}sById: File${exportName}sById
444
+ fileRoutesById: FileRoutesById
459
445
  }`,
460
- `export interface Root${exportName}Children {
461
- ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${getResolvedRouteNodeVariableName(child, exportName)}`).join(",")}
446
+ `export interface RootRouteChildren {
447
+ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`).join(",")}
462
448
  }`
463
- ].join("\n");
464
- fileRoutesByPathInterfacePerPlugin = buildFileRoutesByPathInterface({
465
- ...plugin.moduleAugmentation({ generator: this }),
466
- routeNodes: this.config.verboseFileRoutes !== false ? sortedRouteNodes : [
467
- ...routeFileResult.map(({ node }) => node),
468
- ...sortedRouteNodes.filter((d) => d.isVirtual)
469
- ],
470
- exportName
471
- });
472
- }
473
- let routeTree = "";
474
- if (hasMatchingRouteFiles) {
475
- routeTree = [
476
- `const root${exportName}Children${this.config.disableTypes ? "" : `: Root${exportName}Children`} = {
449
+ ].join("\n");
450
+ fileRoutesByPathInterface = buildFileRoutesByPathInterface({
451
+ module: this.targetTemplate.fullPkg,
452
+ interfaceName: "FileRoutesByPath",
453
+ routeNodes: sortedRouteNodes
454
+ });
455
+ }
456
+ const routeTree = [
457
+ `const rootRouteChildren${config.disableTypes ? "" : `: RootRouteChildren`} = {
477
458
  ${acc.routeTree.map(
478
- (child) => `${child.variableName}${exportName}: ${getResolvedRouteNodeVariableName(child, exportName)}`
479
- ).join(",")}
459
+ (child) => `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`
460
+ ).join(",")}
480
461
  }`,
481
- `export const ${lowerCaseFirstChar(exportName)}Tree = root${exportName}Import._addFileChildren(root${exportName}Children)${this.config.disableTypes ? "" : `._addFileTypes<File${exportName}Types>()`}`
482
- ].join("\n");
483
- }
484
- return {
485
- routeImports: routeImports2,
486
- sortedRouteNodes,
487
- acc,
488
- virtualRouteNodes,
489
- routeTreeConfig,
490
- routeTree,
491
- imports,
492
- createUpdateRoutes,
493
- fileRoutesByFullPathPerPlugin,
494
- fileRoutesByPathInterfacePerPlugin
495
- };
496
- };
497
- const routeTrees = this.pluginsWithTransform.map((plugin) => ({
498
- exportName: plugin.transformPlugin.exportName,
499
- ...buildRouteTreeForExport(plugin)
500
- }));
501
- this.plugins.map((plugin) => {
502
- return plugin.onRouteTreesChanged?.({
503
- routeTrees,
504
- rootRouteNode,
505
- generator: this
506
- });
507
- });
508
- let mergedImports = mergeImportDeclarations(
509
- routeTrees.flatMap((d) => d.imports)
462
+ `export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)${config.disableTypes ? "" : `._addFileTypes<FileRouteTypes>()`}`
463
+ ].join("\n");
464
+ checkRouteFullPathUniqueness(
465
+ sortedRouteNodes.filter(
466
+ (d) => d.children === void 0 && "lazy" !== d._fsRouteType
467
+ ),
468
+ config
510
469
  );
511
- if (this.config.disableTypes) {
470
+ let mergedImports = mergeImportDeclarations(imports);
471
+ if (config.disableTypes) {
512
472
  mergedImports = mergedImports.filter((d) => d.importKind !== "type");
513
473
  }
514
474
  const importStatements = mergedImports.map(buildImportString);
515
475
  let moduleAugmentation = "";
516
- if (this.config.verboseFileRoutes === false && !this.config.disableTypes) {
517
- moduleAugmentation = routeFileResult.map(({ node }) => {
476
+ if (config.verboseFileRoutes === false && !config.disableTypes) {
477
+ moduleAugmentation = opts.routeFileResult.map((node) => {
518
478
  const getModuleDeclaration = (routeNode) => {
519
479
  if (!isRouteNodeValidForAugmentation(routeNode)) {
520
480
  return "";
521
481
  }
522
- const moduleAugmentation2 = this.pluginsWithTransform.map((plugin) => {
523
- return plugin.routeModuleAugmentation({
524
- routeNode
525
- });
526
- }).filter(Boolean).join("\n");
527
- return `declare module './${this.getImportPath(routeNode)}' {
482
+ let moduleAugmentation2 = "";
483
+ if (routeNode._fsRouteType === "lazy") {
484
+ moduleAugmentation2 = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>`;
485
+ } else {
486
+ moduleAugmentation2 = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}',
487
+ FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
488
+ FileRoutesByPath['${routeNode.routePath}']['id'],
489
+ FileRoutesByPath['${routeNode.routePath}']['path'],
490
+ FileRoutesByPath['${routeNode.routePath}']['fullPath']
491
+ >
492
+ `;
493
+ }
494
+ return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' {
528
495
  ${moduleAugmentation2}
529
496
  }`;
530
497
  };
531
498
  return getModuleDeclaration(node);
532
499
  }).join("\n");
533
500
  }
534
- const routeImports = routeTrees.flatMap((t) => t.routeImports);
535
- const rootRouteImports = this.pluginsWithTransform.flatMap(
536
- (p) => getImportForRouteNode(rootRouteNode, p.transformPlugin.exportName) ?? []
501
+ const rootRouteImport = getImportForRouteNode(
502
+ rootRouteNode,
503
+ config,
504
+ this.generatedRouteTreePath,
505
+ this.root
537
506
  );
538
- if (rootRouteImports.length > 0) {
539
- routeImports.unshift(...rootRouteImports);
507
+ routeImports.unshift(rootRouteImport);
508
+ let footer = [];
509
+ if (config.routeTreeFileFooter) {
510
+ if (Array.isArray(config.routeTreeFileFooter)) {
511
+ footer = config.routeTreeFileFooter;
512
+ } else {
513
+ footer = config.routeTreeFileFooter();
514
+ }
540
515
  }
541
516
  const routeTreeContent = [
542
- ...this.config.routeTreeFileHeader,
517
+ ...config.routeTreeFileHeader,
543
518
  `// This file was automatically generated by TanStack Router.
544
519
  // You should NOT make any changes in this file as it will be overwritten.
545
520
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
546
521
  [...importStatements].join("\n"),
547
522
  mergeImportDeclarations(routeImports).map(buildImportString).join("\n"),
548
- routeTrees.flatMap((t) => t.virtualRouteNodes).join("\n"),
549
- routeTrees.flatMap((t) => t.createUpdateRoutes).join("\n"),
550
- routeTrees.map((t) => t.fileRoutesByFullPathPerPlugin).join("\n"),
551
- routeTrees.map((t) => t.fileRoutesByPathInterfacePerPlugin).join("\n"),
523
+ virtualRouteNodes.join("\n"),
524
+ createUpdateRoutes.join("\n"),
525
+ fileRoutesByFullPath,
526
+ fileRoutesByPathInterface,
552
527
  moduleAugmentation,
553
- routeTrees.flatMap((t) => t.routeTreeConfig).join("\n"),
554
- routeTrees.map((t) => t.routeTree).join("\n"),
555
- ...this.config.routeTreeFileFooter
528
+ routeTreeConfig.join("\n"),
529
+ routeTree,
530
+ ...footer
556
531
  ].filter(Boolean).join("\n\n");
557
- return routeTreeContent;
558
- }
559
- getImportPath(node) {
560
- return replaceBackslash(
561
- removeExt(
562
- path.relative(
563
- path.dirname(this.config.generatedRouteTree),
564
- path.resolve(this.config.routesDirectory, node.filePath)
565
- ),
566
- this.config.addExtensions
567
- )
568
- );
532
+ return {
533
+ routeTreeContent,
534
+ routeTree: acc.routeTree,
535
+ routeNodes: acc.routeNodes
536
+ };
569
537
  }
570
538
  async processRouteNodeFile(node) {
571
539
  const result = await this.isRouteFileCacheFresh(node);
572
540
  if (result.status === "fresh") {
573
- node.exports = result.cacheEntry.exports;
574
541
  return {
575
- node,
576
- shouldWriteTree: result.exportsChanged,
542
+ node: result.cacheEntry.node,
543
+ shouldWriteTree: false,
577
544
  cacheEntry: result.cacheEntry
578
545
  };
579
546
  }
547
+ const previousCacheEntry = result.cacheEntry;
580
548
  const existingRouteFile = await this.fs.readFile(node.fullPath);
581
549
  if (existingRouteFile === "file-not-existing") {
582
550
  throw new Error(`⚠️ File ${node.fullPath} does not exist`);
@@ -584,13 +552,15 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
584
552
  const updatedCacheEntry = {
585
553
  fileContent: existingRouteFile.fileContent,
586
554
  mtimeMs: existingRouteFile.stat.mtimeMs,
587
- exports: [],
588
- routeId: node.routePath ?? "$$TSR_NO_ROUTE_PATH_ASSIGNED$$"
555
+ routeId: node.routePath ?? "$$TSR_NO_ROUTE_PATH_ASSIGNED$$",
556
+ node
589
557
  };
590
558
  const escapedRoutePath = node.routePath?.replaceAll("$", "$$") ?? "";
591
559
  let shouldWriteRouteFile = false;
560
+ let shouldWriteTree = false;
592
561
  if (!existingRouteFile.fileContent) {
593
562
  shouldWriteRouteFile = true;
563
+ shouldWriteTree = true;
594
564
  if (node._fsRouteType === "lazy") {
595
565
  const tLazyRouteTemplate = this.targetTemplate.lazyRoute;
596
566
  updatedCacheEntry.fileContent = await fillTemplate(
@@ -603,7 +573,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
603
573
  tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd()
604
574
  }
605
575
  );
606
- updatedCacheEntry.exports = ["Route"];
607
576
  } else if (
608
577
  // Creating a new normal route file
609
578
  ["layout", "static"].some(
@@ -626,31 +595,37 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
626
595
  tsrExportEnd: tRouteTemplate.imports.tsrExportEnd()
627
596
  }
628
597
  );
629
- updatedCacheEntry.exports = ["Route"];
630
598
  } else {
631
599
  return null;
632
600
  }
633
- } else {
634
- const transformResult = await transform({
635
- source: updatedCacheEntry.fileContent,
636
- ctx: {
637
- target: this.config.target,
638
- routeId: escapedRoutePath,
639
- lazy: node._fsRouteType === "lazy",
640
- verboseFileRoutes: !(this.config.verboseFileRoutes === false)
641
- },
642
- plugins: this.transformPlugins
643
- });
644
- if (transformResult.result === "error") {
645
- throw new Error(
646
- `Error transforming route file ${node.fullPath}: ${transformResult.error}`
647
- );
648
- }
649
- updatedCacheEntry.exports = transformResult.exports;
650
- if (transformResult.result === "modified") {
651
- updatedCacheEntry.fileContent = transformResult.output;
652
- shouldWriteRouteFile = true;
653
- }
601
+ }
602
+ const transformResult = await transform({
603
+ source: updatedCacheEntry.fileContent,
604
+ ctx: {
605
+ target: this.config.target,
606
+ routeId: escapedRoutePath,
607
+ lazy: node._fsRouteType === "lazy",
608
+ verboseFileRoutes: !(this.config.verboseFileRoutes === false)
609
+ },
610
+ node
611
+ });
612
+ if (transformResult.result === "no-route-export") {
613
+ this.logger.warn(
614
+ `Route file "${node.fullPath}" does not contain any route piece. This is likely a mistake.`
615
+ );
616
+ return null;
617
+ }
618
+ if (transformResult.result === "error") {
619
+ throw new Error(
620
+ `Error transforming route file ${node.fullPath}: ${transformResult.error}`
621
+ );
622
+ }
623
+ if (transformResult.result === "modified") {
624
+ updatedCacheEntry.fileContent = transformResult.output;
625
+ shouldWriteRouteFile = true;
626
+ }
627
+ for (const plugin of this.plugins) {
628
+ plugin.afterTransform?.({ node, prevNode: previousCacheEntry?.node });
654
629
  }
655
630
  if (shouldWriteRouteFile) {
656
631
  const stats = await this.safeFileWrite({
@@ -664,11 +639,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
664
639
  updatedCacheEntry.mtimeMs = stats.mtimeMs;
665
640
  }
666
641
  this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
667
- node.exports = updatedCacheEntry.exports;
668
- const shouldWriteTree = !deepEqual(
669
- result.cacheEntry?.exports,
670
- updatedCacheEntry.exports
671
- );
672
642
  return {
673
643
  node,
674
644
  shouldWriteTree,
@@ -752,7 +722,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
752
722
  this.routeNodeShadowCache.set(node.fullPath, fileChangedCache.cacheEntry);
753
723
  return {
754
724
  status: "fresh",
755
- exportsChanged: false,
756
725
  cacheEntry: fileChangedCache.cacheEntry
757
726
  };
758
727
  }
@@ -769,19 +738,8 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
769
738
  }
770
739
  if (shadowCacheFileChange.result === false) {
771
740
  if (fileChangedCache.result === true) {
772
- if (deepEqual(
773
- fileChangedCache.cacheEntry.exports,
774
- shadowCacheFileChange.cacheEntry.exports
775
- )) {
776
- return {
777
- status: "fresh",
778
- exportsChanged: false,
779
- cacheEntry: shadowCacheFileChange.cacheEntry
780
- };
781
- }
782
741
  return {
783
742
  status: "fresh",
784
- exportsChanged: true,
785
743
  cacheEntry: shadowCacheFileChange.cacheEntry
786
744
  };
787
745
  }
@@ -796,9 +754,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
796
754
  async handleRootNode(node) {
797
755
  const result = await this.isRouteFileCacheFresh(node);
798
756
  if (result.status === "fresh") {
799
- node.exports = result.cacheEntry.exports;
800
757
  this.routeNodeShadowCache.set(node.fullPath, result.cacheEntry);
801
- return result.exportsChanged;
802
758
  }
803
759
  const rootNodeFile = await this.fs.readFile(node.fullPath);
804
760
  if (rootNodeFile === "file-not-existing") {
@@ -807,8 +763,8 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
807
763
  const updatedCacheEntry = {
808
764
  fileContent: rootNodeFile.fileContent,
809
765
  mtimeMs: rootNodeFile.stat.mtimeMs,
810
- exports: [],
811
- routeId: node.routePath ?? "$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$"
766
+ routeId: node.routePath ?? "$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$",
767
+ node
812
768
  };
813
769
  if (!rootNodeFile.fileContent) {
814
770
  const rootTemplate = this.targetTemplate.rootRoute;
@@ -834,23 +790,13 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
834
790
  updatedCacheEntry.fileContent = rootRouteContent;
835
791
  updatedCacheEntry.mtimeMs = stats.mtimeMs;
836
792
  }
837
- const rootRouteExports = [];
838
- for (const plugin of this.pluginsWithTransform) {
839
- const exportName = plugin.transformPlugin.exportName;
840
- if (rootNodeFile.fileContent.includes(`export const ${exportName}`)) {
841
- rootRouteExports.push(exportName);
842
- }
843
- }
844
- updatedCacheEntry.exports = rootRouteExports;
845
- node.exports = rootRouteExports;
846
793
  this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry);
847
- const shouldWriteTree = !deepEqual(
848
- result.cacheEntry?.exports,
849
- rootRouteExports
850
- );
851
- return shouldWriteTree;
852
794
  }
853
- handleNode(node, acc) {
795
+ async getCrawlingResult() {
796
+ await this.runPromise;
797
+ return this.crawlingResult;
798
+ }
799
+ static handleNode(node, acc) {
854
800
  resetRegex(this.routeGroupPatternRegex);
855
801
  let parentRoute = hasParentRoute(acc.routeNodes, node, node.routePath);
856
802
  if (parentRoute?.isVirtualParentRoute && parentRoute.children?.length) {
@@ -964,7 +910,9 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get
964
910
  }
965
911
  return false;
966
912
  }
967
- }
913
+ };
914
+ _Generator.routeGroupPatternRegex = /\(.+\)/g;
915
+ let Generator = _Generator;
968
916
  export {
969
917
  Generator
970
918
  };