@depup/tanstack__react-router 1.167.4-depup.0 → 1.168.1-depup.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 (91) hide show
  1. package/README.md +3 -4
  2. package/changes.json +2 -6
  3. package/dist/cjs/Match.cjs +118 -52
  4. package/dist/cjs/Match.cjs.map +1 -1
  5. package/dist/cjs/Matches.cjs +20 -20
  6. package/dist/cjs/Matches.cjs.map +1 -1
  7. package/dist/cjs/Scripts.cjs +36 -32
  8. package/dist/cjs/Scripts.cjs.map +1 -1
  9. package/dist/cjs/Transitioner.cjs +10 -16
  10. package/dist/cjs/Transitioner.cjs.map +1 -1
  11. package/dist/cjs/headContentUtils.cjs +147 -59
  12. package/dist/cjs/headContentUtils.cjs.map +1 -1
  13. package/dist/cjs/index.cjs +1 -1
  14. package/dist/cjs/index.dev.cjs +1 -1
  15. package/dist/cjs/link.cjs +34 -29
  16. package/dist/cjs/link.cjs.map +1 -1
  17. package/dist/cjs/not-found.cjs +20 -2
  18. package/dist/cjs/not-found.cjs.map +1 -1
  19. package/dist/cjs/router.cjs +2 -1
  20. package/dist/cjs/router.cjs.map +1 -1
  21. package/dist/cjs/routerStores.cjs +21 -0
  22. package/dist/cjs/routerStores.cjs.map +1 -0
  23. package/dist/cjs/routerStores.d.cts +7 -0
  24. package/dist/cjs/ssr/RouterClient.cjs +1 -1
  25. package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
  26. package/dist/cjs/ssr/renderRouterToStream.cjs +2 -2
  27. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
  28. package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
  29. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
  30. package/dist/cjs/useCanGoBack.cjs +7 -2
  31. package/dist/cjs/useCanGoBack.cjs.map +1 -1
  32. package/dist/cjs/useLocation.cjs +21 -2
  33. package/dist/cjs/useLocation.cjs.map +1 -1
  34. package/dist/cjs/useMatch.cjs +29 -9
  35. package/dist/cjs/useMatch.cjs.map +1 -1
  36. package/dist/cjs/useRouterState.cjs +2 -2
  37. package/dist/cjs/useRouterState.cjs.map +1 -1
  38. package/dist/esm/Match.js +118 -52
  39. package/dist/esm/Match.js.map +1 -1
  40. package/dist/esm/Matches.js +21 -21
  41. package/dist/esm/Matches.js.map +1 -1
  42. package/dist/esm/Scripts.js +36 -32
  43. package/dist/esm/Scripts.js.map +1 -1
  44. package/dist/esm/Transitioner.js +10 -16
  45. package/dist/esm/Transitioner.js.map +1 -1
  46. package/dist/esm/headContentUtils.js +148 -60
  47. package/dist/esm/headContentUtils.js.map +1 -1
  48. package/dist/esm/index.dev.js +1 -1
  49. package/dist/esm/index.js +1 -1
  50. package/dist/esm/link.js +34 -29
  51. package/dist/esm/link.js.map +1 -1
  52. package/dist/esm/not-found.js +20 -2
  53. package/dist/esm/not-found.js.map +1 -1
  54. package/dist/esm/router.js +2 -1
  55. package/dist/esm/router.js.map +1 -1
  56. package/dist/esm/routerStores.d.ts +7 -0
  57. package/dist/esm/routerStores.js +20 -0
  58. package/dist/esm/routerStores.js.map +1 -0
  59. package/dist/esm/ssr/RouterClient.js +1 -1
  60. package/dist/esm/ssr/RouterClient.js.map +1 -1
  61. package/dist/esm/ssr/renderRouterToStream.js +2 -2
  62. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  63. package/dist/esm/ssr/renderRouterToString.js +1 -1
  64. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  65. package/dist/esm/useCanGoBack.js +6 -2
  66. package/dist/esm/useCanGoBack.js.map +1 -1
  67. package/dist/esm/useLocation.js +20 -2
  68. package/dist/esm/useLocation.js.map +1 -1
  69. package/dist/esm/useMatch.js +29 -9
  70. package/dist/esm/useMatch.js.map +1 -1
  71. package/dist/esm/useRouterState.js +2 -2
  72. package/dist/esm/useRouterState.js.map +1 -1
  73. package/dist/llms/rules/api.d.ts +1 -1
  74. package/dist/llms/rules/api.js +3 -9
  75. package/package.json +11 -13
  76. package/src/Match.tsx +218 -78
  77. package/src/Matches.tsx +45 -25
  78. package/src/Scripts.tsx +72 -44
  79. package/src/Transitioner.tsx +24 -16
  80. package/src/headContentUtils.tsx +210 -27
  81. package/src/link.tsx +66 -71
  82. package/src/not-found.tsx +41 -4
  83. package/src/router.ts +2 -1
  84. package/src/routerStores.ts +26 -0
  85. package/src/ssr/RouterClient.tsx +1 -1
  86. package/src/ssr/renderRouterToStream.tsx +2 -2
  87. package/src/ssr/renderRouterToString.tsx +1 -1
  88. package/src/useCanGoBack.ts +14 -2
  89. package/src/useLocation.tsx +32 -5
  90. package/src/useMatch.tsx +61 -21
  91. package/src/useRouterState.tsx +4 -2
package/src/Scripts.tsx CHANGED
@@ -1,5 +1,7 @@
1
+ import { useStore } from '@tanstack/react-store'
2
+ import { deepEqual } from '@tanstack/router-core'
3
+ import { isServer } from '@tanstack/router-core/isServer'
1
4
  import { Asset } from './Asset'
2
- import { useRouterState } from './useRouterState'
3
5
  import { useRouter } from './useRouter'
4
6
  import type { RouterManagedTag } from '@tanstack/router-core'
5
7
 
@@ -10,54 +12,80 @@ import type { RouterManagedTag } from '@tanstack/router-core'
10
12
  export const Scripts = () => {
11
13
  const router = useRouter()
12
14
  const nonce = router.options.ssr?.nonce
13
- const assetScripts = useRouterState({
14
- select: (state) => {
15
- const assetScripts: Array<RouterManagedTag> = []
16
- const manifest = router.ssr?.manifest
17
15
 
18
- if (!manifest) {
19
- return []
20
- }
16
+ const getAssetScripts = (matches: Array<any>) => {
17
+ const assetScripts: Array<RouterManagedTag> = []
18
+ const manifest = router.ssr?.manifest
21
19
 
22
- state.matches
23
- .map((match) => router.looseRoutesById[match.routeId]!)
24
- .forEach((route) =>
25
- manifest.routes[route.id]?.assets
26
- ?.filter((d) => d.tag === 'script')
27
- .forEach((asset) => {
28
- assetScripts.push({
29
- tag: 'script',
30
- attrs: { ...asset.attrs, nonce },
31
- children: asset.children,
32
- } as any)
33
- }),
34
- )
20
+ if (!manifest) {
21
+ return []
22
+ }
35
23
 
36
- return assetScripts
37
- },
38
- structuralSharing: true as any,
39
- })
24
+ matches
25
+ .map((match) => router.looseRoutesById[match.routeId]!)
26
+ .forEach((route) =>
27
+ manifest.routes[route.id]?.assets
28
+ ?.filter((d) => d.tag === 'script')
29
+ .forEach((asset) => {
30
+ assetScripts.push({
31
+ tag: 'script',
32
+ attrs: { ...asset.attrs, nonce },
33
+ children: asset.children,
34
+ } as any)
35
+ }),
36
+ )
40
37
 
41
- const { scripts } = useRouterState({
42
- select: (state) => ({
43
- scripts: (
44
- state.matches
45
- .map((match) => match.scripts!)
46
- .flat(1)
47
- .filter(Boolean) as Array<RouterManagedTag>
48
- ).map(({ children, ...script }) => ({
49
- tag: 'script',
50
- attrs: {
51
- ...script,
52
- suppressHydrationWarning: true,
53
- nonce,
54
- },
55
- children,
56
- })),
57
- }),
58
- structuralSharing: true as any,
59
- })
38
+ return assetScripts
39
+ }
40
+
41
+ const getScripts = (matches: Array<any>): Array<RouterManagedTag> =>
42
+ (
43
+ matches
44
+ .map((match) => match.scripts!)
45
+ .flat(1)
46
+ .filter(Boolean) as Array<RouterManagedTag>
47
+ ).map(
48
+ ({ children, ...script }) =>
49
+ ({
50
+ tag: 'script',
51
+ attrs: {
52
+ ...script,
53
+ suppressHydrationWarning: true,
54
+ nonce,
55
+ },
56
+ children,
57
+ }) satisfies RouterManagedTag,
58
+ )
59
+
60
+ if (isServer ?? router.isServer) {
61
+ const assetScripts = getAssetScripts(
62
+ router.stores.activeMatchesSnapshot.state,
63
+ )
64
+ const scripts = getScripts(router.stores.activeMatchesSnapshot.state)
65
+ return renderScripts(router, scripts, assetScripts)
66
+ }
67
+
68
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
69
+ const assetScripts = useStore(
70
+ router.stores.activeMatchesSnapshot,
71
+ getAssetScripts,
72
+ deepEqual,
73
+ )
74
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
75
+ const scripts = useStore(
76
+ router.stores.activeMatchesSnapshot,
77
+ getScripts,
78
+ deepEqual,
79
+ )
80
+
81
+ return renderScripts(router, scripts, assetScripts)
82
+ }
60
83
 
84
+ function renderScripts(
85
+ router: ReturnType<typeof useRouter>,
86
+ scripts: Array<RouterManagedTag>,
87
+ assetScripts: Array<RouterManagedTag>,
88
+ ) {
61
89
  let serverBufferedScript: RouterManagedTag | undefined = undefined
62
90
 
63
91
  if (router.serverSsr) {
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react'
2
+ import { batch, useStore } from '@tanstack/react-store'
2
3
  import {
3
4
  getLocationChangeInfo,
4
5
  handleHashScroll,
@@ -6,7 +7,6 @@ import {
6
7
  } from '@tanstack/router-core'
7
8
  import { useLayoutEffect, usePrevious } from './utils'
8
9
  import { useRouter } from './useRouter'
9
- import { useRouterState } from './useRouterState'
10
10
 
11
11
  export function Transitioner() {
12
12
  const router = useRouter()
@@ -14,13 +14,11 @@ export function Transitioner() {
14
14
 
15
15
  const [isTransitioning, setIsTransitioning] = React.useState(false)
16
16
  // Track pending state changes
17
- const { hasPendingMatches, isLoading } = useRouterState({
18
- select: (s) => ({
19
- isLoading: s.isLoading,
20
- hasPendingMatches: s.matches.some((d) => d.status === 'pending'),
21
- }),
22
- structuralSharing: true,
23
- })
17
+ const isLoading = useStore(router.stores.isLoading, (value) => value)
18
+ const hasPendingMatches = useStore(
19
+ router.stores.hasPendingMatches,
20
+ (value) => value,
21
+ )
24
22
 
25
23
  const previousIsLoading = usePrevious(isLoading)
26
24
 
@@ -95,7 +93,10 @@ export function Transitioner() {
95
93
  if (previousIsLoading && !isLoading) {
96
94
  router.emit({
97
95
  type: 'onLoad', // When the new URL has committed, when the new matches have been loaded into state.matches
98
- ...getLocationChangeInfo(router.state),
96
+ ...getLocationChangeInfo(
97
+ router.stores.location.state,
98
+ router.stores.resolvedLocation.state,
99
+ ),
99
100
  })
100
101
  }
101
102
  }, [previousIsLoading, router, isLoading])
@@ -105,24 +106,31 @@ export function Transitioner() {
105
106
  if (previousIsPagePending && !isPagePending) {
106
107
  router.emit({
107
108
  type: 'onBeforeRouteMount',
108
- ...getLocationChangeInfo(router.state),
109
+ ...getLocationChangeInfo(
110
+ router.stores.location.state,
111
+ router.stores.resolvedLocation.state,
112
+ ),
109
113
  })
110
114
  }
111
115
  }, [isPagePending, previousIsPagePending, router])
112
116
 
113
117
  useLayoutEffect(() => {
114
118
  if (previousIsAnyPending && !isAnyPending) {
115
- const changeInfo = getLocationChangeInfo(router.state)
119
+ const changeInfo = getLocationChangeInfo(
120
+ router.stores.location.state,
121
+ router.stores.resolvedLocation.state,
122
+ )
116
123
  router.emit({
117
124
  type: 'onResolved',
118
125
  ...changeInfo,
119
126
  })
120
127
 
121
- router.__store.setState((s: typeof router.state) => ({
122
- ...s,
123
- status: 'idle',
124
- resolvedLocation: s.location,
125
- }))
128
+ batch(() => {
129
+ router.stores.status.setState(() => 'idle')
130
+ router.stores.resolvedLocation.setState(
131
+ () => router.stores.location.state,
132
+ )
133
+ })
126
134
 
127
135
  if (changeInfo.hrefChanged) {
128
136
  handleHashScroll(router)
@@ -1,9 +1,171 @@
1
1
  import * as React from 'react'
2
- import { escapeHtml } from '@tanstack/router-core'
2
+ import { useStore } from '@tanstack/react-store'
3
+ import { deepEqual, escapeHtml } from '@tanstack/router-core'
4
+ import { isServer } from '@tanstack/router-core/isServer'
3
5
  import { useRouter } from './useRouter'
4
- import { useRouterState } from './useRouterState'
5
6
  import type { RouterManagedTag } from '@tanstack/router-core'
6
7
 
8
+ function buildTagsFromMatches(
9
+ router: ReturnType<typeof useRouter>,
10
+ nonce: string | undefined,
11
+ matches: Array<any>,
12
+ ): Array<RouterManagedTag> {
13
+ const routeMeta = matches.map((match) => match.meta!).filter(Boolean)
14
+
15
+ const resultMeta: Array<RouterManagedTag> = []
16
+ const metaByAttribute: Record<string, true> = {}
17
+ let title: RouterManagedTag | undefined
18
+ for (let i = routeMeta.length - 1; i >= 0; i--) {
19
+ const metas = routeMeta[i]!
20
+ for (let j = metas.length - 1; j >= 0; j--) {
21
+ const m = metas[j]
22
+ if (!m) continue
23
+
24
+ if (m.title) {
25
+ if (!title) {
26
+ title = {
27
+ tag: 'title',
28
+ children: m.title,
29
+ }
30
+ }
31
+ } else if ('script:ld+json' in m) {
32
+ try {
33
+ const json = JSON.stringify(m['script:ld+json'])
34
+ resultMeta.push({
35
+ tag: 'script',
36
+ attrs: {
37
+ type: 'application/ld+json',
38
+ },
39
+ children: escapeHtml(json),
40
+ })
41
+ } catch {
42
+ // Skip invalid JSON-LD objects
43
+ }
44
+ } else {
45
+ const attribute = m.name ?? m.property
46
+ if (attribute) {
47
+ if (metaByAttribute[attribute]) {
48
+ continue
49
+ } else {
50
+ metaByAttribute[attribute] = true
51
+ }
52
+ }
53
+
54
+ resultMeta.push({
55
+ tag: 'meta',
56
+ attrs: {
57
+ ...m,
58
+ nonce,
59
+ },
60
+ })
61
+ }
62
+ }
63
+ }
64
+
65
+ if (title) {
66
+ resultMeta.push(title)
67
+ }
68
+
69
+ if (nonce) {
70
+ resultMeta.push({
71
+ tag: 'meta',
72
+ attrs: {
73
+ property: 'csp-nonce',
74
+ content: nonce,
75
+ },
76
+ })
77
+ }
78
+ resultMeta.reverse()
79
+
80
+ const constructedLinks = matches
81
+ .map((match) => match.links!)
82
+ .filter(Boolean)
83
+ .flat(1)
84
+ .map((link) => ({
85
+ tag: 'link',
86
+ attrs: {
87
+ ...link,
88
+ nonce,
89
+ },
90
+ })) satisfies Array<RouterManagedTag>
91
+
92
+ const manifest = router.ssr?.manifest
93
+ const assetLinks = matches
94
+ .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
95
+ .filter(Boolean)
96
+ .flat(1)
97
+ .filter((asset) => asset.tag === 'link')
98
+ .map(
99
+ (asset) =>
100
+ ({
101
+ tag: 'link',
102
+ attrs: {
103
+ ...asset.attrs,
104
+ suppressHydrationWarning: true,
105
+ nonce,
106
+ },
107
+ }) satisfies RouterManagedTag,
108
+ )
109
+
110
+ const preloadLinks: Array<RouterManagedTag> = []
111
+ matches
112
+ .map((match) => router.looseRoutesById[match.routeId]!)
113
+ .forEach((route) =>
114
+ router.ssr?.manifest?.routes[route.id]?.preloads
115
+ ?.filter(Boolean)
116
+ .forEach((preload) => {
117
+ preloadLinks.push({
118
+ tag: 'link',
119
+ attrs: {
120
+ rel: 'modulepreload',
121
+ href: preload,
122
+ nonce,
123
+ },
124
+ })
125
+ }),
126
+ )
127
+
128
+ const styles = (
129
+ matches
130
+ .map((match) => match.styles!)
131
+ .flat(1)
132
+ .filter(Boolean) as Array<RouterManagedTag>
133
+ ).map(({ children, ...attrs }) => ({
134
+ tag: 'style',
135
+ attrs: {
136
+ ...attrs,
137
+ nonce,
138
+ },
139
+ children,
140
+ }))
141
+
142
+ const headScripts = (
143
+ matches
144
+ .map((match) => match.headScripts!)
145
+ .flat(1)
146
+ .filter(Boolean) as Array<RouterManagedTag>
147
+ ).map(({ children, ...script }) => ({
148
+ tag: 'script',
149
+ attrs: {
150
+ ...script,
151
+ nonce,
152
+ },
153
+ children,
154
+ }))
155
+
156
+ return uniqBy(
157
+ [
158
+ ...resultMeta,
159
+ ...preloadLinks,
160
+ ...constructedLinks,
161
+ ...assetLinks,
162
+ ...styles,
163
+ ...headScripts,
164
+ ] as Array<RouterManagedTag>,
165
+ (d) => JSON.stringify(d),
166
+ )
167
+ }
168
+
7
169
  /**
8
170
  * Build the list of head/link/meta/script tags to render for active matches.
9
171
  * Used internally by `HeadContent`.
@@ -11,12 +173,25 @@ import type { RouterManagedTag } from '@tanstack/router-core'
11
173
  export const useTags = () => {
12
174
  const router = useRouter()
13
175
  const nonce = router.options.ssr?.nonce
14
- const routeMeta = useRouterState({
15
- select: (state) => {
16
- return state.matches.map((match) => match.meta!).filter(Boolean)
176
+
177
+ if (isServer ?? router.isServer) {
178
+ return buildTagsFromMatches(
179
+ router,
180
+ nonce,
181
+ router.stores.activeMatchesSnapshot.state,
182
+ )
183
+ }
184
+
185
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
186
+ const routeMeta = useStore(
187
+ router.stores.activeMatchesSnapshot,
188
+ (matches) => {
189
+ return matches.map((match) => match.meta!).filter(Boolean)
17
190
  },
18
- })
191
+ deepEqual,
192
+ )
19
193
 
194
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
20
195
  const meta: Array<RouterManagedTag> = React.useMemo(() => {
21
196
  const resultMeta: Array<RouterManagedTag> = []
22
197
  const metaByAttribute: Record<string, true> = {}
@@ -88,9 +263,11 @@ export const useTags = () => {
88
263
  return resultMeta
89
264
  }, [routeMeta, nonce])
90
265
 
91
- const links = useRouterState({
92
- select: (state) => {
93
- const constructed = state.matches
266
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
267
+ const links = useStore(
268
+ router.stores.activeMatchesSnapshot,
269
+ (matches) => {
270
+ const constructed = matches
94
271
  .map((match) => match.links!)
95
272
  .filter(Boolean)
96
273
  .flat(1)
@@ -106,7 +283,7 @@ export const useTags = () => {
106
283
 
107
284
  // These are the assets extracted from the ViteManifest
108
285
  // using the `startManifestPlugin`
109
- const assets = state.matches
286
+ const assets = matches
110
287
  .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
111
288
  .filter(Boolean)
112
289
  .flat(1)
@@ -125,14 +302,16 @@ export const useTags = () => {
125
302
 
126
303
  return [...constructed, ...assets]
127
304
  },
128
- structuralSharing: true as any,
129
- })
305
+ deepEqual,
306
+ )
130
307
 
131
- const preloadLinks = useRouterState({
132
- select: (state) => {
308
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
309
+ const preloadLinks = useStore(
310
+ router.stores.activeMatchesSnapshot,
311
+ (matches) => {
133
312
  const preloadLinks: Array<RouterManagedTag> = []
134
313
 
135
- state.matches
314
+ matches
136
315
  .map((match) => router.looseRoutesById[match.routeId]!)
137
316
  .forEach((route) =>
138
317
  router.ssr?.manifest?.routes[route.id]?.preloads
@@ -151,13 +330,15 @@ export const useTags = () => {
151
330
 
152
331
  return preloadLinks
153
332
  },
154
- structuralSharing: true as any,
155
- })
333
+ deepEqual,
334
+ )
156
335
 
157
- const styles = useRouterState({
158
- select: (state) =>
336
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
337
+ const styles = useStore(
338
+ router.stores.activeMatchesSnapshot,
339
+ (matches) =>
159
340
  (
160
- state.matches
341
+ matches
161
342
  .map((match) => match.styles!)
162
343
  .flat(1)
163
344
  .filter(Boolean) as Array<RouterManagedTag>
@@ -169,13 +350,15 @@ export const useTags = () => {
169
350
  },
170
351
  children,
171
352
  })),
172
- structuralSharing: true as any,
173
- })
353
+ deepEqual,
354
+ )
174
355
 
175
- const headScripts: Array<RouterManagedTag> = useRouterState({
176
- select: (state) =>
356
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
357
+ const headScripts: Array<RouterManagedTag> = useStore(
358
+ router.stores.activeMatchesSnapshot,
359
+ (matches) =>
177
360
  (
178
- state.matches
361
+ matches
179
362
  .map((match) => match.headScripts!)
180
363
  .flat(1)
181
364
  .filter(Boolean) as Array<RouterManagedTag>
@@ -187,8 +370,8 @@ export const useTags = () => {
187
370
  },
188
371
  children,
189
372
  })),
190
- structuralSharing: true as any,
191
- })
373
+ deepEqual,
374
+ )
192
375
 
193
376
  return uniqBy(
194
377
  [