From f7e9cfeb3a06ec93726870dd17116a019959d980 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:49:06 +0530 Subject: [PATCH] fix: de-duplicate head tags while merging (#975) (#976) --- src/client/app/composables/head.ts | 50 ++++++------------------------ src/node/build/render.ts | 16 +++++++--- src/node/config.ts | 2 +- src/shared/shared.ts | 20 +++++++++++- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/client/app/composables/head.ts b/src/client/app/composables/head.ts index 91a46d217fb..d74d6b5c0af 100644 --- a/src/client/app/composables/head.ts +++ b/src/client/app/composables/head.ts @@ -1,5 +1,5 @@ import { watchEffect, Ref } from 'vue' -import { HeadConfig, SiteData, createTitle } from '../../shared' +import { HeadConfig, SiteData, createTitle, mergeHead } from '../../shared' import { Route } from '../router' export function useUpdateHead(route: Route, siteDataByRouteRef: Ref) { @@ -14,50 +14,20 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref) { return } - const newEls: HTMLElement[] = [] - const commonLength = Math.min(managedHeadTags.length, newTags.length) - for (let i = 0; i < commonLength; i++) { - let el = managedHeadTags[i] - const [tag, attrs, innerHTML = ''] = newTags[i] - if (el.tagName.toLocaleLowerCase() === tag) { - for (const key in attrs) { - if (el.getAttribute(key) !== attrs[key]) { - el.setAttribute(key, attrs[key]) - } - } - for (let i = 0; i < el.attributes.length; i++) { - const name = el.attributes[i].name - if (!(name in attrs)) { - el.removeAttribute(name) - } - } - if (el.innerHTML !== innerHTML) { - el.innerHTML = innerHTML - } - } else { - document.head.removeChild(el) - el = createHeadElement(newTags[i]) - document.head.append(el) - } - newEls.push(el) - } - - managedHeadTags - .slice(commonLength) - .forEach((el) => document.head.removeChild(el)) - newTags.slice(commonLength).forEach((headConfig) => { + managedHeadTags.forEach((el) => document.head.removeChild(el)) + managedHeadTags = [] + newTags.forEach((headConfig) => { const el = createHeadElement(headConfig) document.head.appendChild(el) - newEls.push(el) + managedHeadTags.push(el) }) - managedHeadTags = newEls } watchEffect(() => { const pageData = route.data const siteData = siteDataByRouteRef.value const pageDescription = pageData && pageData.description - const frontmatterHead = pageData && pageData.frontmatter.head + const frontmatterHead = (pageData && pageData.frontmatter.head) || [] // update title and description document.title = createTitle(siteData, pageData) @@ -66,11 +36,9 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref) { .querySelector(`meta[name=description]`)! .setAttribute('content', pageDescription || siteData.description) - updateHeadTags([ - // site head can only change during dev - ...(import.meta.env.DEV ? siteData.head : []), - ...(frontmatterHead ? filterOutHeadDescription(frontmatterHead) : []) - ]) + updateHeadTags( + mergeHead(siteData.head, filterOutHeadDescription(frontmatterHead)) + ) }) } diff --git a/src/node/build/render.ts b/src/node/build/render.ts index c4330e3e63a..a03729e5f42 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -5,7 +5,13 @@ import { pathToFileURL } from 'url' import escape from 'escape-html' import { normalizePath, transformWithEsbuild } from 'vite' import { RollupOutput, OutputChunk, OutputAsset } from 'rollup' -import { HeadConfig, PageData, createTitle, notFoundPageData } from '../shared' +import { + HeadConfig, + PageData, + createTitle, + notFoundPageData, + mergeHead +} from '../shared' import { slash } from '../utils/slash' import { SiteConfig, resolveSiteDataByRoute } from '../config' @@ -115,10 +121,10 @@ export async function renderPage( const title: string = createTitle(siteData, pageData) const description: string = pageData.description || siteData.description - const head = [ - ...siteData.head, - ...filterOutHeadDescription(pageData.frontmatter.head) - ] + const head = mergeHead( + siteData.head, + filterOutHeadDescription(pageData.frontmatter.head) + ) let inlinedScript = '' if (config.mpa && result) { diff --git a/src/node/config.ts b/src/node/config.ts index 223238c72e5..1fac1d1b083 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -285,7 +285,7 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] { if (userConfig?.appearance ?? true) { head.push([ 'script', - {}, + { id: 'check-dark-light' }, ` ;(() => { const saved = localStorage.getItem('${APPEARANCE_KEY}') diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 67cb0b942df..e5041629858 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -1,4 +1,9 @@ -import { SiteData, PageData, LocaleConfig } from '../../types/shared' +import { + SiteData, + PageData, + LocaleConfig, + HeadConfig +} from '../../types/shared' export type { SiteData, @@ -136,3 +141,16 @@ function cleanRoute(siteData: SiteData, route: string): string { return route.slice(baseWithoutSuffix.length) } + +function hasTag(head: HeadConfig[], tag: HeadConfig) { + const [tagType, tagAttrs] = tag + const keyAttr = Object.entries(tagAttrs)[0] // First key + if (keyAttr == null) return false + return head.some( + ([type, attrs]) => type === tagType && attrs[keyAttr[0]] === keyAttr[1] + ) +} + +export function mergeHead(prev: HeadConfig[], curr: HeadConfig[]) { + return [...prev.filter((tagAttrs) => !hasTag(curr, tagAttrs)), ...curr] +}