@tanstack/vue-router 1.147.0 → 1.147.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 (41) hide show
  1. package/dist/esm/HeadContent.d.ts +0 -2
  2. package/dist/esm/HeadContent.dev.d.ts +10 -0
  3. package/dist/esm/HeadContent.dev.js +26 -0
  4. package/dist/esm/HeadContent.dev.js.map +1 -0
  5. package/dist/esm/HeadContent.js +3 -134
  6. package/dist/esm/HeadContent.js.map +1 -1
  7. package/dist/esm/headContentUtils.d.ts +3 -0
  8. package/dist/esm/headContentUtils.js +128 -0
  9. package/dist/esm/headContentUtils.js.map +1 -0
  10. package/dist/esm/index.d.ts +1 -0
  11. package/dist/esm/index.dev.d.ts +2 -0
  12. package/dist/esm/index.dev.js +146 -0
  13. package/dist/esm/index.dev.js.map +1 -0
  14. package/dist/esm/index.js +3 -1
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/ssr/RouterServer.js +1 -1
  17. package/dist/esm/ssr/RouterServer.js.map +1 -1
  18. package/dist/source/HeadContent.d.ts +0 -2
  19. package/dist/source/HeadContent.dev.d.ts +10 -0
  20. package/dist/source/HeadContent.dev.jsx +35 -0
  21. package/dist/source/HeadContent.dev.jsx.map +1 -0
  22. package/dist/source/HeadContent.jsx +2 -165
  23. package/dist/source/HeadContent.jsx.map +1 -1
  24. package/dist/source/headContentUtils.d.ts +3 -0
  25. package/dist/source/headContentUtils.jsx +150 -0
  26. package/dist/source/headContentUtils.jsx.map +1 -0
  27. package/dist/source/index.d.ts +1 -0
  28. package/dist/source/index.dev.d.ts +2 -0
  29. package/dist/source/index.dev.jsx +6 -0
  30. package/dist/source/index.dev.jsx.map +1 -0
  31. package/dist/source/index.jsx +1 -0
  32. package/dist/source/index.jsx.map +1 -1
  33. package/dist/source/ssr/RouterServer.jsx +1 -1
  34. package/dist/source/ssr/RouterServer.jsx.map +1 -1
  35. package/package.json +3 -2
  36. package/src/HeadContent.dev.tsx +42 -0
  37. package/src/HeadContent.tsx +2 -195
  38. package/src/headContentUtils.tsx +176 -0
  39. package/src/index.dev.tsx +6 -0
  40. package/src/index.tsx +1 -0
  41. package/src/ssr/RouterServer.tsx +1 -1
@@ -1,181 +1,7 @@
1
1
  import * as Vue from 'vue'
2
2
 
3
- import { escapeHtml } from '@tanstack/router-core'
4
3
  import { Asset } from './Asset'
5
- import { useRouter } from './useRouter'
6
- import { useRouterState } from './useRouterState'
7
- import type { RouterManagedTag } from '@tanstack/router-core'
8
-
9
- /**
10
- * Renders a stylesheet link for dev mode CSS collection.
11
- * On the server, renders the full link with route-scoped CSS URL.
12
- * On the client, renders the same link to avoid hydration mismatch,
13
- * then removes it after hydration since Vite's HMR handles CSS updates.
14
- */
15
- const DevStylesLink = Vue.defineComponent({
16
- name: 'DevStylesLink',
17
- setup() {
18
- const routeIds = useRouterState({
19
- select: (state) => state.matches.map((match) => match.routeId),
20
- })
21
-
22
- Vue.onMounted(() => {
23
- // After hydration, remove the SSR-rendered dev styles link
24
- document
25
- .querySelectorAll('[data-tanstack-start-dev-styles]')
26
- .forEach((el) => el.remove())
27
- })
28
-
29
- // Build the same href on both server and client for hydration match
30
- const href = Vue.computed(
31
- () =>
32
- `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.value.join(','))}`,
33
- )
34
-
35
- return () =>
36
- Vue.h('link', {
37
- rel: 'stylesheet',
38
- href: href.value,
39
- 'data-tanstack-start-dev-styles': true,
40
- })
41
- },
42
- })
43
-
44
- export const useTags = () => {
45
- const router = useRouter()
46
-
47
- const routeMeta = useRouterState({
48
- select: (state) => {
49
- return state.matches.map((match) => match.meta!).filter(Boolean)
50
- },
51
- })
52
-
53
- const meta: Vue.Ref<Array<RouterManagedTag>> = Vue.computed(() => {
54
- const resultMeta: Array<RouterManagedTag> = []
55
- const metaByAttribute: Record<string, true> = {}
56
- let title: RouterManagedTag | undefined
57
- ;[...routeMeta.value].reverse().forEach((metas) => {
58
- ;[...metas].reverse().forEach((m) => {
59
- if (!m) return
60
-
61
- if (m.title) {
62
- if (!title) {
63
- title = {
64
- tag: 'title',
65
- children: m.title,
66
- }
67
- }
68
- } else if ('script:ld+json' in m) {
69
- // Handle JSON-LD structured data
70
- // Content is HTML-escaped to prevent XSS when injected via innerHTML
71
- try {
72
- const json = JSON.stringify(m['script:ld+json'])
73
- resultMeta.push({
74
- tag: 'script',
75
- attrs: {
76
- type: 'application/ld+json',
77
- },
78
- children: escapeHtml(json),
79
- })
80
- } catch {
81
- // Skip invalid JSON-LD objects
82
- }
83
- } else {
84
- const attribute = m.name ?? m.property
85
- if (attribute) {
86
- if (metaByAttribute[attribute]) {
87
- return
88
- } else {
89
- metaByAttribute[attribute] = true
90
- }
91
- }
92
-
93
- resultMeta.push({
94
- tag: 'meta',
95
- attrs: {
96
- ...m,
97
- },
98
- })
99
- }
100
- })
101
- })
102
-
103
- if (title) {
104
- resultMeta.push(title)
105
- }
106
-
107
- resultMeta.reverse()
108
-
109
- return resultMeta
110
- })
111
-
112
- const links = useRouterState({
113
- select: (state) =>
114
- state.matches
115
- .map((match) => match.links!)
116
- .filter(Boolean)
117
- .flat(1)
118
- .map((link) => ({
119
- tag: 'link',
120
- attrs: {
121
- ...link,
122
- },
123
- })) as Array<RouterManagedTag>,
124
- })
125
-
126
- const preloadMeta = useRouterState({
127
- select: (state) => {
128
- const preloadMeta: Array<RouterManagedTag> = []
129
-
130
- state.matches
131
- .map((match) => router.looseRoutesById[match.routeId]!)
132
- .forEach((route) =>
133
- router.ssr?.manifest?.routes[route.id]?.preloads
134
- ?.filter(Boolean)
135
- .forEach((preload) => {
136
- preloadMeta.push({
137
- tag: 'link',
138
- attrs: {
139
- rel: 'modulepreload',
140
- href: preload,
141
- },
142
- })
143
- }),
144
- )
145
-
146
- return preloadMeta
147
- },
148
- })
149
-
150
- const headScripts = useRouterState({
151
- select: (state) =>
152
- (
153
- state.matches
154
- .map((match) => match.headScripts!)
155
- .flat(1)
156
- .filter(Boolean) as Array<RouterManagedTag>
157
- ).map(({ children, ...script }) => ({
158
- tag: 'script',
159
- attrs: {
160
- ...script,
161
- },
162
- children,
163
- })),
164
- })
165
-
166
- return () =>
167
- uniqBy(
168
- [
169
- ...meta.value,
170
- ...preloadMeta.value,
171
- ...links.value,
172
- ...headScripts.value,
173
- ] as Array<RouterManagedTag>,
174
- (d) => {
175
- return JSON.stringify(d)
176
- },
177
- )
178
- }
4
+ import { useTags } from './headContentUtils'
179
5
 
180
6
  /**
181
7
  * @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route.
@@ -187,31 +13,12 @@ export const HeadContent = Vue.defineComponent({
187
13
  const tags = useTags()
188
14
 
189
15
  return () => {
190
- const children = tags().map((tag) =>
16
+ return tags().map((tag) =>
191
17
  Vue.h(Asset, {
192
18
  ...tag,
193
19
  key: `tsr-meta-${JSON.stringify(tag)}`,
194
20
  }),
195
21
  )
196
-
197
- // In dev mode, prepend the DevStylesLink
198
- if (process.env.NODE_ENV !== 'production') {
199
- return [Vue.h(DevStylesLink), ...children]
200
- }
201
-
202
- return children
203
22
  }
204
23
  },
205
24
  })
206
-
207
- function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
208
- const seen = new Set<string>()
209
- return arr.filter((item) => {
210
- const key = fn(item)
211
- if (seen.has(key)) {
212
- return false
213
- }
214
- seen.add(key)
215
- return true
216
- })
217
- }
@@ -0,0 +1,176 @@
1
+ import * as Vue from 'vue'
2
+
3
+ import { escapeHtml } from '@tanstack/router-core'
4
+ import { useRouter } from './useRouter'
5
+ import { useRouterState } from './useRouterState'
6
+ import type { RouterManagedTag } from '@tanstack/router-core'
7
+
8
+ export const useTags = () => {
9
+ const router = useRouter()
10
+
11
+ const routeMeta = useRouterState({
12
+ select: (state) => {
13
+ return state.matches.map((match) => match.meta!).filter(Boolean)
14
+ },
15
+ })
16
+
17
+ const meta: Vue.Ref<Array<RouterManagedTag>> = Vue.computed(() => {
18
+ const resultMeta: Array<RouterManagedTag> = []
19
+ const metaByAttribute: Record<string, true> = {}
20
+ let title: RouterManagedTag | undefined
21
+ ;[...routeMeta.value].reverse().forEach((metas) => {
22
+ ;[...metas].reverse().forEach((m) => {
23
+ if (!m) return
24
+
25
+ if (m.title) {
26
+ if (!title) {
27
+ title = {
28
+ tag: 'title',
29
+ children: m.title,
30
+ }
31
+ }
32
+ } else if ('script:ld+json' in m) {
33
+ // Handle JSON-LD structured data
34
+ // Content is HTML-escaped to prevent XSS when injected via innerHTML
35
+ try {
36
+ const json = JSON.stringify(m['script:ld+json'])
37
+ resultMeta.push({
38
+ tag: 'script',
39
+ attrs: {
40
+ type: 'application/ld+json',
41
+ },
42
+ children: escapeHtml(json),
43
+ })
44
+ } catch {
45
+ // Skip invalid JSON-LD objects
46
+ }
47
+ } else {
48
+ const attribute = m.name ?? m.property
49
+ if (attribute) {
50
+ if (metaByAttribute[attribute]) {
51
+ return
52
+ } else {
53
+ metaByAttribute[attribute] = true
54
+ }
55
+ }
56
+
57
+ resultMeta.push({
58
+ tag: 'meta',
59
+ attrs: {
60
+ ...m,
61
+ },
62
+ })
63
+ }
64
+ })
65
+ })
66
+
67
+ if (title) {
68
+ resultMeta.push(title)
69
+ }
70
+
71
+ resultMeta.reverse()
72
+
73
+ return resultMeta
74
+ })
75
+
76
+ const links = useRouterState({
77
+ select: (state) =>
78
+ state.matches
79
+ .map((match) => match.links!)
80
+ .filter(Boolean)
81
+ .flat(1)
82
+ .map((link) => ({
83
+ tag: 'link',
84
+ attrs: {
85
+ ...link,
86
+ },
87
+ })) as Array<RouterManagedTag>,
88
+ })
89
+
90
+ const preloadMeta = useRouterState({
91
+ select: (state) => {
92
+ const preloadMeta: Array<RouterManagedTag> = []
93
+
94
+ state.matches
95
+ .map((match) => router.looseRoutesById[match.routeId]!)
96
+ .forEach((route) =>
97
+ router.ssr?.manifest?.routes[route.id]?.preloads
98
+ ?.filter(Boolean)
99
+ .forEach((preload) => {
100
+ preloadMeta.push({
101
+ tag: 'link',
102
+ attrs: {
103
+ rel: 'modulepreload',
104
+ href: preload,
105
+ },
106
+ })
107
+ }),
108
+ )
109
+
110
+ return preloadMeta
111
+ },
112
+ })
113
+
114
+ const headScripts = useRouterState({
115
+ select: (state) =>
116
+ (
117
+ state.matches
118
+ .map((match) => match.headScripts!)
119
+ .flat(1)
120
+ .filter(Boolean) as Array<RouterManagedTag>
121
+ ).map(({ children, ...script }) => ({
122
+ tag: 'script',
123
+ attrs: {
124
+ ...script,
125
+ },
126
+ children,
127
+ })),
128
+ })
129
+
130
+ const manifestAssets = useRouterState({
131
+ select: (state) => {
132
+ const manifest = router.ssr?.manifest
133
+
134
+ const assets = state.matches
135
+ .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
136
+ .filter(Boolean)
137
+ .flat(1)
138
+ .filter((asset) => asset.tag === 'link')
139
+ .map(
140
+ (asset) =>
141
+ ({
142
+ tag: 'link',
143
+ attrs: { ...asset.attrs },
144
+ }) satisfies RouterManagedTag,
145
+ )
146
+
147
+ return assets
148
+ },
149
+ })
150
+
151
+ return () =>
152
+ uniqBy(
153
+ [
154
+ ...manifestAssets.value,
155
+ ...meta.value,
156
+ ...preloadMeta.value,
157
+ ...links.value,
158
+ ...headScripts.value,
159
+ ] as Array<RouterManagedTag>,
160
+ (d) => {
161
+ return JSON.stringify(d)
162
+ },
163
+ )
164
+ }
165
+
166
+ export function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
167
+ const seen = new Set<string>()
168
+ return arr.filter((item) => {
169
+ const key = fn(item)
170
+ if (seen.has(key)) {
171
+ return false
172
+ }
173
+ seen.add(key)
174
+ return true
175
+ })
176
+ }
@@ -0,0 +1,6 @@
1
+ // Development entry point - re-exports everything from index.tsx
2
+ // but overrides HeadContent with the dev version that handles
3
+ // dev styles cleanup after hydration
4
+
5
+ export * from './index'
6
+ export { HeadContent } from './HeadContent.dev'
package/src/index.tsx CHANGED
@@ -339,6 +339,7 @@ export type {
339
339
  export { ScriptOnce } from './ScriptOnce'
340
340
  export { Asset } from './Asset'
341
341
  export { HeadContent } from './HeadContent'
342
+ export { useTags } from './headContentUtils'
342
343
  export { Scripts } from './Scripts'
343
344
  export { Body } from './Body'
344
345
  export { Html } from './Html'
@@ -1,6 +1,6 @@
1
1
  import * as Vue from 'vue'
2
2
  import { Asset } from '../Asset'
3
- import { useTags } from '../HeadContent'
3
+ import { useTags } from '../headContentUtils'
4
4
  import { RouterProvider } from '../RouterProvider'
5
5
  import { Scripts } from '../Scripts'
6
6
  import type { AnyRouter, RouterManagedTag } from '@tanstack/router-core'