@tanstack/router-plugin 1.99.14 → 1.101.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/core/code-splitter/compilers.cjs +187 -34
- package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
- package/dist/cjs/core/code-splitter/compilers.d.cts +14 -2
- package/dist/cjs/core/code-splitter/path-ids.cjs +37 -0
- package/dist/cjs/core/code-splitter/path-ids.cjs.map +1 -0
- package/dist/cjs/core/code-splitter/path-ids.d.cts +2 -0
- package/dist/cjs/core/config.cjs +33 -1
- package/dist/cjs/core/config.cjs.map +1 -1
- package/dist/cjs/core/config.d.cts +24 -0
- package/dist/cjs/core/constants.cjs +17 -2
- package/dist/cjs/core/constants.cjs.map +1 -1
- package/dist/cjs/core/constants.d.cts +5 -1
- package/dist/cjs/core/router-code-splitter-plugin.cjs +69 -16
- package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
- package/dist/cjs/esbuild.d.cts +3 -0
- package/dist/cjs/rspack.d.cts +3 -0
- package/dist/cjs/vite.d.cts +3 -0
- package/dist/cjs/webpack.d.cts +3 -0
- package/dist/esm/core/code-splitter/compilers.d.ts +14 -2
- package/dist/esm/core/code-splitter/compilers.js +189 -36
- package/dist/esm/core/code-splitter/compilers.js.map +1 -1
- package/dist/esm/core/code-splitter/path-ids.d.ts +2 -0
- package/dist/esm/core/code-splitter/path-ids.js +37 -0
- package/dist/esm/core/code-splitter/path-ids.js.map +1 -0
- package/dist/esm/core/config.d.ts +24 -0
- package/dist/esm/core/config.js +34 -2
- package/dist/esm/core/config.js.map +1 -1
- package/dist/esm/core/constants.d.ts +5 -1
- package/dist/esm/core/constants.js +17 -2
- package/dist/esm/core/constants.js.map +1 -1
- package/dist/esm/core/router-code-splitter-plugin.js +72 -19
- package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
- package/dist/esm/esbuild.d.ts +3 -0
- package/dist/esm/rspack.d.ts +3 -0
- package/dist/esm/vite.d.ts +3 -0
- package/dist/esm/webpack.d.ts +3 -0
- package/package.json +3 -3
- package/src/core/code-splitter/compilers.ts +230 -46
- package/src/core/code-splitter/path-ids.ts +39 -0
- package/src/core/config.ts +63 -0
- package/src/core/constants.ts +18 -1
- package/src/core/router-code-splitter-plugin.ts +104 -20
|
@@ -3,9 +3,12 @@ import babel from '@babel/core'
|
|
|
3
3
|
import * as template from '@babel/template'
|
|
4
4
|
import { deadCodeElimination } from 'babel-dead-code-elimination'
|
|
5
5
|
import { generateFromAst, parseAst } from '@tanstack/router-utils'
|
|
6
|
-
import {
|
|
6
|
+
import { tsrSplit } from '../constants'
|
|
7
|
+
import { createIdentifier } from './path-ids'
|
|
7
8
|
import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
|
|
9
|
+
import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants'
|
|
8
10
|
|
|
11
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
9
12
|
const debug = process.env.TSR_VITE_DEBUG
|
|
10
13
|
|
|
11
14
|
type SplitModulesById = Record<
|
|
@@ -26,17 +29,6 @@ interface State {
|
|
|
26
29
|
splitModulesById: SplitModulesById
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
function addSplitSearchParamToFilename(filename: string) {
|
|
30
|
-
const [bareFilename] = filename.split('?')
|
|
31
|
-
return `${bareFilename}?${splitPrefix}`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function removeSplitSearchParamFromFilename(filename: string) {
|
|
35
|
-
const [bareFilename] = filename.split('?')
|
|
36
|
-
return bareFilename!
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type SplitRouteIdentNodes = 'component' | 'loader'
|
|
40
32
|
type SplitNodeMeta = {
|
|
41
33
|
routeIdent: SplitRouteIdentNodes
|
|
42
34
|
splitStrategy: 'normal' | 'react-component'
|
|
@@ -44,7 +36,17 @@ type SplitNodeMeta = {
|
|
|
44
36
|
exporterIdent: string
|
|
45
37
|
localExporterIdent: string
|
|
46
38
|
}
|
|
47
|
-
const
|
|
39
|
+
const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
|
|
40
|
+
[
|
|
41
|
+
'loader',
|
|
42
|
+
{
|
|
43
|
+
routeIdent: 'loader',
|
|
44
|
+
localImporterIdent: '$$splitLoaderImporter', // const $$splitLoaderImporter = () => import('...')
|
|
45
|
+
splitStrategy: 'normal',
|
|
46
|
+
localExporterIdent: 'SplitLoader', // const SplitLoader = ...
|
|
47
|
+
exporterIdent: 'loader', // export { SplitLoader as loader }
|
|
48
|
+
},
|
|
49
|
+
],
|
|
48
50
|
[
|
|
49
51
|
'component',
|
|
50
52
|
{
|
|
@@ -56,32 +58,74 @@ const SPLIT_NOES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
|
|
|
56
58
|
},
|
|
57
59
|
],
|
|
58
60
|
[
|
|
59
|
-
'
|
|
61
|
+
'pendingComponent',
|
|
60
62
|
{
|
|
61
|
-
routeIdent: '
|
|
62
|
-
localImporterIdent: '$$
|
|
63
|
-
splitStrategy: '
|
|
64
|
-
localExporterIdent: '
|
|
65
|
-
exporterIdent: '
|
|
63
|
+
routeIdent: 'pendingComponent',
|
|
64
|
+
localImporterIdent: '$$splitPendingComponentImporter', // const $$splitPendingComponentImporter = () => import('...')
|
|
65
|
+
splitStrategy: 'react-component',
|
|
66
|
+
localExporterIdent: 'SplitPendingComponent', // const SplitPendingComponent = ...
|
|
67
|
+
exporterIdent: 'pendingComponent', // export { SplitPendingComponent as pendingComponent }
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
'errorComponent',
|
|
72
|
+
{
|
|
73
|
+
routeIdent: 'errorComponent',
|
|
74
|
+
localImporterIdent: '$$splitErrorComponentImporter', // const $$splitErrorComponentImporter = () => import('...')
|
|
75
|
+
splitStrategy: 'react-component',
|
|
76
|
+
localExporterIdent: 'SplitErrorComponent', // const SplitErrorComponent = ...
|
|
77
|
+
exporterIdent: 'errorComponent', // export { SplitErrorComponent as errorComponent }
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
'notFoundComponent',
|
|
82
|
+
{
|
|
83
|
+
routeIdent: 'notFoundComponent',
|
|
84
|
+
localImporterIdent: '$$splitNotFoundComponentImporter', // const $$splitNotFoundComponentImporter = () => import('...')
|
|
85
|
+
splitStrategy: 'react-component',
|
|
86
|
+
localExporterIdent: 'SplitNotFoundComponent', // const SplitNotFoundComponent = ...
|
|
87
|
+
exporterIdent: 'notFoundComponent', // export { SplitNotFoundComponent as notFoundComponent }
|
|
66
88
|
},
|
|
67
89
|
],
|
|
68
90
|
])
|
|
69
|
-
const
|
|
91
|
+
const KNOWN_SPLIT_ROUTE_IDENTS = [...SPLIT_NODES_CONFIG.keys()] as const
|
|
92
|
+
|
|
93
|
+
function addSplitSearchParamToFilename(
|
|
94
|
+
filename: string,
|
|
95
|
+
grouping: Array<string>,
|
|
96
|
+
) {
|
|
97
|
+
const [bareFilename] = filename.split('?')
|
|
98
|
+
|
|
99
|
+
const params = new URLSearchParams()
|
|
100
|
+
params.append(tsrSplit, createIdentifier(grouping))
|
|
101
|
+
|
|
102
|
+
return `${bareFilename}?${params.toString()}`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function removeSplitSearchParamFromFilename(filename: string) {
|
|
106
|
+
const [bareFilename] = filename.split('?')
|
|
107
|
+
return bareFilename!
|
|
108
|
+
}
|
|
70
109
|
|
|
71
110
|
export function compileCodeSplitReferenceRoute(
|
|
72
|
-
opts: ParseAstOptions & {
|
|
111
|
+
opts: ParseAstOptions & {
|
|
112
|
+
runtimeEnv: 'dev' | 'prod'
|
|
113
|
+
codeSplitGroupings: CodeSplitGroupings
|
|
114
|
+
},
|
|
73
115
|
): GeneratorResult {
|
|
74
116
|
const ast = parseAst(opts)
|
|
75
117
|
|
|
118
|
+
function findIndexForSplitNode(str: string) {
|
|
119
|
+
return opts.codeSplitGroupings.findIndex((group) =>
|
|
120
|
+
group.includes(str as any),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
76
124
|
babel.traverse(ast, {
|
|
77
125
|
Program: {
|
|
78
126
|
enter(programPath, programState) {
|
|
79
127
|
const state = programState as unknown as State
|
|
80
128
|
|
|
81
|
-
// We need to extract the existing search params from the filename, if any
|
|
82
|
-
// and add the splitPrefix to them, then write them back to the filename
|
|
83
|
-
const splitUrl = addSplitSearchParamToFilename(opts.filename)
|
|
84
|
-
|
|
85
129
|
/**
|
|
86
130
|
* If the component for the route is being imported from
|
|
87
131
|
* another file, this is to track the path to that file
|
|
@@ -91,8 +135,7 @@ export function compileCodeSplitReferenceRoute(
|
|
|
91
135
|
*
|
|
92
136
|
* `import '../shared/imported'`
|
|
93
137
|
*/
|
|
94
|
-
|
|
95
|
-
let existingLoaderImportPath: string | null = null
|
|
138
|
+
const removableImportPaths = new Set<string>([])
|
|
96
139
|
|
|
97
140
|
programPath.traverse(
|
|
98
141
|
{
|
|
@@ -124,9 +167,23 @@ export function compileCodeSplitReferenceRoute(
|
|
|
124
167
|
options.properties.forEach((prop) => {
|
|
125
168
|
if (t.isObjectProperty(prop)) {
|
|
126
169
|
if (t.isIdentifier(prop.key)) {
|
|
170
|
+
// If the user has not specified a split grouping for this key
|
|
171
|
+
// then we should not split it
|
|
172
|
+
const codeSplitGroupingByKey = findIndexForSplitNode(
|
|
173
|
+
prop.key.name,
|
|
174
|
+
)
|
|
175
|
+
if (codeSplitGroupingByKey === -1) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const codeSplitGroup = [
|
|
179
|
+
...new Set(
|
|
180
|
+
opts.codeSplitGroupings[codeSplitGroupingByKey],
|
|
181
|
+
),
|
|
182
|
+
]
|
|
183
|
+
|
|
127
184
|
const key = prop.key.name
|
|
128
185
|
// find key in nodeSplitConfig
|
|
129
|
-
const isNodeConfigAvailable =
|
|
186
|
+
const isNodeConfigAvailable = SPLIT_NODES_CONFIG.has(
|
|
130
187
|
key as any,
|
|
131
188
|
)
|
|
132
189
|
|
|
@@ -134,7 +191,16 @@ export function compileCodeSplitReferenceRoute(
|
|
|
134
191
|
return
|
|
135
192
|
}
|
|
136
193
|
|
|
137
|
-
const splitNodeMeta =
|
|
194
|
+
const splitNodeMeta = SPLIT_NODES_CONFIG.get(
|
|
195
|
+
key as any,
|
|
196
|
+
)!
|
|
197
|
+
|
|
198
|
+
// We need to extract the existing search params from the filename, if any
|
|
199
|
+
// and add the relevant codesplitPrefix to them, then write them back to the filename
|
|
200
|
+
const splitUrl = addSplitSearchParamToFilename(
|
|
201
|
+
opts.filename,
|
|
202
|
+
codeSplitGroup,
|
|
203
|
+
)
|
|
138
204
|
|
|
139
205
|
if (splitNodeMeta.splitStrategy === 'react-component') {
|
|
140
206
|
const value = prop.value
|
|
@@ -142,11 +208,14 @@ export function compileCodeSplitReferenceRoute(
|
|
|
142
208
|
let shouldSplit = true
|
|
143
209
|
|
|
144
210
|
if (t.isIdentifier(value)) {
|
|
145
|
-
|
|
211
|
+
const existingImportPath =
|
|
146
212
|
getImportSpecifierAndPathFromLocalName(
|
|
147
213
|
programPath,
|
|
148
214
|
value.name,
|
|
149
215
|
).path
|
|
216
|
+
if (existingImportPath) {
|
|
217
|
+
removableImportPaths.add(existingImportPath)
|
|
218
|
+
}
|
|
150
219
|
|
|
151
220
|
// exported identifiers should not be split
|
|
152
221
|
// since they are already being imported
|
|
@@ -206,7 +275,7 @@ export function compileCodeSplitReferenceRoute(
|
|
|
206
275
|
|
|
207
276
|
// If the TSRDummyComponent is not defined, define it
|
|
208
277
|
if (
|
|
209
|
-
|
|
278
|
+
opts.runtimeEnv !== 'prod' && // only in development
|
|
210
279
|
!hasImportedOrDefinedIdentifier('TSRDummyComponent')
|
|
211
280
|
) {
|
|
212
281
|
programPath.pushContainer('body', [
|
|
@@ -223,11 +292,14 @@ export function compileCodeSplitReferenceRoute(
|
|
|
223
292
|
let shouldSplit = true
|
|
224
293
|
|
|
225
294
|
if (t.isIdentifier(value)) {
|
|
226
|
-
|
|
295
|
+
const existingImportPath =
|
|
227
296
|
getImportSpecifierAndPathFromLocalName(
|
|
228
297
|
programPath,
|
|
229
298
|
value.name,
|
|
230
299
|
).path
|
|
300
|
+
if (existingImportPath) {
|
|
301
|
+
removableImportPaths.add(existingImportPath)
|
|
302
|
+
}
|
|
231
303
|
|
|
232
304
|
// exported identifiers should not be split
|
|
233
305
|
// since they are already being imported
|
|
@@ -291,17 +363,11 @@ export function compileCodeSplitReferenceRoute(
|
|
|
291
363
|
* from the program, by checking that the import has no
|
|
292
364
|
* specifiers
|
|
293
365
|
*/
|
|
294
|
-
if (
|
|
295
|
-
(existingCompImportPath as string | null) ||
|
|
296
|
-
(existingLoaderImportPath as string | null)
|
|
297
|
-
) {
|
|
366
|
+
if (removableImportPaths.size > 0) {
|
|
298
367
|
programPath.traverse({
|
|
299
368
|
ImportDeclaration(path) {
|
|
300
369
|
if (path.node.specifiers.length > 0) return
|
|
301
|
-
if (
|
|
302
|
-
path.node.source.value === existingCompImportPath ||
|
|
303
|
-
path.node.source.value === existingLoaderImportPath
|
|
304
|
-
) {
|
|
370
|
+
if (removableImportPaths.has(path.node.source.value)) {
|
|
305
371
|
path.remove()
|
|
306
372
|
}
|
|
307
373
|
},
|
|
@@ -321,10 +387,14 @@ export function compileCodeSplitReferenceRoute(
|
|
|
321
387
|
}
|
|
322
388
|
|
|
323
389
|
export function compileCodeSplitVirtualRoute(
|
|
324
|
-
opts: ParseAstOptions
|
|
390
|
+
opts: ParseAstOptions & {
|
|
391
|
+
splitTargets: Array<SplitRouteIdentNodes>
|
|
392
|
+
},
|
|
325
393
|
): GeneratorResult {
|
|
326
394
|
const ast = parseAst(opts)
|
|
327
395
|
|
|
396
|
+
const intendedSplitNodes = new Set(opts.splitTargets)
|
|
397
|
+
|
|
328
398
|
const knownExportedIdents = new Set<string>()
|
|
329
399
|
|
|
330
400
|
babel.traverse(ast, {
|
|
@@ -338,9 +408,12 @@ export function compileCodeSplitVirtualRoute(
|
|
|
338
408
|
> = {
|
|
339
409
|
component: undefined,
|
|
340
410
|
loader: undefined,
|
|
411
|
+
pendingComponent: undefined,
|
|
412
|
+
errorComponent: undefined,
|
|
413
|
+
notFoundComponent: undefined,
|
|
341
414
|
}
|
|
342
415
|
|
|
343
|
-
// Find the
|
|
416
|
+
// Find and track all the known split-able nodes
|
|
344
417
|
programPath.traverse(
|
|
345
418
|
{
|
|
346
419
|
CallExpression: (path) => {
|
|
@@ -366,7 +439,10 @@ export function compileCodeSplitVirtualRoute(
|
|
|
366
439
|
if (t.isObjectExpression(options)) {
|
|
367
440
|
options.properties.forEach((prop) => {
|
|
368
441
|
if (t.isObjectProperty(prop)) {
|
|
369
|
-
|
|
442
|
+
// do not use `intendedSplitNodes` here
|
|
443
|
+
// since we have special considerations that need
|
|
444
|
+
// to be accounted for like (not splitting exported identifiers)
|
|
445
|
+
KNOWN_SPLIT_ROUTE_IDENTS.forEach((splitType) => {
|
|
370
446
|
if (
|
|
371
447
|
!t.isIdentifier(prop.key) ||
|
|
372
448
|
prop.key.name !== splitType
|
|
@@ -389,7 +465,7 @@ export function compileCodeSplitVirtualRoute(
|
|
|
389
465
|
if (isExported && t.isIdentifier(value)) {
|
|
390
466
|
removeExports(ast, value)
|
|
391
467
|
} else {
|
|
392
|
-
const meta =
|
|
468
|
+
const meta = SPLIT_NODES_CONFIG.get(splitType)!
|
|
393
469
|
trackedNodesToSplitByType[splitType] = {
|
|
394
470
|
node: prop.value,
|
|
395
471
|
meta,
|
|
@@ -408,7 +484,8 @@ export function compileCodeSplitVirtualRoute(
|
|
|
408
484
|
state,
|
|
409
485
|
)
|
|
410
486
|
|
|
411
|
-
|
|
487
|
+
// Start the transformation to only exported the intended split nodes
|
|
488
|
+
intendedSplitNodes.forEach((SPLIT_TYPE) => {
|
|
412
489
|
const splitKey = trackedNodesToSplitByType[SPLIT_TYPE]
|
|
413
490
|
|
|
414
491
|
if (!splitKey) {
|
|
@@ -521,6 +598,18 @@ export function compileCodeSplitVirtualRoute(
|
|
|
521
598
|
),
|
|
522
599
|
]),
|
|
523
600
|
)
|
|
601
|
+
} else if (t.isTSAsExpression(splitNode)) {
|
|
602
|
+
// remove the type assertion
|
|
603
|
+
splitNode = splitNode.expression
|
|
604
|
+
programPath.pushContainer(
|
|
605
|
+
'body',
|
|
606
|
+
t.variableDeclaration('const', [
|
|
607
|
+
t.variableDeclarator(
|
|
608
|
+
t.identifier(splitMeta.localExporterIdent),
|
|
609
|
+
splitNode,
|
|
610
|
+
),
|
|
611
|
+
]),
|
|
612
|
+
)
|
|
524
613
|
} else {
|
|
525
614
|
console.info('Unexpected splitNode type:', splitNode)
|
|
526
615
|
throw new Error(`Unexpected splitNode type ☝️: ${splitNode.type}`)
|
|
@@ -586,7 +675,7 @@ export function compileCodeSplitVirtualRoute(
|
|
|
586
675
|
return str
|
|
587
676
|
}, '')
|
|
588
677
|
|
|
589
|
-
const warningMessage = `These exports from "${opts.filename
|
|
678
|
+
const warningMessage = `These exports from "${opts.filename}" are not being code-split and will increase your bundle size: ${list}\nThese should either have their export statements removed or be imported from another file that is not a route.`
|
|
590
679
|
console.warn(warningMessage)
|
|
591
680
|
|
|
592
681
|
// append this warning to the file using a template
|
|
@@ -605,6 +694,101 @@ export function compileCodeSplitVirtualRoute(
|
|
|
605
694
|
})
|
|
606
695
|
}
|
|
607
696
|
|
|
697
|
+
/**
|
|
698
|
+
* This function should read get the options from by searching for the key `codeSplitGroupings`
|
|
699
|
+
* on createFileRoute and return it's values if it exists, else return undefined
|
|
700
|
+
*/
|
|
701
|
+
export function detectCodeSplitGroupingsFromRoute(opts: ParseAstOptions): {
|
|
702
|
+
groupings: CodeSplitGroupings | undefined
|
|
703
|
+
routeId: string
|
|
704
|
+
} {
|
|
705
|
+
const ast = parseAst(opts)
|
|
706
|
+
|
|
707
|
+
let routeId = ''
|
|
708
|
+
|
|
709
|
+
let codeSplitGroupings: CodeSplitGroupings | undefined = undefined
|
|
710
|
+
|
|
711
|
+
babel.traverse(ast, {
|
|
712
|
+
Program: {
|
|
713
|
+
enter(programPath) {
|
|
714
|
+
programPath.traverse({
|
|
715
|
+
CallExpression(path) {
|
|
716
|
+
if (!t.isIdentifier(path.node.callee)) {
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (
|
|
721
|
+
!(
|
|
722
|
+
path.node.callee.name === 'createRoute' ||
|
|
723
|
+
path.node.callee.name === 'createFileRoute'
|
|
724
|
+
)
|
|
725
|
+
) {
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (t.isCallExpression(path.parentPath.node)) {
|
|
730
|
+
// Extract out the routeId
|
|
731
|
+
if (t.isCallExpression(path.parentPath.node.callee)) {
|
|
732
|
+
const callee = path.parentPath.node.callee
|
|
733
|
+
|
|
734
|
+
if (t.isIdentifier(callee.callee)) {
|
|
735
|
+
const firstArg = callee.arguments[0]
|
|
736
|
+
if (t.isStringLiteral(firstArg)) {
|
|
737
|
+
routeId = firstArg.value
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Extracting the codeSplitGroupings
|
|
743
|
+
const options = resolveIdentifier(
|
|
744
|
+
path,
|
|
745
|
+
path.parentPath.node.arguments[0],
|
|
746
|
+
)
|
|
747
|
+
if (t.isObjectExpression(options)) {
|
|
748
|
+
options.properties.forEach((prop) => {
|
|
749
|
+
if (t.isObjectProperty(prop)) {
|
|
750
|
+
if (t.isIdentifier(prop.key)) {
|
|
751
|
+
if (prop.key.name === 'codeSplitGroupings') {
|
|
752
|
+
const value = prop.value
|
|
753
|
+
|
|
754
|
+
if (t.isArrayExpression(value)) {
|
|
755
|
+
codeSplitGroupings = value.elements.map((group) => {
|
|
756
|
+
if (t.isArrayExpression(group)) {
|
|
757
|
+
return group.elements.map((node) => {
|
|
758
|
+
if (!t.isStringLiteral(node)) {
|
|
759
|
+
throw new Error(
|
|
760
|
+
'You must provide a string literal for the codeSplitGroupings',
|
|
761
|
+
)
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return node.value
|
|
765
|
+
}) as Array<SplitRouteIdentNodes>
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
throw new Error(
|
|
769
|
+
'You must provide arrays with codeSplitGroupings options.',
|
|
770
|
+
)
|
|
771
|
+
})
|
|
772
|
+
} else {
|
|
773
|
+
throw new Error(
|
|
774
|
+
'You must provide an array of arrays for the codeSplitGroupings.',
|
|
775
|
+
)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
})
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
return { groupings: codeSplitGroupings, routeId }
|
|
790
|
+
}
|
|
791
|
+
|
|
608
792
|
function getImportSpecifierAndPathFromLocalName(
|
|
609
793
|
programPath: babel.NodePath<t.Program>,
|
|
610
794
|
name: string,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function createIdentifier(strings: Array<string>): string {
|
|
2
|
+
if (strings.length === 0) {
|
|
3
|
+
throw new Error('Cannot create an identifier from an empty array')
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const sortedStrings = [...strings].sort()
|
|
7
|
+
const combinedString = sortedStrings.join('---') // Delimiter
|
|
8
|
+
|
|
9
|
+
// Replace unsafe characters
|
|
10
|
+
let safeString = combinedString.replace(/\//g, '--slash--')
|
|
11
|
+
safeString = safeString.replace(/\\/g, '--backslash--')
|
|
12
|
+
safeString = safeString.replace(/\?/g, '--question--')
|
|
13
|
+
safeString = safeString.replace(/%/g, '--percent--')
|
|
14
|
+
safeString = safeString.replace(/#/g, '--hash--')
|
|
15
|
+
safeString = safeString.replace(/\+/g, '--plus--')
|
|
16
|
+
safeString = safeString.replace(/=/g, '--equals--')
|
|
17
|
+
safeString = safeString.replace(/&/g, '--ampersand--')
|
|
18
|
+
safeString = safeString.replace(/\s/g, '_') // Replace spaces with underscores
|
|
19
|
+
|
|
20
|
+
return safeString
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function decodeIdentifier(identifier: string): Array<string> {
|
|
24
|
+
if (!identifier) {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let combinedString = identifier.replace(/--slash--/g, '/')
|
|
29
|
+
combinedString = combinedString.replace(/--backslash--/g, '\\')
|
|
30
|
+
combinedString = combinedString.replace(/--question--/g, '?')
|
|
31
|
+
combinedString = combinedString.replace(/--percent--/g, '%')
|
|
32
|
+
combinedString = combinedString.replace(/--hash--/g, '#')
|
|
33
|
+
combinedString = combinedString.replace(/--plus--/g, '+')
|
|
34
|
+
combinedString = combinedString.replace(/--equals--/g, '=')
|
|
35
|
+
combinedString = combinedString.replace(/--ampersand--/g, '&')
|
|
36
|
+
combinedString = combinedString.replace(/_/g, ' ') // Restore spaces
|
|
37
|
+
|
|
38
|
+
return combinedString.split('---')
|
|
39
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -3,9 +3,72 @@ import {
|
|
|
3
3
|
configSchema as generatorConfigSchema,
|
|
4
4
|
getConfig as getGeneratorConfig,
|
|
5
5
|
} from '@tanstack/router-generator'
|
|
6
|
+
import type { RegisteredRouter, RouteIds } from '@tanstack/react-router'
|
|
7
|
+
import type { CodeSplitGroupings } from './constants'
|
|
8
|
+
|
|
9
|
+
export const splitGroupingsSchema = z
|
|
10
|
+
.array(
|
|
11
|
+
z.array(
|
|
12
|
+
z.union([
|
|
13
|
+
z.literal('loader'),
|
|
14
|
+
z.literal('component'),
|
|
15
|
+
z.literal('pendingComponent'),
|
|
16
|
+
z.literal('errorComponent'),
|
|
17
|
+
z.literal('notFoundComponent'),
|
|
18
|
+
]),
|
|
19
|
+
),
|
|
20
|
+
{
|
|
21
|
+
message:
|
|
22
|
+
" Must be an Array of Arrays containing the split groupings. i.e. [['component'], ['pendingComponent'], ['errorComponent', 'notFoundComponent']]",
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
.superRefine((val, ctx) => {
|
|
26
|
+
const flattened = val.flat()
|
|
27
|
+
const unique = [...new Set(flattened)]
|
|
28
|
+
|
|
29
|
+
// Elements must be unique,
|
|
30
|
+
// ie. this shouldn't be allows [['component'], ['component', 'loader']]
|
|
31
|
+
if (unique.length !== flattened.length) {
|
|
32
|
+
ctx.addIssue({
|
|
33
|
+
code: 'custom',
|
|
34
|
+
message:
|
|
35
|
+
" Split groupings must be unique and not repeated. i.e. i.e. [['component'], ['pendingComponent'], ['errorComponent', 'notFoundComponent']]." +
|
|
36
|
+
`\n You input was: ${JSON.stringify(val)}.`,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export type CodeSplittingOptions = {
|
|
42
|
+
/**
|
|
43
|
+
* Use this function to programmatically control the code splitting behavior
|
|
44
|
+
* based on the `routeId` for each route.
|
|
45
|
+
*
|
|
46
|
+
* If you just need to change the default behavior, you can use the `defaultBehavior` option.
|
|
47
|
+
* @param params
|
|
48
|
+
*/
|
|
49
|
+
splitBehavior?: (params: {
|
|
50
|
+
routeId: RouteIds<RegisteredRouter['routeTree']>
|
|
51
|
+
}) => CodeSplitGroupings | undefined | void
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The default/global configuration to control your code splitting behavior per route.
|
|
55
|
+
* @default [['component'],['pendingComponent'],['errorComponent'],['notFoundComponent']]
|
|
56
|
+
*/
|
|
57
|
+
defaultBehavior?: CodeSplitGroupings
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const codeSplittingOptionsSchema = z.object({
|
|
61
|
+
splitBehavior: z.function().optional(),
|
|
62
|
+
defaultBehavior: splitGroupingsSchema.optional(),
|
|
63
|
+
})
|
|
6
64
|
|
|
7
65
|
export const configSchema = generatorConfigSchema.extend({
|
|
8
66
|
enableRouteGeneration: z.boolean().optional(),
|
|
67
|
+
codeSplittingOptions: z
|
|
68
|
+
.custom<CodeSplittingOptions>((v) => {
|
|
69
|
+
return codeSplittingOptionsSchema.parse(v)
|
|
70
|
+
})
|
|
71
|
+
.optional(),
|
|
9
72
|
})
|
|
10
73
|
|
|
11
74
|
export const getConfig = (inlineConfig: Partial<Config>, root: string) => {
|
package/src/core/constants.ts
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const tsrSplit = 'tsr-split'
|
|
2
|
+
|
|
3
|
+
export const splitRouteIdentNodes = [
|
|
4
|
+
'loader',
|
|
5
|
+
'component',
|
|
6
|
+
'pendingComponent',
|
|
7
|
+
'errorComponent',
|
|
8
|
+
'notFoundComponent',
|
|
9
|
+
] as const
|
|
10
|
+
export type SplitRouteIdentNodes = (typeof splitRouteIdentNodes)[number]
|
|
11
|
+
export type CodeSplitGroupings = Array<Array<SplitRouteIdentNodes>>
|
|
12
|
+
|
|
13
|
+
export const defaultCodeSplitGroupings: CodeSplitGroupings = [
|
|
14
|
+
['component'],
|
|
15
|
+
['pendingComponent'],
|
|
16
|
+
['errorComponent'],
|
|
17
|
+
['notFoundComponent'],
|
|
18
|
+
]
|