@tanstack/router-plugin 1.167.1 → 1.167.3

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 (60) hide show
  1. package/dist/cjs/core/code-splitter/compilers.cjs +63 -34
  2. package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
  3. package/dist/cjs/core/code-splitter/plugins/framework-plugins.cjs +7 -1
  4. package/dist/cjs/core/code-splitter/plugins/framework-plugins.cjs.map +1 -1
  5. package/dist/cjs/core/code-splitter/plugins/react-refresh-ignored-route-exports.cjs +49 -0
  6. package/dist/cjs/core/code-splitter/plugins/react-refresh-ignored-route-exports.cjs.map +1 -0
  7. package/dist/cjs/core/code-splitter/plugins/react-refresh-ignored-route-exports.d.cts +2 -0
  8. package/dist/cjs/core/code-splitter/plugins/react-refresh-route-components.cjs +24 -12
  9. package/dist/cjs/core/code-splitter/plugins/react-refresh-route-components.cjs.map +1 -1
  10. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.cjs +41 -0
  11. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.cjs.map +1 -0
  12. package/dist/cjs/core/code-splitter/plugins/react-stable-hmr-split-route-components.d.cts +2 -0
  13. package/dist/cjs/core/code-splitter/plugins.d.cts +13 -0
  14. package/dist/cjs/core/code-splitter/types.d.cts +9 -0
  15. package/dist/cjs/core/route-hmr-statement.cjs +58 -15
  16. package/dist/cjs/core/route-hmr-statement.cjs.map +1 -1
  17. package/dist/cjs/core/route-hmr-statement.d.cts +1 -1
  18. package/dist/cjs/core/router-code-splitter-plugin.cjs +3 -3
  19. package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
  20. package/dist/cjs/core/router-hmr-plugin.cjs +2 -2
  21. package/dist/cjs/core/router-hmr-plugin.cjs.map +1 -1
  22. package/dist/cjs/core/utils.cjs +9 -1
  23. package/dist/cjs/core/utils.cjs.map +1 -1
  24. package/dist/cjs/core/utils.d.cts +1 -0
  25. package/dist/esm/core/code-splitter/compilers.js +64 -35
  26. package/dist/esm/core/code-splitter/compilers.js.map +1 -1
  27. package/dist/esm/core/code-splitter/plugins/framework-plugins.js +7 -1
  28. package/dist/esm/core/code-splitter/plugins/framework-plugins.js.map +1 -1
  29. package/dist/esm/core/code-splitter/plugins/react-refresh-ignored-route-exports.d.ts +2 -0
  30. package/dist/esm/core/code-splitter/plugins/react-refresh-ignored-route-exports.js +46 -0
  31. package/dist/esm/core/code-splitter/plugins/react-refresh-ignored-route-exports.js.map +1 -0
  32. package/dist/esm/core/code-splitter/plugins/react-refresh-route-components.js +25 -13
  33. package/dist/esm/core/code-splitter/plugins/react-refresh-route-components.js.map +1 -1
  34. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.d.ts +2 -0
  35. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.js +38 -0
  36. package/dist/esm/core/code-splitter/plugins/react-stable-hmr-split-route-components.js.map +1 -0
  37. package/dist/esm/core/code-splitter/plugins.d.ts +13 -0
  38. package/dist/esm/core/code-splitter/types.d.ts +9 -0
  39. package/dist/esm/core/route-hmr-statement.d.ts +1 -1
  40. package/dist/esm/core/route-hmr-statement.js +58 -15
  41. package/dist/esm/core/route-hmr-statement.js.map +1 -1
  42. package/dist/esm/core/router-code-splitter-plugin.js +3 -3
  43. package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
  44. package/dist/esm/core/router-hmr-plugin.js +3 -3
  45. package/dist/esm/core/router-hmr-plugin.js.map +1 -1
  46. package/dist/esm/core/utils.d.ts +1 -0
  47. package/dist/esm/core/utils.js +9 -2
  48. package/dist/esm/core/utils.js.map +1 -1
  49. package/package.json +4 -4
  50. package/src/core/code-splitter/compilers.ts +118 -62
  51. package/src/core/code-splitter/plugins/framework-plugins.ts +7 -1
  52. package/src/core/code-splitter/plugins/react-refresh-ignored-route-exports.ts +65 -0
  53. package/src/core/code-splitter/plugins/react-refresh-route-components.ts +68 -39
  54. package/src/core/code-splitter/plugins/react-stable-hmr-split-route-components.ts +56 -0
  55. package/src/core/code-splitter/plugins.ts +18 -0
  56. package/src/core/code-splitter/types.ts +11 -0
  57. package/src/core/route-hmr-statement.ts +141 -25
  58. package/src/core/router-code-splitter-plugin.ts +2 -2
  59. package/src/core/router-hmr-plugin.ts +7 -6
  60. package/src/core/utils.ts +27 -2
@@ -8,7 +8,8 @@ import {
8
8
  parseAst,
9
9
  } from '@tanstack/router-utils'
10
10
  import { tsrShared, tsrSplit } from '../constants'
11
- import { routeHmrStatement } from '../route-hmr-statement'
11
+ import { createRouteHmrStatement } from '../route-hmr-statement'
12
+ import { getObjectPropertyKeyName } from '../utils'
12
13
  import { createIdentifier } from './path-ids'
13
14
  import { getFrameworkOptions } from './framework-options'
14
15
  import type {
@@ -18,14 +19,8 @@ import type {
18
19
  import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
19
20
  import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants'
20
21
  import type { Config, DeletableNodes } from '../config'
22
+ import type { SplitNodeMeta } from './types'
21
23
 
22
- type SplitNodeMeta = {
23
- routeIdent: SplitRouteIdentNodes
24
- splitStrategy: 'lazyFn' | 'lazyRouteComponent'
25
- localImporterIdent: string
26
- exporterIdent: string
27
- localExporterIdent: string
28
- }
29
24
  const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
30
25
  [
31
26
  'loader',
@@ -78,6 +73,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
78
73
  },
79
74
  ],
80
75
  ])
76
+
81
77
  const KNOWN_SPLIT_ROUTE_IDENTS = [...SPLIT_NODES_CONFIG.keys()] as const
82
78
 
83
79
  function addSplitSearchParamToFilename(
@@ -297,10 +293,12 @@ export function computeSharedBindings(opts: {
297
293
  const splitGroupsPresent = new Set<number>()
298
294
  let hasNonSplit = false
299
295
  for (const prop of routeOptions.properties) {
300
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue
301
- if (prop.key.name === 'codeSplitGroupings') continue
296
+ if (!t.isObjectProperty(prop)) continue
297
+ const key = getObjectPropertyKeyName(prop)
298
+ if (!key) continue
299
+ if (key === 'codeSplitGroupings') continue
302
300
  if (t.isIdentifier(prop.value) && prop.value.name === 'undefined') continue
303
- const groupIndex = findIndexForSplitNode(prop.key.name) // -1 if non-split
301
+ const groupIndex = findIndexForSplitNode(key) // -1 if non-split
304
302
  if (groupIndex === -1) {
305
303
  hasNonSplit = true
306
304
  } else {
@@ -333,8 +331,9 @@ export function computeSharedBindings(opts: {
333
331
  const refsByGroup = new Map<string, Set<number>>()
334
332
 
335
333
  for (const prop of routeOptions.properties) {
336
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue
337
- const key = prop.key.name
334
+ if (!t.isObjectProperty(prop)) continue
335
+ const key = getObjectPropertyKeyName(prop)
336
+ if (!key) continue
338
337
 
339
338
  if (key === 'codeSplitGroupings') continue
340
339
 
@@ -665,6 +664,13 @@ export function compileCodeSplitReferenceRoute(
665
664
  const PACKAGE = frameworkOptions.package
666
665
  const LAZY_ROUTE_COMPONENT_IDENT = frameworkOptions.idents.lazyRouteComponent
667
666
  const LAZY_FN_IDENT = frameworkOptions.idents.lazyFn
667
+ const stableRouteOptionKeys = [
668
+ ...new Set(
669
+ (opts.compilerPlugins ?? []).flatMap(
670
+ (plugin) => plugin.getStableRouteOptionKeys?.() ?? [],
671
+ ),
672
+ ),
673
+ ]
668
674
 
669
675
  let createRouteFn: string
670
676
 
@@ -702,16 +708,48 @@ export function compileCodeSplitReferenceRoute(
702
708
  return programPath.scope.hasBinding(name)
703
709
  }
704
710
 
711
+ const addRouteHmr = (
712
+ insertionPath: babel.NodePath,
713
+ routeOptions: t.ObjectExpression,
714
+ ) => {
715
+ if (!opts.addHmr || hmrAdded) {
716
+ return
717
+ }
718
+
719
+ opts.compilerPlugins?.forEach((plugin) => {
720
+ const pluginResult = plugin.onAddHmr?.({
721
+ programPath,
722
+ callExpressionPath: path,
723
+ insertionPath,
724
+ routeOptions,
725
+ createRouteFn,
726
+ opts: opts as CompileCodeSplitReferenceRouteOptions,
727
+ })
728
+
729
+ if (pluginResult?.modified) {
730
+ modified = true
731
+ }
732
+ })
733
+
734
+ programPath.pushContainer(
735
+ 'body',
736
+ createRouteHmrStatement(stableRouteOptionKeys),
737
+ )
738
+ modified = true
739
+ hmrAdded = true
740
+ }
741
+
705
742
  if (t.isObjectExpression(routeOptions)) {
743
+ const insertionPath = path.getStatementParent() ?? path
744
+
706
745
  if (opts.deleteNodes && opts.deleteNodes.size > 0) {
707
746
  routeOptions.properties = routeOptions.properties.filter(
708
747
  (prop) => {
709
748
  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
- }
749
+ const key = getObjectPropertyKeyName(prop)
750
+ if (key && opts.deleteNodes!.has(key as any)) {
751
+ modified = true
752
+ return false
715
753
  }
716
754
  }
717
755
  return true
@@ -719,8 +757,6 @@ export function compileCodeSplitReferenceRoute(
719
757
  )
720
758
  }
721
759
  if (!splittableCreateRouteFns.includes(createRouteFn)) {
722
- const insertionPath = path.getStatementParent() ?? path
723
-
724
760
  opts.compilerPlugins?.forEach((plugin) => {
725
761
  const pluginResult = plugin.onUnsplittableRoute?.({
726
762
  programPath,
@@ -737,19 +773,15 @@ export function compileCodeSplitReferenceRoute(
737
773
  })
738
774
 
739
775
  // we can't split this route but we still add HMR handling if enabled
740
- if (opts.addHmr && !hmrAdded) {
741
- programPath.pushContainer('body', routeHmrStatement)
742
- modified = true
743
- hmrAdded = true
744
- }
776
+ addRouteHmr(insertionPath, routeOptions)
745
777
  // exit traversal so this route is not split
746
778
  return programPath.stop()
747
779
  }
748
780
  routeOptions.properties.forEach((prop) => {
749
781
  if (t.isObjectProperty(prop)) {
750
- if (t.isIdentifier(prop.key)) {
751
- const key = prop.key.name
782
+ const key = getObjectPropertyKeyName(prop)
752
783
 
784
+ if (key) {
753
785
  // If the user has not specified a split grouping for this key
754
786
  // then we should not split it
755
787
  const codeSplitGroupingByKey = findIndexForSplitNode(key)
@@ -858,16 +890,42 @@ export function compileCodeSplitReferenceRoute(
858
890
  ])
859
891
  }
860
892
 
861
- prop.value = template.expression(
862
- `${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
863
- )()
893
+ const insertionPath = path.getStatementParent() ?? path
894
+ let splitPropValue: t.Expression | undefined
895
+
896
+ for (const plugin of opts.compilerPlugins ?? []) {
897
+ const pluginPropValue = plugin.onSplitRouteProperty?.(
898
+ {
899
+ programPath,
900
+ callExpressionPath: path,
901
+ insertionPath,
902
+ routeOptions,
903
+ prop,
904
+ splitNodeMeta,
905
+ lazyRouteComponentIdent:
906
+ LAZY_ROUTE_COMPONENT_IDENT,
907
+ },
908
+ )
909
+
910
+ if (!pluginPropValue) {
911
+ continue
912
+ }
864
913
 
865
- // add HMR handling
866
- if (opts.addHmr && !hmrAdded) {
867
- programPath.pushContainer('body', routeHmrStatement)
868
914
  modified = true
869
- hmrAdded = true
915
+ splitPropValue = pluginPropValue
916
+ break
917
+ }
918
+
919
+ if (splitPropValue) {
920
+ prop.value = splitPropValue
921
+ } else {
922
+ prop.value = template.expression(
923
+ `${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
924
+ )()
870
925
  }
926
+
927
+ // add HMR handling
928
+ addRouteHmr(insertionPath, routeOptions)
871
929
  } else {
872
930
  // if (splitNodeMeta.splitStrategy === 'lazyFn') {
873
931
  const value = prop.value
@@ -937,6 +995,8 @@ export function compileCodeSplitReferenceRoute(
937
995
 
938
996
  programPath.scope.crawl()
939
997
  })
998
+
999
+ addRouteHmr(insertionPath, routeOptions)
940
1000
  }
941
1001
  }
942
1002
 
@@ -1128,10 +1188,7 @@ export function compileCodeSplitVirtualRoute(
1128
1188
  // since we have special considerations that need
1129
1189
  // to be accounted for like (not splitting exported identifiers)
1130
1190
  KNOWN_SPLIT_ROUTE_IDENTS.forEach((splitType) => {
1131
- if (
1132
- !t.isIdentifier(prop.key) ||
1133
- prop.key.name !== splitType
1134
- ) {
1191
+ if (getObjectPropertyKeyName(prop) !== splitType) {
1135
1192
  return
1136
1193
  }
1137
1194
 
@@ -1673,33 +1730,32 @@ export function detectCodeSplitGroupingsFromRoute(opts: ParseAstOptions): {
1673
1730
  if (t.isObjectExpression(routeOptions)) {
1674
1731
  routeOptions.properties.forEach((prop) => {
1675
1732
  if (t.isObjectProperty(prop)) {
1676
- if (t.isIdentifier(prop.key)) {
1677
- if (prop.key.name === 'codeSplitGroupings') {
1678
- const value = prop.value
1733
+ const key = getObjectPropertyKeyName(prop)
1734
+ if (key === 'codeSplitGroupings') {
1735
+ const value = prop.value
1736
+
1737
+ if (t.isArrayExpression(value)) {
1738
+ codeSplitGroupings = value.elements.map((group) => {
1739
+ if (t.isArrayExpression(group)) {
1740
+ return group.elements.map((node) => {
1741
+ if (!t.isStringLiteral(node)) {
1742
+ throw new Error(
1743
+ 'You must provide a string literal for the codeSplitGroupings',
1744
+ )
1745
+ }
1746
+
1747
+ return node.value
1748
+ }) as Array<SplitRouteIdentNodes>
1749
+ }
1679
1750
 
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
1751
  throw new Error(
1700
- 'You must provide an array of arrays for the codeSplitGroupings.',
1752
+ 'You must provide arrays with codeSplitGroupings options.',
1701
1753
  )
1702
- }
1754
+ })
1755
+ } else {
1756
+ throw new Error(
1757
+ 'You must provide an array of arrays for the codeSplitGroupings.',
1758
+ )
1703
1759
  }
1704
1760
  }
1705
1761
  }
@@ -1,4 +1,6 @@
1
+ import { createReactRefreshIgnoredRouteExportsPlugin } from './react-refresh-ignored-route-exports'
1
2
  import { createReactRefreshRouteComponentsPlugin } from './react-refresh-route-components'
3
+ import { createReactStableHmrSplitRouteComponentsPlugin } from './react-stable-hmr-split-route-components'
2
4
  import type { ReferenceRouteCompilerPlugin } from '../plugins'
3
5
  import type { Config } from '../../config'
4
6
 
@@ -9,7 +11,11 @@ export function getReferenceRouteCompilerPlugins(opts: {
9
11
  switch (opts.targetFramework) {
10
12
  case 'react': {
11
13
  if (opts.addHmr) {
12
- return [createReactRefreshRouteComponentsPlugin()]
14
+ return [
15
+ createReactRefreshIgnoredRouteExportsPlugin(),
16
+ createReactRefreshRouteComponentsPlugin(),
17
+ createReactStableHmrSplitRouteComponentsPlugin(),
18
+ ]
13
19
  }
14
20
  return undefined
15
21
  }
@@ -0,0 +1,65 @@
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
+ const buildReactRefreshIgnoredRouteExportsStatement = template.statement(
7
+ `
8
+ if (import.meta.hot && typeof window !== 'undefined') {
9
+ const tsrReactRefresh = window.__TSR_REACT_REFRESH__ ??= (() => {
10
+ const ignoredExportsById = new Map()
11
+ const previousGetIgnoredExports = window.__getReactRefreshIgnoredExports
12
+
13
+ window.__getReactRefreshIgnoredExports = (ctx) => {
14
+ const ignoredExports = previousGetIgnoredExports?.(ctx) ?? []
15
+ const moduleIgnored = ignoredExportsById.get(ctx.id) ?? []
16
+ return [...ignoredExports, ...moduleIgnored]
17
+ }
18
+
19
+ return {
20
+ ignoredExportsById,
21
+ }
22
+ })()
23
+
24
+ tsrReactRefresh.ignoredExportsById.set(%%moduleId%%, ['Route'])
25
+ }
26
+ `,
27
+ { syntacticPlaceholders: true },
28
+ )
29
+
30
+ /**
31
+ * A trivial component-shaped export that gives `@vitejs/plugin-react` a valid
32
+ * Fast Refresh boundary. Without at least one non-ignored component export,
33
+ * the module would be invalidated (full page reload) on every update even
34
+ * though our custom route HMR handler already manages the update.
35
+ */
36
+ const buildRefreshAnchorStatement = template.statement(
37
+ `export function %%anchorName%%() { return null }`,
38
+ { syntacticPlaceholders: true },
39
+ )
40
+
41
+ export function createReactRefreshIgnoredRouteExportsPlugin(): ReferenceRouteCompilerPlugin {
42
+ return {
43
+ name: 'react-refresh-ignored-route-exports',
44
+ onAddHmr(ctx) {
45
+ const anchorName = getUniqueProgramIdentifier(
46
+ ctx.programPath,
47
+ 'TSRFastRefreshAnchor',
48
+ )
49
+
50
+ ctx.programPath.pushContainer(
51
+ 'body',
52
+ buildReactRefreshIgnoredRouteExportsStatement({
53
+ moduleId: t.stringLiteral(ctx.opts.id),
54
+ }),
55
+ )
56
+
57
+ ctx.programPath.pushContainer(
58
+ 'body',
59
+ buildRefreshAnchorStatement({ anchorName }),
60
+ )
61
+
62
+ return { modified: true }
63
+ },
64
+ }
65
+ }
@@ -1,63 +1,92 @@
1
1
  import * as t from '@babel/types'
2
- import { getUniqueProgramIdentifier } from '../../utils'
2
+ import {
3
+ getObjectPropertyKeyName,
4
+ getUniqueProgramIdentifier,
5
+ } from '../../utils'
3
6
  import type { ReferenceRouteCompilerPlugin } from '../plugins'
4
7
 
5
8
  const REACT_REFRESH_ROUTE_COMPONENT_IDENTS = new Set([
6
9
  'component',
10
+ 'shellComponent',
7
11
  'pendingComponent',
8
12
  'errorComponent',
9
13
  'notFoundComponent',
10
14
  ])
11
15
 
16
+ function hoistInlineRouteComponents(ctx: {
17
+ programPath: Parameters<typeof getUniqueProgramIdentifier>[0]
18
+ insertionPath: { insertBefore: (nodes: Array<t.VariableDeclaration>) => void }
19
+ routeOptions: t.ObjectExpression
20
+ }) {
21
+ const hoistedDeclarations: Array<t.VariableDeclaration> = []
22
+
23
+ ctx.routeOptions.properties.forEach((prop) => {
24
+ if (!t.isObjectProperty(prop)) {
25
+ return
26
+ }
27
+
28
+ const key = getObjectPropertyKeyName(prop)
29
+
30
+ if (!key || !REACT_REFRESH_ROUTE_COMPONENT_IDENTS.has(key)) {
31
+ return
32
+ }
33
+
34
+ if (
35
+ !t.isArrowFunctionExpression(prop.value) &&
36
+ !t.isFunctionExpression(prop.value)
37
+ ) {
38
+ return
39
+ }
40
+
41
+ const hoistedIdentifier = getUniqueProgramIdentifier(
42
+ ctx.programPath,
43
+ `TSR${key[0]!.toUpperCase()}${key.slice(1)}`,
44
+ )
45
+
46
+ hoistedDeclarations.push(
47
+ t.variableDeclaration('const', [
48
+ t.variableDeclarator(hoistedIdentifier, t.cloneNode(prop.value, true)),
49
+ ]),
50
+ )
51
+
52
+ prop.value = t.cloneNode(hoistedIdentifier)
53
+ })
54
+
55
+ if (hoistedDeclarations.length === 0) {
56
+ return false
57
+ }
58
+
59
+ ctx.insertionPath.insertBefore(hoistedDeclarations)
60
+ return true
61
+ }
62
+
12
63
  export function createReactRefreshRouteComponentsPlugin(): ReferenceRouteCompilerPlugin {
13
64
  return {
14
65
  name: 'react-refresh-route-components',
66
+ getStableRouteOptionKeys() {
67
+ return [...REACT_REFRESH_ROUTE_COMPONENT_IDENTS]
68
+ },
15
69
  onUnsplittableRoute(ctx) {
16
70
  if (!ctx.opts.addHmr) {
17
71
  return
18
72
  }
19
73
 
20
- const hoistedDeclarations: Array<t.VariableDeclaration> = []
21
-
22
- ctx.routeOptions.properties.forEach((prop) => {
23
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) {
24
- return
25
- }
26
-
27
- if (!REACT_REFRESH_ROUTE_COMPONENT_IDENTS.has(prop.key.name)) {
28
- return
29
- }
30
-
31
- if (
32
- !t.isArrowFunctionExpression(prop.value) &&
33
- !t.isFunctionExpression(prop.value)
34
- ) {
35
- return
36
- }
37
-
38
- const hoistedIdentifier = getUniqueProgramIdentifier(
39
- ctx.programPath,
40
- `TSR${prop.key.name[0]!.toUpperCase()}${prop.key.name.slice(1)}`,
41
- )
42
-
43
- hoistedDeclarations.push(
44
- t.variableDeclaration('const', [
45
- t.variableDeclarator(
46
- hoistedIdentifier,
47
- t.cloneNode(prop.value, true),
48
- ),
49
- ]),
50
- )
51
-
52
- prop.value = t.cloneNode(hoistedIdentifier)
53
- })
54
-
55
- if (hoistedDeclarations.length === 0) {
74
+ if (hoistInlineRouteComponents(ctx)) {
75
+ return { modified: true }
76
+ }
77
+
78
+ return
79
+ },
80
+ onAddHmr(ctx) {
81
+ if (!ctx.opts.addHmr) {
56
82
  return
57
83
  }
58
84
 
59
- ctx.insertionPath.insertBefore(hoistedDeclarations)
60
- return { modified: true }
85
+ if (hoistInlineRouteComponents(ctx)) {
86
+ return { modified: true }
87
+ }
88
+
89
+ return
61
90
  },
62
91
  }
63
92
  }
@@ -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,13 +23,30 @@ 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
  }
28
39
 
29
40
  export type ReferenceRouteCompilerPlugin = {
30
41
  name: string
42
+ getStableRouteOptionKeys?: () => Array<string>
43
+ onAddHmr?: (
44
+ ctx: ReferenceRouteCompilerPluginContext,
45
+ ) => void | ReferenceRouteCompilerPluginResult
31
46
  onUnsplittableRoute?: (
32
47
  ctx: ReferenceRouteCompilerPluginContext,
33
48
  ) => void | ReferenceRouteCompilerPluginResult
49
+ onSplitRouteProperty?: (
50
+ ctx: ReferenceRouteSplitPropertyCompilerPluginContext,
51
+ ) => void | t.Expression
34
52
  }
@@ -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
+ }