From 37c800a2ca7c90dbc9447a20dd31abb81af1dfc5 Mon Sep 17 00:00:00 2001 From: Georges Gomes Date: Fri, 25 Mar 2022 17:06:15 +0100 Subject: [PATCH] Re-enable update of tags --- src/client/app/composables/head.ts | 81 ++++++++++++++++++++++++++++++ src/client/app/index.ts | 6 +++ 2 files changed, 87 insertions(+) create mode 100644 src/client/app/composables/head.ts diff --git a/src/client/app/composables/head.ts b/src/client/app/composables/head.ts new file mode 100644 index 00000000..878d9da7 --- /dev/null +++ b/src/client/app/composables/head.ts @@ -0,0 +1,81 @@ +import { watchEffect, Ref } from 'vue' +import { HeadConfig, processHead, SiteData } from '../../shared' +import { Route } from '../router' + +export function useUpdateHead(route: Route, siteDataByRouteRef: Ref) { + let managedHeadTags: HTMLElement[] = [] + let isFirstUpdate = true + + const updateHeadTags = (newTags: HeadConfig[]) => { + if (import.meta.env.PROD && isFirstUpdate) { + // in production, the initial meta tags are already pre-rendered so we + // skip the first update. + isFirstUpdate = false + 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) => { + const el = createHeadElement(headConfig) + document.head.appendChild(el) + newEls.push(el) + }) + managedHeadTags = newEls + } + + watchEffect(() => { + const pageData = route.data + const siteData = siteDataByRouteRef.value + const pageTitle = pageData && pageData.title + const pageDescription = pageData && pageData.description + + // update title and description + document.title = (pageTitle ? pageTitle + ` | ` : ``) + siteData.title + document + .querySelector(`meta[name=description]`)! + .setAttribute('content', pageDescription || siteData.description) + + updateHeadTags(processHead(siteData.head, pageData)); + }) +} + +function createHeadElement([tag, attrs, innerHTML]: HeadConfig) { + const el = document.createElement(tag) + for (const key in attrs) { + el.setAttribute(key, attrs[key]) + } + if (innerHTML) { + el.innerHTML = innerHTML + } + return el +} diff --git a/src/client/app/index.ts b/src/client/app/index.ts index 923ebb37..3065c813 100644 --- a/src/client/app/index.ts +++ b/src/client/app/index.ts @@ -10,6 +10,7 @@ import { import { inBrowser, pathToFile } from './utils' import { Router, RouterSymbol, createRouter } from './router' import { siteDataRef, useData } from './data' +import { useUpdateHead } from './composables/head' import Theme from '/@theme/index' import { usePrefetch } from './composables/preFetch' import { dataSymbol, initData } from './data' @@ -54,6 +55,11 @@ export function createApp() { const data = initData(router.route) app.provide(dataSymbol, data) + if (inBrowser) { + // dynamically update head tags + useUpdateHead(router.route, data.site) + } + // install global components app.component('Content', Content) app.component('ClientOnly', ClientOnly)