Skip to content

Commit a255df0

Browse files
committedMar 11, 2025
fix(vue): broken reactivity
1 parent c795edc commit a255df0

File tree

9 files changed

+119
-48
lines changed

9 files changed

+119
-48
lines changed
 

‎packages/schema-org/build.config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,12 @@ export default defineBuildConfig({
2525
'unplugin-vue-components',
2626
'vue',
2727
'@vue/runtime-core',
28+
'@unhead/react',
29+
'@unhead/solid-js',
30+
'@unhead/svelte',
31+
'@unhead/vue',
32+
'unhead',
33+
'unhead/utils',
34+
'unhead/plugins',
2835
],
2936
})

‎packages/schema-org/package.json

-21
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,6 @@
7676
"lint": "eslint \"{src,test}/**/*.{ts,vue,json,yml}\" --fix",
7777
"test:attw": "attw --pack"
7878
},
79-
"peerDependencies": {
80-
"@unhead/react": "^2",
81-
"@unhead/solid-js": "^2",
82-
"@unhead/svelte": "^2",
83-
"@unhead/vue": "^2",
84-
"unhead": "^2"
85-
},
86-
"peerDependenciesMeta": {
87-
"@unhead/react": {
88-
"optional": true
89-
},
90-
"@unhead/solid-js": {
91-
"optional": true
92-
},
93-
"@unhead/svelte": {
94-
"optional": true
95-
},
96-
"@unhead/vue": {
97-
"optional": true
98-
}
99-
},
10079
"dependencies": {
10180
"defu": "^6.1.4",
10281
"ohash": "^2.0.10",

‎packages/schema-org/test/vue/vue.test.ts

+70-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { defineArticle, defineWebPage, useSchemaOrg } from '@unhead/schema-org/vue'
1+
import { defineArticle, defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org/vue'
22
import { useHead } from '@unhead/vue'
33
import { createHead as createClientHead, renderDOMHead } from '@unhead/vue/client'
44
import { renderSSRHead } from '@unhead/vue/server'
55
import { describe, expect, it } from 'vitest'
6+
import { computed, ref } from 'vue'
67
import { useDom } from '../../../unhead/test/fixtures'
8+
import { createHead as createServerHead } from '../../../vue/src/server'
79
import { ssrVueAppWithUnhead } from '../../../vue/test/util'
810

911
describe('schema.org e2e', () => {
@@ -150,4 +152,71 @@ describe('schema.org e2e', () => {
150152
}
151153
`)
152154
})
155+
it('ref simple', async () => {
156+
const head = createServerHead({
157+
disableDefaults: true,
158+
})
159+
useSchemaOrg([
160+
defineWebSite(ref({
161+
name: 'Test',
162+
})),
163+
], { head })
164+
165+
const data = await renderSSRHead(head)
166+
expect(data.bodyTags).toMatchInlineSnapshot(`
167+
"<script type="application/ld+json" data-hid="schema-org-graph">{
168+
"@context": "https://schema.org",
169+
"@graph": [
170+
{
171+
"@id": "#/schema//d006e97",
172+
"name": "Test"
173+
}
174+
]
175+
}</script>"
176+
`)
177+
})
178+
179+
it('refs', async () => {
180+
const head = createServerHead({
181+
disableDefaults: true,
182+
init: [
183+
{
184+
templateParams: {
185+
schemaOrg: computed(() => {
186+
return {
187+
inLanguage: ref('foo'),
188+
}
189+
}),
190+
},
191+
},
192+
],
193+
})
194+
useSchemaOrg([
195+
defineWebPage(computed(() => ({
196+
name: ref('test'),
197+
foo: computed(() => 'bar'),
198+
}))),
199+
defineWebSite(ref({
200+
name: 'Test',
201+
})),
202+
], { head })
203+
204+
const data = await renderSSRHead(head)
205+
expect(data.bodyTags).toMatchInlineSnapshot(`
206+
"<script type="application/ld+json" data-hid="schema-org-graph">{
207+
"@context": "https://schema.org",
208+
"@graph": [
209+
{
210+
"@id": "#/schema//6b94a87",
211+
"foo": "bar",
212+
"name": "test"
213+
},
214+
{
215+
"@id": "#/schema//d006e97",
216+
"name": "Test"
217+
}
218+
]
219+
}</script>"
220+
`)
221+
})
153222
})

‎packages/unhead/src/server/createHead.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function createHead<T = ResolvableHead>(options: CreateServerHeadOptions
1212
if (k && k.startsWith('on') && typeof v === 'function') {
1313
return `this.dataset.${k}fired = true`
1414
}
15+
return v
1516
},
1617
],
1718
init: [

‎packages/unhead/src/types/head.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export interface ActiveHeadEntry<Input> {
9393
_poll: (rm?: boolean) => void
9494
}
9595

96-
export type PropResolver = (key: string, value: any, tag?: HeadTag) => any
96+
export type PropResolver = (key?: string, value?: any, tag?: HeadTag) => any
9797

9898
export interface CreateHeadOptions {
9999
document?: Document

‎packages/unhead/src/utils/normalize.ts

+11-13
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ function normalizeTag(tagName: HeadTag['tag'], _input: HeadTag['props'] | string
112112
: { [(tagName === 'script' || tagName === 'noscript' || tagName === 'style') ? 'innerHTML' : 'textContent']: _input }
113113

114114
const tag = normalizeProps({ tag: tagName, props: {} }, input)
115-
116115
if (tag.key && DupeableTags.has(tag.tag)) {
117116
tag.props['data-hid'] = tag._h = tag.key
118117
}
@@ -131,22 +130,21 @@ export function normalizeEntryToTags(input: any, propResolvers: PropResolver[]):
131130
if (!input) {
132131
return []
133132
}
134-
135-
const tags: (HeadTag | HeadTag[])[] = []
136-
137133
if (typeof input === 'function') {
138-
return normalizeEntryToTags(input(), propResolvers)
134+
input = input()
139135
}
140-
input = walkResolver(input, (key, val) => {
141-
let res = val
136+
const resolvers = (key?: string, val?: any) => {
142137
for (let i = 0; i < propResolvers.length; i++) {
143-
const v = propResolvers[i](key, res)
144-
if (v !== undefined)
145-
res = v
138+
val = propResolvers[i](key, val)
146139
}
147-
return res
148-
})
149-
Object.entries(input).forEach(([key, value]) => {
140+
return val
141+
}
142+
input = resolvers(undefined, input)
143+
144+
const tags: (HeadTag | HeadTag[])[] = []
145+
146+
input = walkResolver(input, resolvers)
147+
Object.entries(input || {}).forEach(([key, value]) => {
150148
if (value === undefined)
151149
return
152150
for (const v of (Array.isArray(value) ? value : [value]))

‎packages/unhead/src/utils/walkResolver.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@ export function walkResolver(val: any, resolve?: PropResolver, key?: string): an
44
// Combined primitive type check
55
const type = typeof val
66

7-
let v: any
8-
if (!resolve || !key) {
9-
v = type === 'function' ? val() : val
10-
}
11-
else if (resolve && (key === 'titleTemplate' || (key[0] === 'o' && key[1] === 'n'))) {
12-
v = resolve(key, val)
13-
}
14-
else if (resolve) {
15-
v = type === 'function' ? resolve(key, val()) : resolve(key, val)
7+
if (type === 'function') {
8+
if (!key || (key !== 'titleTemplate' && !(key[0] === 'o' && key[1] === 'n'))) {
9+
val = val()
10+
}
1611
}
17-
12+
const v = resolve?.(key, val) || val
1813
if (Array.isArray(v)) {
1914
return v.map(r => walkResolver(r, resolve))
2015
}

‎packages/vue/test/unit/ssr/examples.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,26 @@ describe('vue ssr examples', () => {
147147
}
148148
`)
149149
})
150+
it('https://github.com/vite-pwa/vite-plugin-pwa/discussions/832', async () => {
151+
const head = await ssrVueAppWithUnhead(() => {
152+
useHead(ref({
153+
link: [
154+
{
155+
rel: 'manifest',
156+
href: '/manifest.webmanifest',
157+
},
158+
],
159+
}))
160+
})
161+
const ctx = await renderSSRHead(head)
162+
expect(ctx).toMatchInlineSnapshot(`
163+
{
164+
"bodyAttrs": "",
165+
"bodyTags": "",
166+
"bodyTagsOpen": "",
167+
"headTags": "<link rel="manifest" href="/manifest.webmanifest">",
168+
"htmlAttrs": "",
169+
}
170+
`)
171+
})
150172
})

‎packages/vue/test/util.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ export function csrVueAppWithUnhead(dom: JSDOM, fn: () => void | Promise<void>,
3030
return head
3131
}
3232

33-
export async function ssrVueAppWithUnhead(fn: () => void | Promise<void>, options?: CreateHeadOptions) {
33+
export async function ssrVueAppWithUnhead(fn: (head: ReturnType<typeof createServerHead>) => void | Promise<void>, options?: CreateHeadOptions) {
3434
const head = createServerHead({
3535
disableDefaults: true,
3636
...options,
3737
})
3838
const app = createSSRApp({
3939
async setup() {
40-
fn()
40+
fn(head)
4141
return () => '<div>hi</div>'
4242
},
4343
})

0 commit comments

Comments
 (0)
Please sign in to comment.