Skip to content

Commit 1a349e7

Browse files
committed
fix: track title side effects
1 parent e3357d5 commit 1a349e7

File tree

5 files changed

+66
-9
lines changed

5 files changed

+66
-9
lines changed

packages/unhead/src/client/renderDOMHead.ts

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
5454
// let's hydrate - fill the elMap for fast lookups
5555
if (!state) {
5656
state = {
57+
title: dom.title,
5758
elMap: new Map()
5859
.set('htmlAttrs', dom.documentElement)
5960
.set('bodyAttrs', dom.body),
@@ -197,6 +198,7 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
197198
// 1. render tags which don't create a new element
198199
if (tag.tag === 'title') {
199200
dom.title = tag.textContent as string
201+
track('title', '', () => dom.title = state.title)
200202
continue
201203
}
202204
ctx.$el = ctx.$el || state.elMap.get(id)

packages/unhead/src/types/head.ts

+1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export interface Unhead<Input = ResolvableHead> {
231231
}
232232

233233
export interface DomState {
234+
title: string
234235
pendingSideEffects: SideEffectsRecord
235236
sideEffects: SideEffectsRecord
236237
elMap: Map<string, Element | Element[]>

packages/unhead/src/unhead.ts

+20-8
Original file line numberDiff line numberDiff line change
@@ -146,17 +146,29 @@ export function createUnhead<T = ResolvableHead>(resolvedOptions: CreateHeadOpti
146146
}, ctx.tagMap)
147147

148148
const title = ctx.tagMap.get('title')
149-
const titleTemplate = ctx.tagMap.get('titleTemplate')?.textContent
149+
const titleTemplate = ctx.tagMap.get('titleTemplate')
150150
head._title = title?.textContent
151-
head._titleTemplate = typeof titleTemplate === 'string' ? titleTemplate : undefined
152151

153-
if (titleTemplate && title) {
154-
// @ts-expect-error todo
155-
let newTitle = typeof titleTemplate === 'function' ? titleTemplate(title.textContent) : titleTemplate
156-
if (typeof newTitle === 'string' && !head.plugins.has('template-params')) {
157-
newTitle = newTitle.replace('%s', title.textContent || '')
152+
if (titleTemplate) {
153+
const titleTemplateFn = titleTemplate?.textContent
154+
head._titleTemplate = typeof titleTemplateFn === 'string' ? titleTemplateFn : undefined
155+
if (titleTemplateFn) {
156+
// @ts-expect-error todo
157+
let newTitle = (typeof titleTemplateFn === 'function' ? titleTemplateFn(title?.textContent) : titleTemplateFn)
158+
if (typeof newTitle === 'string' && !head.plugins.has('template-params')) {
159+
newTitle = newTitle.replace('%s', title?.textContent || '')
160+
}
161+
if (title) {
162+
newTitle === null
163+
? ctx.tagMap.delete('title')
164+
: ctx.tagMap.set('title', { ...title, textContent: newTitle })
165+
}
166+
else {
167+
// convert title template to a title
168+
titleTemplate.tag = 'title'
169+
titleTemplate.textContent = newTitle
170+
}
158171
}
159-
newTitle === null ? ctx.tagMap.delete('title') : ctx.tagMap.set('title', { ...title, textContent: newTitle })
160172
}
161173
// merge _tags into one map
162174
ctx.tags = Array.from(ctx.tagMap!.values())

packages/vue/test/unit/dom/titleTemplate.test.ts

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ import { useDom } from '../../../../unhead/test/fixtures'
77
import { csrVueAppWithUnhead } from '../../util'
88

99
describe('vue dom titleTemplate', () => {
10+
it('fn replace', async () => {
11+
const dom = useDom()
12+
const head = csrVueAppWithUnhead(dom, () => {}, {
13+
init: [
14+
{
15+
titleTemplate: () => 'Test',
16+
},
17+
],
18+
})
19+
20+
await renderDOMHead(head, { document: dom.window.document })
21+
22+
expect(dom.window.document.title).toMatchInlineSnapshot(
23+
`"Test"`,
24+
)
25+
})
26+
1027
it('basic', async () => {
1128
const dom = useDom()
1229
const head = csrVueAppWithUnhead(dom, () => {
@@ -25,4 +42,29 @@ describe('vue dom titleTemplate', () => {
2542
<body><div id="app" data-v-app=""><div>hello world</div></div></body></html>"
2643
`)
2744
})
45+
46+
it('remove', async () => {
47+
const dom = useDom()
48+
let entry
49+
const head = csrVueAppWithUnhead(dom, () => {
50+
entry = useHead({
51+
title: 'test',
52+
})
53+
}, {
54+
})
55+
56+
await renderDOMHead(head, { document: dom.window.document })
57+
58+
expect(dom.window.document.title).toMatchInlineSnapshot(
59+
`"test"`,
60+
)
61+
62+
entry!.dispose()
63+
64+
await renderDOMHead(head, { document: dom.window.document })
65+
66+
expect(dom.window.document.title).toMatchInlineSnapshot(
67+
`""`,
68+
)
69+
})
2870
})

packages/vue/test/unit/e2e/basic.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,6 @@ describe('vue e2e', () => {
419419
home.dispose()
420420

421421
await renderDOMHead(csrHead, { document: dom.window.document })
422-
expect(dom.window.document.title).toMatchInlineSnapshot(`"Home Page | Company"`)
422+
expect(dom.window.document.title).toMatchInlineSnapshot(`"| Company"`)
423423
})
424424
})

0 commit comments

Comments
 (0)