@tanstack/router-generator 1.8.2 → 1.10.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.
package/src/generator.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'path'
2
- import * as fs from 'fs/promises'
2
+ import * as fs from 'fs'
3
+ import * as fsp from 'fs/promises'
3
4
  import * as prettier from 'prettier'
4
5
  import { Config } from './config'
5
6
  import { cleanPath, trimPathLeft } from './utils'
@@ -22,6 +23,7 @@ export type RouteNode = {
22
23
  isErrorComponent?: boolean
23
24
  isPendingComponent?: boolean
24
25
  isVirtual?: boolean
26
+ isLazy?: boolean
25
27
  isRoot?: boolean
26
28
  children?: RouteNode[]
27
29
  parent?: RouteNode
@@ -34,7 +36,7 @@ async function getRouteNodes(config: Config) {
34
36
 
35
37
  async function recurse(dir: string) {
36
38
  const fullDir = path.resolve(config.routesDirectory, dir)
37
- let dirList = await fs.readdir(fullDir, { withFileTypes: true })
39
+ let dirList = await fsp.readdir(fullDir, { withFileTypes: true })
38
40
 
39
41
  dirList = dirList.filter((d) => {
40
42
  if (
@@ -73,9 +75,25 @@ async function getRouteNodes(config: Config) {
73
75
  let isErrorComponent = routePath?.endsWith('/errorComponent')
74
76
  let isPendingComponent = routePath?.endsWith('/pendingComponent')
75
77
  let isLoader = routePath?.endsWith('/loader')
78
+ let isLazy = routePath?.endsWith('/lazy')
79
+
80
+ ;(
81
+ [
82
+ [isComponent, 'component'],
83
+ [isErrorComponent, 'errorComponent'],
84
+ [isPendingComponent, 'pendingComponent'],
85
+ [isLoader, 'loader'],
86
+ ] as const
87
+ ).forEach(([isType, type]) => {
88
+ if (isType) {
89
+ console.warn(
90
+ `WARNING: The \`.${type}.tsx\` suffix used for the ${filePath} file is deprecated. Use the new \`.lazy.tsx\` suffix instead.`,
91
+ )
92
+ }
93
+ })
76
94
 
77
95
  routePath = routePath?.replace(
78
- /\/(component|errorComponent|pendingComponent|loader|route)$/,
96
+ /\/(component|errorComponent|pendingComponent|loader|route|lazy)$/,
79
97
  '',
80
98
  )
81
99
 
@@ -95,6 +113,7 @@ async function getRouteNodes(config: Config) {
95
113
  isErrorComponent,
96
114
  isPendingComponent,
97
115
  isLoader,
116
+ isLazy,
98
117
  })
99
118
  }
100
119
  }),
@@ -116,6 +135,7 @@ type RouteSubNode = {
116
135
  errorComponent?: RouteNode
117
136
  pendingComponent?: RouteNode
118
137
  loader?: RouteNode
138
+ lazy?: RouteNode
119
139
  }
120
140
 
121
141
  export async function generator(config: Config) {
@@ -151,7 +171,7 @@ export async function generator(config: Config) {
151
171
  (d) => (d.filePath?.match(/[./]index[.]/) ? 1 : -1),
152
172
  (d) =>
153
173
  d.filePath?.match(
154
- /[./](component|errorComponent|pendingComponent|loader)[.]/,
174
+ /[./](component|errorComponent|pendingComponent|loader|lazy)[.]/,
155
175
  )
156
176
  ? 1
157
177
  : -1,
@@ -169,7 +189,7 @@ export async function generator(config: Config) {
169
189
  // build up a tree based on the routeNodes' routePath
170
190
  let routeNodes: RouteNode[] = []
171
191
 
172
- const handleNode = (node: RouteNode) => {
192
+ const handleNode = async (node: RouteNode) => {
173
193
  const parentRoute = hasParentRoute(routeNodes, node.routePath)
174
194
  if (parentRoute) node.parent = parentRoute
175
195
 
@@ -184,27 +204,81 @@ export async function generator(config: Config) {
184
204
 
185
205
  node.isNonPath = first.startsWith('_')
186
206
  node.isNonLayout = first.endsWith('_')
187
-
188
207
  node.cleanedPath = removeUnderscores(node.path) ?? ''
189
208
 
209
+ // Ensure the boilerplate for the route exists
210
+ const routeCode = fs.readFileSync(node.fullPath, 'utf-8')
211
+
212
+ const escapedRoutePath = removeTrailingUnderscores(
213
+ node.routePath?.replaceAll('$', '$$') ?? '',
214
+ )
215
+
216
+ let replaced = routeCode
217
+
218
+ if (!routeCode) {
219
+ if (node.isLazy) {
220
+ replaced = [
221
+ `import { createLazyFileRoute } from '@tanstack/react-router'`,
222
+ `export const Route = createLazyFileRoute('${escapedRoutePath}')({
223
+ component: () => <div>Hello ${escapedRoutePath}!</div>
224
+ })`,
225
+ ].join('\n\n')
226
+ } else if (
227
+ node.isRoute ||
228
+ (!node.isComponent &&
229
+ !node.isErrorComponent &&
230
+ !node.isPendingComponent &&
231
+ !node.isLoader)
232
+ ) {
233
+ replaced = [
234
+ `import { createFileRoute } from '@tanstack/react-router'`,
235
+ `export const Route = createFileRoute('${escapedRoutePath}')({
236
+ component: () => <div>Hello ${escapedRoutePath}!</div>
237
+ })`,
238
+ ].join('\n\n')
239
+ }
240
+ } else {
241
+ replaced = routeCode
242
+ .replace(
243
+ /(FileRoute\(\s*['"])([^\s]+)(['"](?:,?)\s*\))/g,
244
+ (match, p1, p2, p3) => `${p1}${escapedRoutePath}${p3}`,
245
+ )
246
+ .replace(
247
+ /(createFileRoute\(\s*['"])([^\s]+)(['"](?:,?)\s*\))/g,
248
+ (match, p1, p2, p3) => `${p1}${escapedRoutePath}${p3}`,
249
+ )
250
+ .replace(
251
+ /(createLazyFileRoute\(\s*['"])([^\s]+)(['"](?:,?)\s*\))/g,
252
+ (match, p1, p2, p3) => `${p1}${escapedRoutePath}${p3}`,
253
+ )
254
+ }
255
+
256
+ if (replaced !== routeCode) {
257
+ console.log(`🌲 Updating ${node.fullPath}`)
258
+ await fsp.writeFile(node.fullPath, replaced)
259
+ }
260
+
190
261
  if (
191
262
  !node.isVirtual &&
192
263
  (node.isLoader ||
193
264
  node.isComponent ||
194
265
  node.isErrorComponent ||
195
- node.isPendingComponent)
266
+ node.isPendingComponent ||
267
+ node.isLazy)
196
268
  ) {
197
269
  routePiecesByPath[node.routePath!] =
198
270
  routePiecesByPath[node.routePath!] || {}
199
271
 
200
272
  routePiecesByPath[node.routePath!]![
201
- node.isLoader
202
- ? 'loader'
203
- : node.isErrorComponent
204
- ? 'errorComponent'
205
- : node.isPendingComponent
206
- ? 'pendingComponent'
207
- : 'component'
273
+ node.isLazy
274
+ ? 'lazy'
275
+ : node.isLoader
276
+ ? 'loader'
277
+ : node.isErrorComponent
278
+ ? 'errorComponent'
279
+ : node.isPendingComponent
280
+ ? 'pendingComponent'
281
+ : 'component'
208
282
  ] = node
209
283
 
210
284
  const anchorRoute = routeNodes.find((d) => d.routePath === node.routePath)
@@ -213,6 +287,7 @@ export async function generator(config: Config) {
213
287
  handleNode({
214
288
  ...node,
215
289
  isVirtual: true,
290
+ isLazy: false,
216
291
  isLoader: false,
217
292
  isComponent: false,
218
293
  isErrorComponent: false,
@@ -239,31 +314,10 @@ export async function generator(config: Config) {
239
314
  depth = 1,
240
315
  ): Promise<string> {
241
316
  const children = nodes.map(async (node) => {
242
- const routeCode = await fs.readFile(node.fullPath, 'utf-8')
243
-
244
- // Ensure the boilerplate for the route exists
245
317
  if (node.isRoot) {
246
318
  return
247
319
  }
248
320
 
249
- // Ensure that new FileRoute(anything?) is replaced with FileRoute(${node.routePath})
250
- // routePath can contain $ characters, which have special meaning when used in replace
251
- // so we have to escape it by turning all $ into $$. But since we do it through a replace call
252
- // we have to double escape it into $$$$. For more information, see
253
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
254
- const escapedRoutePath = removeTrailingUnderscores(
255
- node.routePath?.replaceAll('$', '$$') ?? '',
256
- )
257
- const quote = config.quoteStyle === 'single' ? `'` : `"`
258
- const replaced = routeCode.replace(
259
- /(FileRoute\(\s*['"])([^\s]+)(['"](?:,?)\s*\))/g,
260
- (match, p1, p2, p3) => `${p1}${escapedRoutePath}${p3}`,
261
- )
262
-
263
- if (replaced !== routeCode) {
264
- await fs.writeFile(node.fullPath, replaced)
265
- }
266
-
267
321
  const route = `${node.variableName}Route`
268
322
 
269
323
  if (node.children?.length) {
@@ -288,7 +342,7 @@ export async function generator(config: Config) {
288
342
  ])
289
343
 
290
344
  const imports = Object.entries({
291
- FileRoute: sortedRouteNodes.some((d) => d.isVirtual),
345
+ createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
292
346
  lazyFn: sortedRouteNodes.some(
293
347
  (node) => routePiecesByPath[node.routePath!]?.loader,
294
348
  ),
@@ -337,9 +391,9 @@ export async function generator(config: Config) {
337
391
  .map((node) => {
338
392
  return `const ${
339
393
  node.variableName
340
- }Import = new FileRoute('${removeTrailingUnderscores(
394
+ }Import = createFileRoute('${removeTrailingUnderscores(
341
395
  node.routePath,
342
- )}').createRoute()`
396
+ )}')()`
343
397
  })
344
398
  .join('\n'),
345
399
  '// Create/Update Routes',
@@ -351,6 +405,7 @@ export async function generator(config: Config) {
351
405
  routePiecesByPath[node.routePath!]?.errorComponent
352
406
  const pendingComponentNode =
353
407
  routePiecesByPath[node.routePath!]?.pendingComponent
408
+ const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
354
409
 
355
410
  return [
356
411
  `const ${node.variableName}Route = ${node.variableName}Import.update({
@@ -373,28 +428,47 @@ export async function generator(config: Config) {
373
428
  ),
374
429
  )}'), 'loader') })`
375
430
  : '',
376
- componentNode || errorComponentNode || pendingComponentNode
431
+ componentNode ||
432
+ errorComponentNode ||
433
+ pendingComponentNode ||
434
+ lazyComponentNode
377
435
  ? `.update({
378
- ${(
379
- [
380
- ['component', componentNode],
381
- ['errorComponent', errorComponentNode],
382
- ['pendingComponent', pendingComponentNode],
383
- ] as const
384
- )
385
- .filter((d) => d[1])
386
- .map((d) => {
387
- return `${
388
- d[0]
389
- }: lazyRouteComponent(() => import('./${replaceBackslash(
390
- removeExt(
391
- path.relative(
392
- path.dirname(config.generatedRouteTree),
393
- path.resolve(config.routesDirectory, d[1]!.filePath),
436
+ ${[
437
+ ...(
438
+ [
439
+ ['component', componentNode],
440
+ ['errorComponent', errorComponentNode],
441
+ ['pendingComponent', pendingComponentNode],
442
+ ] as const
443
+ )
444
+ .filter((d) => d[1])
445
+ .map((d) => {
446
+ return `${
447
+ d[0]
448
+ }: lazyRouteComponent(() => import('./${replaceBackslash(
449
+ removeExt(
450
+ path.relative(
451
+ path.dirname(config.generatedRouteTree),
452
+ path.resolve(config.routesDirectory, d[1]!.filePath),
453
+ ),
454
+ ),
455
+ )}'), '${d[0]}')`
456
+ }),
457
+ lazyComponentNode
458
+ ? `lazy: () => import('./${replaceBackslash(
459
+ removeExt(
460
+ path.relative(
461
+ path.dirname(config.generatedRouteTree),
462
+ path.resolve(
463
+ config.routesDirectory,
464
+ lazyComponentNode!.filePath,
465
+ ),
466
+ ),
394
467
  ),
395
- ),
396
- )}'), '${d[0]}')`
397
- })
468
+ )}').then((d) => d.Route)`
469
+ : '',
470
+ ]
471
+ .filter(Boolean)
398
472
  .join('\n,')}
399
473
  })`
400
474
  : '',
@@ -434,7 +508,7 @@ export async function generator(config: Config) {
434
508
  parser: 'typescript',
435
509
  })
436
510
 
437
- const routeTreeContent = await fs
511
+ const routeTreeContent = await fsp
438
512
  .readFile(path.resolve(config.generatedRouteTree), 'utf-8')
439
513
  .catch((err: any) => {
440
514
  if (err.code === 'ENOENT') {
@@ -446,11 +520,11 @@ export async function generator(config: Config) {
446
520
  if (!checkLatest()) return
447
521
 
448
522
  if (routeTreeContent !== routeConfigFileContent) {
449
- await fs.mkdir(path.dirname(path.resolve(config.generatedRouteTree)), {
523
+ await fsp.mkdir(path.dirname(path.resolve(config.generatedRouteTree)), {
450
524
  recursive: true,
451
525
  })
452
526
  if (!checkLatest()) return
453
- await fs.writeFile(
527
+ await fsp.writeFile(
454
528
  path.resolve(config.generatedRouteTree),
455
529
  routeConfigFileContent,
456
530
  )