@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.
Files changed (42) hide show
  1. package/dist/cjs/core/code-splitter/compilers.cjs +187 -34
  2. package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
  3. package/dist/cjs/core/code-splitter/compilers.d.cts +14 -2
  4. package/dist/cjs/core/code-splitter/path-ids.cjs +37 -0
  5. package/dist/cjs/core/code-splitter/path-ids.cjs.map +1 -0
  6. package/dist/cjs/core/code-splitter/path-ids.d.cts +2 -0
  7. package/dist/cjs/core/config.cjs +33 -1
  8. package/dist/cjs/core/config.cjs.map +1 -1
  9. package/dist/cjs/core/config.d.cts +24 -0
  10. package/dist/cjs/core/constants.cjs +17 -2
  11. package/dist/cjs/core/constants.cjs.map +1 -1
  12. package/dist/cjs/core/constants.d.cts +5 -1
  13. package/dist/cjs/core/router-code-splitter-plugin.cjs +69 -16
  14. package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
  15. package/dist/cjs/esbuild.d.cts +3 -0
  16. package/dist/cjs/rspack.d.cts +3 -0
  17. package/dist/cjs/vite.d.cts +3 -0
  18. package/dist/cjs/webpack.d.cts +3 -0
  19. package/dist/esm/core/code-splitter/compilers.d.ts +14 -2
  20. package/dist/esm/core/code-splitter/compilers.js +189 -36
  21. package/dist/esm/core/code-splitter/compilers.js.map +1 -1
  22. package/dist/esm/core/code-splitter/path-ids.d.ts +2 -0
  23. package/dist/esm/core/code-splitter/path-ids.js +37 -0
  24. package/dist/esm/core/code-splitter/path-ids.js.map +1 -0
  25. package/dist/esm/core/config.d.ts +24 -0
  26. package/dist/esm/core/config.js +34 -2
  27. package/dist/esm/core/config.js.map +1 -1
  28. package/dist/esm/core/constants.d.ts +5 -1
  29. package/dist/esm/core/constants.js +17 -2
  30. package/dist/esm/core/constants.js.map +1 -1
  31. package/dist/esm/core/router-code-splitter-plugin.js +72 -19
  32. package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
  33. package/dist/esm/esbuild.d.ts +3 -0
  34. package/dist/esm/rspack.d.ts +3 -0
  35. package/dist/esm/vite.d.ts +3 -0
  36. package/dist/esm/webpack.d.ts +3 -0
  37. package/package.json +3 -3
  38. package/src/core/code-splitter/compilers.ts +230 -46
  39. package/src/core/code-splitter/path-ids.ts +39 -0
  40. package/src/core/config.ts +63 -0
  41. package/src/core/constants.ts +18 -1
  42. 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 { splitPrefix } from '../constants'
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 SPLIT_NOES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
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
- 'loader',
61
+ 'pendingComponent',
60
62
  {
61
- routeIdent: 'loader',
62
- localImporterIdent: '$$splitLoaderImporter', // const $$splitLoaderImporter = () => import('...')
63
- splitStrategy: 'normal',
64
- localExporterIdent: 'SplitLoader', // const SplitLoader = ...
65
- exporterIdent: 'loader', // export { SplitLoader as loader }
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 SPLIT_ROUTE_IDENT_NODES = [...SPLIT_NOES_CONFIG.keys()] as 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 & { isProduction: boolean },
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
- let existingCompImportPath: string | null = null
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 = SPLIT_NOES_CONFIG.has(
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 = SPLIT_NOES_CONFIG.get(key as any)!
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
- existingCompImportPath =
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
- !opts.isProduction && // only in development
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
- existingLoaderImportPath =
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 node
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
- SPLIT_ROUTE_IDENT_NODES.forEach((splitType) => {
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 = SPLIT_NOES_CONFIG.get(splitType)!
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
- SPLIT_ROUTE_IDENT_NODES.forEach((SPLIT_TYPE) => {
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.replace('?' + splitPrefix, '')}" 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.`
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
+ }
@@ -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) => {
@@ -1 +1,18 @@
1
- export const splitPrefix = 'tsr-split'
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
+ ]