Skip to content

Commit f7e9cfe

Browse files
authored
fix: de-duplicate head tags while merging (#975) (#976)
1 parent 65ec9d7 commit f7e9cfe

File tree

4 files changed

+40
-48
lines changed

4 files changed

+40
-48
lines changed

src/client/app/composables/head.ts

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { watchEffect, Ref } from 'vue'
2-
import { HeadConfig, SiteData, createTitle } from '../../shared'
2+
import { HeadConfig, SiteData, createTitle, mergeHead } from '../../shared'
33
import { Route } from '../router'
44

55
export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
@@ -14,50 +14,20 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
1414
return
1515
}
1616

17-
const newEls: HTMLElement[] = []
18-
const commonLength = Math.min(managedHeadTags.length, newTags.length)
19-
for (let i = 0; i < commonLength; i++) {
20-
let el = managedHeadTags[i]
21-
const [tag, attrs, innerHTML = ''] = newTags[i]
22-
if (el.tagName.toLocaleLowerCase() === tag) {
23-
for (const key in attrs) {
24-
if (el.getAttribute(key) !== attrs[key]) {
25-
el.setAttribute(key, attrs[key])
26-
}
27-
}
28-
for (let i = 0; i < el.attributes.length; i++) {
29-
const name = el.attributes[i].name
30-
if (!(name in attrs)) {
31-
el.removeAttribute(name)
32-
}
33-
}
34-
if (el.innerHTML !== innerHTML) {
35-
el.innerHTML = innerHTML
36-
}
37-
} else {
38-
document.head.removeChild(el)
39-
el = createHeadElement(newTags[i])
40-
document.head.append(el)
41-
}
42-
newEls.push(el)
43-
}
44-
45-
managedHeadTags
46-
.slice(commonLength)
47-
.forEach((el) => document.head.removeChild(el))
48-
newTags.slice(commonLength).forEach((headConfig) => {
17+
managedHeadTags.forEach((el) => document.head.removeChild(el))
18+
managedHeadTags = []
19+
newTags.forEach((headConfig) => {
4920
const el = createHeadElement(headConfig)
5021
document.head.appendChild(el)
51-
newEls.push(el)
22+
managedHeadTags.push(el)
5223
})
53-
managedHeadTags = newEls
5424
}
5525

5626
watchEffect(() => {
5727
const pageData = route.data
5828
const siteData = siteDataByRouteRef.value
5929
const pageDescription = pageData && pageData.description
60-
const frontmatterHead = pageData && pageData.frontmatter.head
30+
const frontmatterHead = (pageData && pageData.frontmatter.head) || []
6131

6232
// update title and description
6333
document.title = createTitle(siteData, pageData)
@@ -66,11 +36,9 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
6636
.querySelector(`meta[name=description]`)!
6737
.setAttribute('content', pageDescription || siteData.description)
6838

69-
updateHeadTags([
70-
// site head can only change during dev
71-
...(import.meta.env.DEV ? siteData.head : []),
72-
...(frontmatterHead ? filterOutHeadDescription(frontmatterHead) : [])
73-
])
39+
updateHeadTags(
40+
mergeHead(siteData.head, filterOutHeadDescription(frontmatterHead))
41+
)
7442
})
7543
}
7644

src/node/build/render.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { pathToFileURL } from 'url'
55
import escape from 'escape-html'
66
import { normalizePath, transformWithEsbuild } from 'vite'
77
import { RollupOutput, OutputChunk, OutputAsset } from 'rollup'
8-
import { HeadConfig, PageData, createTitle, notFoundPageData } from '../shared'
8+
import {
9+
HeadConfig,
10+
PageData,
11+
createTitle,
12+
notFoundPageData,
13+
mergeHead
14+
} from '../shared'
915
import { slash } from '../utils/slash'
1016
import { SiteConfig, resolveSiteDataByRoute } from '../config'
1117

@@ -115,10 +121,10 @@ export async function renderPage(
115121
const title: string = createTitle(siteData, pageData)
116122
const description: string = pageData.description || siteData.description
117123

118-
const head = [
119-
...siteData.head,
120-
...filterOutHeadDescription(pageData.frontmatter.head)
121-
]
124+
const head = mergeHead(
125+
siteData.head,
126+
filterOutHeadDescription(pageData.frontmatter.head)
127+
)
122128

123129
let inlinedScript = ''
124130
if (config.mpa && result) {

src/node/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] {
285285
if (userConfig?.appearance ?? true) {
286286
head.push([
287287
'script',
288-
{},
288+
{ id: 'check-dark-light' },
289289
`
290290
;(() => {
291291
const saved = localStorage.getItem('${APPEARANCE_KEY}')

src/shared/shared.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { SiteData, PageData, LocaleConfig } from '../../types/shared'
1+
import {
2+
SiteData,
3+
PageData,
4+
LocaleConfig,
5+
HeadConfig
6+
} from '../../types/shared'
27

38
export type {
49
SiteData,
@@ -136,3 +141,16 @@ function cleanRoute(siteData: SiteData, route: string): string {
136141

137142
return route.slice(baseWithoutSuffix.length)
138143
}
144+
145+
function hasTag(head: HeadConfig[], tag: HeadConfig) {
146+
const [tagType, tagAttrs] = tag
147+
const keyAttr = Object.entries(tagAttrs)[0] // First key
148+
if (keyAttr == null) return false
149+
return head.some(
150+
([type, attrs]) => type === tagType && attrs[keyAttr[0]] === keyAttr[1]
151+
)
152+
}
153+
154+
export function mergeHead(prev: HeadConfig[], curr: HeadConfig[]) {
155+
return [...prev.filter((tagAttrs) => !hasTag(curr, tagAttrs)), ...curr]
156+
}

0 commit comments

Comments
 (0)