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