@tanstack/router-plugin 1.167.1 → 1.167.2

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 (29) hide show
  1. package/dist/cjs/core/code-splitter/compilers.cjs +47 -25
  2. package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
  3. package/dist/cjs/core/code-splitter/plugins/framework-plugins.cjs +2 -1
  4. package/dist/cjs/core/code-splitter/plugins/framework-plugins.cjs.map +1 -1
  5. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.cjs +41 -0
  6. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.cjs.map +1 -0
  7. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.d.cts +2 -0
  8. package/dist/cjs/core/code-splitter/plugins.d.cts +11 -0
  9. package/dist/cjs/core/code-splitter/types.d.cts +9 -0
  10. package/dist/cjs/core/utils.cjs +3 -1
  11. package/dist/cjs/core/utils.cjs.map +1 -1
  12. package/dist/esm/core/code-splitter/compilers.js +47 -25
  13. package/dist/esm/core/code-splitter/compilers.js.map +1 -1
  14. package/dist/esm/core/code-splitter/plugins/framework-plugins.js +2 -1
  15. package/dist/esm/core/code-splitter/plugins/framework-plugins.js.map +1 -1
  16. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.d.ts +2 -0
  17. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.js +38 -0
  18. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.js.map +1 -0
  19. package/dist/esm/core/code-splitter/plugins.d.ts +11 -0
  20. package/dist/esm/core/code-splitter/types.d.ts +9 -0
  21. package/dist/esm/core/utils.js +3 -1
  22. package/dist/esm/core/utils.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/core/code-splitter/compilers.ts +84 -50
  25. package/src/core/code-splitter/plugins/framework-plugins.ts +5 -1
  26. package/src/core/code-splitter/plugins/react-stable-hmr-split-route-components.ts +56 -0
  27. package/src/core/code-splitter/plugins.ts +14 -0
  28. package/src/core/code-splitter/types.ts +11 -0
  29. package/src/core/utils.ts +9 -2
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/core/utils.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport type babel from '@babel/core'\n\nexport const debug =\n process.env.TSR_VITE_DEBUG &&\n ['true', 'router-plugin'].includes(process.env.TSR_VITE_DEBUG)\n\n/**\n * Normalizes a file path by converting Windows backslashes to forward slashes.\n * This ensures consistent path handling across different bundlers and operating systems.\n *\n * The route generator stores paths with forward slashes, but rspack/webpack on Windows\n * pass native paths with backslashes to transform handlers.\n */\nexport function normalizePath(path: string): string {\n return path.replace(/\\\\/g, '/')\n}\n\nexport function getUniqueProgramIdentifier(\n programPath: babel.NodePath<t.Program>,\n baseName: string,\n): t.Identifier {\n let name = baseName\n let suffix = 2\n\n while (\n programPath.scope.hasBinding(name) ||\n programPath.scope.hasGlobal(name)\n ) {\n name = `${baseName}${suffix}`\n suffix++\n }\n\n return t.identifier(name)\n}\n"],"mappings":";;AAGA,IAAa,QACX,QAAQ,IAAI,kBACZ,CAAC,QAAQ,gBAAgB,CAAC,SAAS,QAAQ,IAAI,eAAe;;;;;;;;AAShE,SAAgB,cAAc,MAAsB;AAClD,QAAO,KAAK,QAAQ,OAAO,IAAI;;AAGjC,SAAgB,2BACd,aACA,UACc;CACd,IAAI,OAAO;CACX,IAAI,SAAS;AAEb,QACE,YAAY,MAAM,WAAW,KAAK,IAClC,YAAY,MAAM,UAAU,KAAK,EACjC;AACA,SAAO,GAAG,WAAW;AACrB;;AAGF,QAAO,EAAE,WAAW,KAAK"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/core/utils.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport type babel from '@babel/core'\n\nexport const debug =\n process.env.TSR_VITE_DEBUG &&\n ['true', 'router-plugin'].includes(process.env.TSR_VITE_DEBUG)\n\n/**\n * Normalizes a file path by converting Windows backslashes to forward slashes.\n * This ensures consistent path handling across different bundlers and operating systems.\n *\n * The route generator stores paths with forward slashes, but rspack/webpack on Windows\n * pass native paths with backslashes to transform handlers.\n */\nexport function normalizePath(path: string): string {\n return path.replace(/\\\\/g, '/')\n}\n\nexport function getUniqueProgramIdentifier(\n programPath: babel.NodePath<t.Program>,\n baseName: string,\n): t.Identifier {\n let name = baseName\n let suffix = 2\n\n const programScope = programPath.scope.getProgramParent()\n\n while (\n programScope.hasBinding(name) ||\n programScope.hasGlobal(name) ||\n programScope.hasReference(name)\n ) {\n name = `${baseName}${suffix}`\n suffix++\n }\n\n // Register the name so subsequent calls within the same traversal\n // see it and avoid collisions\n programScope.references[name] = true\n\n return t.identifier(name)\n}\n"],"mappings":";;AAGA,IAAa,QACX,QAAQ,IAAI,kBACZ,CAAC,QAAQ,gBAAgB,CAAC,SAAS,QAAQ,IAAI,eAAe;;;;;;;;AAShE,SAAgB,cAAc,MAAsB;AAClD,QAAO,KAAK,QAAQ,OAAO,IAAI;;AAGjC,SAAgB,2BACd,aACA,UACc;CACd,IAAI,OAAO;CACX,IAAI,SAAS;CAEb,MAAM,eAAe,YAAY,MAAM,kBAAkB;AAEzD,QACE,aAAa,WAAW,KAAK,IAC7B,aAAa,UAAU,KAAK,IAC5B,aAAa,aAAa,KAAK,EAC/B;AACA,SAAO,GAAG,WAAW;AACrB;;AAKF,cAAa,WAAW,QAAQ;AAEhC,QAAO,EAAE,WAAW,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-plugin",
3
- "version": "1.167.1",
3
+ "version": "1.167.2",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -18,14 +18,8 @@ import type {
18
18
  import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
19
19
  import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants'
20
20
  import type { Config, DeletableNodes } from '../config'
21
+ import type { SplitNodeMeta } from './types'
21
22
 
22
- type SplitNodeMeta = {
23
- routeIdent: SplitRouteIdentNodes
24
- splitStrategy: 'lazyFn' | 'lazyRouteComponent'
25
- localImporterIdent: string
26
- exporterIdent: string
27
- localExporterIdent: string
28
- }
29
23
  const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
30
24
  [
31
25
  'loader',
@@ -183,6 +177,18 @@ export function collectIdentifiersFromNode(node: t.Node): Set<string> {
183
177
  return ids
184
178
  }
185
179
 
180
+ function getObjectPropertyKeyName(prop: t.ObjectProperty): string | undefined {
181
+ if (t.isIdentifier(prop.key)) {
182
+ return prop.key.name
183
+ }
184
+
185
+ if (t.isStringLiteral(prop.key)) {
186
+ return prop.key.value
187
+ }
188
+
189
+ return undefined
190
+ }
191
+
186
192
  /**
187
193
  * Build a map from binding name → declaration AST node for all
188
194
  * locally-declared module-level bindings. Built once, O(1) lookup.
@@ -297,10 +303,12 @@ export function computeSharedBindings(opts: {
297
303
  const splitGroupsPresent = new Set<number>()
298
304
  let hasNonSplit = false
299
305
  for (const prop of routeOptions.properties) {
300
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue
301
- if (prop.key.name === 'codeSplitGroupings') continue
306
+ if (!t.isObjectProperty(prop)) continue
307
+ const key = getObjectPropertyKeyName(prop)
308
+ if (!key) continue
309
+ if (key === 'codeSplitGroupings') continue
302
310
  if (t.isIdentifier(prop.value) && prop.value.name === 'undefined') continue
303
- const groupIndex = findIndexForSplitNode(prop.key.name) // -1 if non-split
311
+ const groupIndex = findIndexForSplitNode(key) // -1 if non-split
304
312
  if (groupIndex === -1) {
305
313
  hasNonSplit = true
306
314
  } else {
@@ -333,8 +341,9 @@ export function computeSharedBindings(opts: {
333
341
  const refsByGroup = new Map<string, Set<number>>()
334
342
 
335
343
  for (const prop of routeOptions.properties) {
336
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue
337
- const key = prop.key.name
344
+ if (!t.isObjectProperty(prop)) continue
345
+ const key = getObjectPropertyKeyName(prop)
346
+ if (!key) continue
338
347
 
339
348
  if (key === 'codeSplitGroupings') continue
340
349
 
@@ -707,11 +716,10 @@ export function compileCodeSplitReferenceRoute(
707
716
  routeOptions.properties = routeOptions.properties.filter(
708
717
  (prop) => {
709
718
  if (t.isObjectProperty(prop)) {
710
- if (t.isIdentifier(prop.key)) {
711
- if (opts.deleteNodes!.has(prop.key.name as any)) {
712
- modified = true
713
- return false
714
- }
719
+ const key = getObjectPropertyKeyName(prop)
720
+ if (key && opts.deleteNodes!.has(key as any)) {
721
+ modified = true
722
+ return false
715
723
  }
716
724
  }
717
725
  return true
@@ -747,9 +755,9 @@ export function compileCodeSplitReferenceRoute(
747
755
  }
748
756
  routeOptions.properties.forEach((prop) => {
749
757
  if (t.isObjectProperty(prop)) {
750
- if (t.isIdentifier(prop.key)) {
751
- const key = prop.key.name
758
+ const key = getObjectPropertyKeyName(prop)
752
759
 
760
+ if (key) {
753
761
  // If the user has not specified a split grouping for this key
754
762
  // then we should not split it
755
763
  const codeSplitGroupingByKey = findIndexForSplitNode(key)
@@ -858,9 +866,39 @@ export function compileCodeSplitReferenceRoute(
858
866
  ])
859
867
  }
860
868
 
861
- prop.value = template.expression(
862
- `${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
863
- )()
869
+ const insertionPath = path.getStatementParent() ?? path
870
+ let splitPropValue: t.Expression | undefined
871
+
872
+ for (const plugin of opts.compilerPlugins ?? []) {
873
+ const pluginPropValue = plugin.onSplitRouteProperty?.(
874
+ {
875
+ programPath,
876
+ callExpressionPath: path,
877
+ insertionPath,
878
+ routeOptions,
879
+ prop,
880
+ splitNodeMeta,
881
+ lazyRouteComponentIdent:
882
+ LAZY_ROUTE_COMPONENT_IDENT,
883
+ },
884
+ )
885
+
886
+ if (!pluginPropValue) {
887
+ continue
888
+ }
889
+
890
+ modified = true
891
+ splitPropValue = pluginPropValue
892
+ break
893
+ }
894
+
895
+ if (splitPropValue) {
896
+ prop.value = splitPropValue
897
+ } else {
898
+ prop.value = template.expression(
899
+ `${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
900
+ )()
901
+ }
864
902
 
865
903
  // add HMR handling
866
904
  if (opts.addHmr && !hmrAdded) {
@@ -1128,10 +1166,7 @@ export function compileCodeSplitVirtualRoute(
1128
1166
  // since we have special considerations that need
1129
1167
  // to be accounted for like (not splitting exported identifiers)
1130
1168
  KNOWN_SPLIT_ROUTE_IDENTS.forEach((splitType) => {
1131
- if (
1132
- !t.isIdentifier(prop.key) ||
1133
- prop.key.name !== splitType
1134
- ) {
1169
+ if (getObjectPropertyKeyName(prop) !== splitType) {
1135
1170
  return
1136
1171
  }
1137
1172
 
@@ -1673,33 +1708,32 @@ export function detectCodeSplitGroupingsFromRoute(opts: ParseAstOptions): {
1673
1708
  if (t.isObjectExpression(routeOptions)) {
1674
1709
  routeOptions.properties.forEach((prop) => {
1675
1710
  if (t.isObjectProperty(prop)) {
1676
- if (t.isIdentifier(prop.key)) {
1677
- if (prop.key.name === 'codeSplitGroupings') {
1678
- const value = prop.value
1711
+ const key = getObjectPropertyKeyName(prop)
1712
+ if (key === 'codeSplitGroupings') {
1713
+ const value = prop.value
1714
+
1715
+ if (t.isArrayExpression(value)) {
1716
+ codeSplitGroupings = value.elements.map((group) => {
1717
+ if (t.isArrayExpression(group)) {
1718
+ return group.elements.map((node) => {
1719
+ if (!t.isStringLiteral(node)) {
1720
+ throw new Error(
1721
+ 'You must provide a string literal for the codeSplitGroupings',
1722
+ )
1723
+ }
1724
+
1725
+ return node.value
1726
+ }) as Array<SplitRouteIdentNodes>
1727
+ }
1679
1728
 
1680
- if (t.isArrayExpression(value)) {
1681
- codeSplitGroupings = value.elements.map((group) => {
1682
- if (t.isArrayExpression(group)) {
1683
- return group.elements.map((node) => {
1684
- if (!t.isStringLiteral(node)) {
1685
- throw new Error(
1686
- 'You must provide a string literal for the codeSplitGroupings',
1687
- )
1688
- }
1689
-
1690
- return node.value
1691
- }) as Array<SplitRouteIdentNodes>
1692
- }
1693
-
1694
- throw new Error(
1695
- 'You must provide arrays with codeSplitGroupings options.',
1696
- )
1697
- })
1698
- } else {
1699
1729
  throw new Error(
1700
- 'You must provide an array of arrays for the codeSplitGroupings.',
1730
+ 'You must provide arrays with codeSplitGroupings options.',
1701
1731
  )
1702
- }
1732
+ })
1733
+ } else {
1734
+ throw new Error(
1735
+ 'You must provide an array of arrays for the codeSplitGroupings.',
1736
+ )
1703
1737
  }
1704
1738
  }
1705
1739
  }
@@ -1,4 +1,5 @@
1
1
  import { createReactRefreshRouteComponentsPlugin } from './react-refresh-route-components'
2
+ import { createReactStableHmrSplitRouteComponentsPlugin } from './react-stable-hmr-split-route-components'
2
3
  import type { ReferenceRouteCompilerPlugin } from '../plugins'
3
4
  import type { Config } from '../../config'
4
5
 
@@ -9,7 +10,10 @@ export function getReferenceRouteCompilerPlugins(opts: {
9
10
  switch (opts.targetFramework) {
10
11
  case 'react': {
11
12
  if (opts.addHmr) {
12
- return [createReactRefreshRouteComponentsPlugin()]
13
+ return [
14
+ createReactRefreshRouteComponentsPlugin(),
15
+ createReactStableHmrSplitRouteComponentsPlugin(),
16
+ ]
13
17
  }
14
18
  return undefined
15
19
  }
@@ -0,0 +1,56 @@
1
+ import * as template from '@babel/template'
2
+ import * as t from '@babel/types'
3
+ import { getUniqueProgramIdentifier } from '../../utils'
4
+ import type { ReferenceRouteCompilerPlugin } from '../plugins'
5
+
6
+ function capitalizeIdentifier(str: string) {
7
+ return str[0]!.toUpperCase() + str.slice(1)
8
+ }
9
+
10
+ function createHotDataKey(exportName: string) {
11
+ return `tsr-split-component:${exportName}`
12
+ }
13
+
14
+ const buildStableSplitComponentStatements = template.statements(
15
+ `
16
+ const %%stableComponentIdent%% = import.meta.hot?.data?.[%%hotDataKey%%] ?? %%lazyRouteComponentIdent%%(%%localImporterIdent%%, %%exporterIdent%%)
17
+ if (import.meta.hot) {
18
+ import.meta.hot.data[%%hotDataKey%%] = %%stableComponentIdent%%
19
+ }
20
+ `,
21
+ {
22
+ syntacticPlaceholders: true,
23
+ },
24
+ )
25
+
26
+ export function createReactStableHmrSplitRouteComponentsPlugin(): ReferenceRouteCompilerPlugin {
27
+ return {
28
+ name: 'react-stable-hmr-split-route-components',
29
+ onSplitRouteProperty(ctx) {
30
+ if (ctx.splitNodeMeta.splitStrategy !== 'lazyRouteComponent') {
31
+ return
32
+ }
33
+
34
+ const stableComponentIdent = getUniqueProgramIdentifier(
35
+ ctx.programPath,
36
+ `TSRSplit${capitalizeIdentifier(ctx.splitNodeMeta.exporterIdent)}`,
37
+ )
38
+
39
+ const hotDataKey = createHotDataKey(ctx.splitNodeMeta.exporterIdent)
40
+
41
+ ctx.insertionPath.insertBefore(
42
+ buildStableSplitComponentStatements({
43
+ stableComponentIdent,
44
+ hotDataKey: t.stringLiteral(hotDataKey),
45
+ lazyRouteComponentIdent: t.identifier(ctx.lazyRouteComponentIdent),
46
+ localImporterIdent: t.identifier(
47
+ ctx.splitNodeMeta.localImporterIdent,
48
+ ),
49
+ exporterIdent: t.stringLiteral(ctx.splitNodeMeta.exporterIdent),
50
+ }),
51
+ )
52
+
53
+ return t.identifier(stableComponentIdent.name)
54
+ },
55
+ }
56
+ }
@@ -2,6 +2,7 @@ import type babel from '@babel/core'
2
2
  import type * as t from '@babel/types'
3
3
  import type { Config, DeletableNodes } from '../config'
4
4
  import type { CodeSplitGroupings } from '../constants'
5
+ import type { SplitNodeMeta } from './types'
5
6
 
6
7
  export type CompileCodeSplitReferenceRouteOptions = {
7
8
  codeSplitGroupings: CodeSplitGroupings
@@ -22,6 +23,16 @@ export type ReferenceRouteCompilerPluginContext = {
22
23
  opts: CompileCodeSplitReferenceRouteOptions
23
24
  }
24
25
 
26
+ export type ReferenceRouteSplitPropertyCompilerPluginContext = {
27
+ programPath: babel.NodePath<t.Program>
28
+ callExpressionPath: babel.NodePath<t.CallExpression>
29
+ insertionPath: babel.NodePath
30
+ routeOptions: t.ObjectExpression
31
+ prop: t.ObjectProperty
32
+ splitNodeMeta: SplitNodeMeta
33
+ lazyRouteComponentIdent: string
34
+ }
35
+
25
36
  export type ReferenceRouteCompilerPluginResult = {
26
37
  modified?: boolean
27
38
  }
@@ -31,4 +42,7 @@ export type ReferenceRouteCompilerPlugin = {
31
42
  onUnsplittableRoute?: (
32
43
  ctx: ReferenceRouteCompilerPluginContext,
33
44
  ) => void | ReferenceRouteCompilerPluginResult
45
+ onSplitRouteProperty?: (
46
+ ctx: ReferenceRouteSplitPropertyCompilerPluginContext,
47
+ ) => void | t.Expression
34
48
  }
@@ -0,0 +1,11 @@
1
+ import type { SplitRouteIdentNodes } from '../constants'
2
+
3
+ export type SplitStrategy = 'lazyFn' | 'lazyRouteComponent'
4
+
5
+ export type SplitNodeMeta = {
6
+ routeIdent: SplitRouteIdentNodes
7
+ splitStrategy: SplitStrategy
8
+ localImporterIdent: string
9
+ exporterIdent: string
10
+ localExporterIdent: string
11
+ }
package/src/core/utils.ts CHANGED
@@ -23,13 +23,20 @@ export function getUniqueProgramIdentifier(
23
23
  let name = baseName
24
24
  let suffix = 2
25
25
 
26
+ const programScope = programPath.scope.getProgramParent()
27
+
26
28
  while (
27
- programPath.scope.hasBinding(name) ||
28
- programPath.scope.hasGlobal(name)
29
+ programScope.hasBinding(name) ||
30
+ programScope.hasGlobal(name) ||
31
+ programScope.hasReference(name)
29
32
  ) {
30
33
  name = `${baseName}${suffix}`
31
34
  suffix++
32
35
  }
33
36
 
37
+ // Register the name so subsequent calls within the same traversal
38
+ // see it and avoid collisions
39
+ programScope.references[name] = true
40
+
34
41
  return t.identifier(name)
35
42
  }