@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/dist/cjs/config.cjs +10 -6
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/config.d.cts +1 -1
- package/dist/cjs/generator.cjs +96 -40
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/generator.d.cts +1 -0
- package/dist/esm/config.d.ts +1 -1
- package/dist/esm/config.js +10 -6
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/generator.d.ts +1 -0
- package/dist/esm/generator.js +95 -40
- package/dist/esm/generator.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +13 -7
- package/src/generator.ts +135 -61
package/src/generator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import * as fs from 'fs
|
|
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
|
|
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.
|
|
202
|
-
? '
|
|
203
|
-
: node.
|
|
204
|
-
? '
|
|
205
|
-
: node.
|
|
206
|
-
? '
|
|
207
|
-
:
|
|
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
|
-
|
|
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 =
|
|
394
|
+
}Import = createFileRoute('${removeTrailingUnderscores(
|
|
341
395
|
node.routePath,
|
|
342
|
-
)}')
|
|
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 ||
|
|
431
|
+
componentNode ||
|
|
432
|
+
errorComponentNode ||
|
|
433
|
+
pendingComponentNode ||
|
|
434
|
+
lazyComponentNode
|
|
377
435
|
? `.update({
|
|
378
|
-
${
|
|
379
|
-
|
|
380
|
-
[
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
path.
|
|
393
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
523
|
+
await fsp.mkdir(path.dirname(path.resolve(config.generatedRouteTree)), {
|
|
450
524
|
recursive: true,
|
|
451
525
|
})
|
|
452
526
|
if (!checkLatest()) return
|
|
453
|
-
await
|
|
527
|
+
await fsp.writeFile(
|
|
454
528
|
path.resolve(config.generatedRouteTree),
|
|
455
529
|
routeConfigFileContent,
|
|
456
530
|
)
|