From dcc8d68f9dea032a11d6092b78440300968e6d50 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Mon, 11 Dec 2023 13:04:39 +0800 Subject: [PATCH 01/10] fix(client): avoid updating existing head tags, close #1268 --- packages/client/src/setupUpdateHead.ts | 52 +++++++++++++------ packages/shared/src/utils/dedupeHead.ts | 2 +- .../shared/src/utils/resolveHeadIdentifier.ts | 41 +++++++++++---- .../tests/resolveHeadIdentifier.spec.ts | 48 +++++++++++++++++ 4 files changed, 114 insertions(+), 29 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index 504cc9240e..759b326c3e 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -1,6 +1,6 @@ import { isPlainObject, isString } from '@vuepress/shared' import type { HeadConfig, VuepressSSRContext } from '@vuepress/shared' -import { onMounted, provide, ref, useSSRContext, watch } from 'vue' +import { onMounted, provide, useSSRContext, watch } from 'vue' import { updateHeadSymbol, usePageHead, @@ -25,14 +25,13 @@ export const setupUpdateHead = (): void => { return } - const headTags = ref([]) + const managedHeadElements: HTMLElement[] = [] // load current head tags from DOM const loadHead = (): void => { - head.value.forEach((item) => { - const tag = queryHeadTag(item) - if (tag) { - headTags.value.push(tag) + head.value.map(queryHeadElement).forEach((el) => { + if (el && managedHeadElements.every((head) => !head.isEqualNode(el))) { + managedHeadElements.push(el) } }) } @@ -41,34 +40,53 @@ export const setupUpdateHead = (): void => { const updateHead: UpdateHead = () => { document.documentElement.lang = lang.value - headTags.value.forEach((item) => { + managedHeadElements.forEach((item) => { if (item.parentNode === document.head) { document.head.removeChild(item) } }) - headTags.value.splice(0, headTags.value.length) - head.value.forEach((item) => { - const tag = createHeadTag(item) - if (tag !== null) { - document.head.appendChild(tag) - headTags.value.push(tag) + const newHeadElements = head.value + .map(createHeadElement) + .filter(Boolean) as HTMLElement[] + + managedHeadElements.forEach((el, index) => { + const matchedIndex = newHeadElements.findIndex( + (newEl) => newEl?.isEqualNode(el ?? null), + ) + + if (matchedIndex === -1) { + el?.remove() + delete managedHeadElements[index] + } else { + delete newHeadElements[matchedIndex] } }) + + newHeadElements.forEach((el) => { + document.head.appendChild(el) + }) + managedHeadElements.push(...newHeadElements) } provide(updateHeadSymbol, updateHead) onMounted(() => { loadHead() - updateHead() - watch(() => head.value, updateHead) + if (__VUEPRESS_DEV__) updateHead() + watch( + () => head.value, + () => { + if (__VUEPRESS_DEV__) loadHead() + updateHead() + }, + ) }) } /** * Query the matched head tag of head config */ -export const queryHeadTag = ([ +export const queryHeadElement = ([ tagName, attrs, content = '', @@ -94,7 +112,7 @@ export const queryHeadTag = ([ /** * Create head tag from head config */ -export const createHeadTag = ([ +export const createHeadElement = ([ tagName, attrs, content, diff --git a/packages/shared/src/utils/dedupeHead.ts b/packages/shared/src/utils/dedupeHead.ts index 06dc30c77d..3b47a72a4c 100644 --- a/packages/shared/src/utils/dedupeHead.ts +++ b/packages/shared/src/utils/dedupeHead.ts @@ -12,7 +12,7 @@ export const dedupeHead = (head: HeadConfig[]): HeadConfig[] => { head.forEach((item) => { const identifier = resolveHeadIdentifier(item) - if (!identifierSet.has(identifier)) { + if (identifier && !identifierSet.has(identifier)) { identifierSet.add(identifier) result.push(item) } diff --git a/packages/shared/src/utils/resolveHeadIdentifier.ts b/packages/shared/src/utils/resolveHeadIdentifier.ts index fff7dc7bbc..36b35292ed 100644 --- a/packages/shared/src/utils/resolveHeadIdentifier.ts +++ b/packages/shared/src/utils/resolveHeadIdentifier.ts @@ -1,27 +1,46 @@ import type { HeadConfig } from '../types/index.js' +const TAGS_ALLOWED = ['link', 'meta', 'script', 'style', 'noscript', 'template'] +const TAGS_UNIQUE = ['title', 'base'] + /** * Resolve identifier of a tag, to avoid duplicated tags in `` */ -export const resolveHeadIdentifier = ([ - tag, - attrs, - content, -]: HeadConfig): string => { +export const resolveHeadIdentifier = ([tag, attrs, content]: HeadConfig): + | string + | null => { + // avoid duplicated unique tags + if (TAGS_UNIQUE.includes(tag)) { + return tag + } + + // avoid tags not allowed + if (!TAGS_ALLOWED.includes(tag)) { + return null + } + // avoid duplicated `` with same `name` if (tag === 'meta' && attrs.name) { return `${tag}.${attrs.name}` } - // there should be only one `` or `<base>` - if (['title', 'base'].includes(tag)) { - return tag - } - // avoid duplicated `<template>` with same `id` if (tag === 'template' && attrs.id) { return `${tag}.${attrs.id}` } - return JSON.stringify([tag, attrs, content]) + return JSON.stringify([ + tag, + Object.entries(attrs) + .map(([key, value]) => { + // normalize boolean attributes + if (typeof value === 'boolean') { + return value ? [key, ''] : null + } + return [key, value] + }) + .filter((item): item is [string, string] => item != null) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)), + content, + ]) } diff --git a/packages/shared/tests/resolveHeadIdentifier.spec.ts b/packages/shared/tests/resolveHeadIdentifier.spec.ts index 6e169b0943..db0a18916b 100644 --- a/packages/shared/tests/resolveHeadIdentifier.spec.ts +++ b/packages/shared/tests/resolveHeadIdentifier.spec.ts @@ -60,6 +60,54 @@ describe('shared > resolveHeadIdentifier', () => { expect(templateFooBaz).not.toBe(templateBarBar) }) + it('should resolve same identifiers of same HeadConfig', () => { + const style1 = resolveHeadIdentifier([ + 'style', + { id: 'foo' }, + `body { color: red; }`, + ]) + const style2 = resolveHeadIdentifier([ + 'style', + { id: 'foo' }, + `body { color: red; }`, + ]) + const link1 = resolveHeadIdentifier([ + 'link', + { href: 'https://example.com', defer: '' }, + ]) + const link2 = resolveHeadIdentifier([ + 'link', + { href: 'https://example.com', defer: true }, + ]) + const link3 = resolveHeadIdentifier([ + 'link', + { defer: '', href: 'https://example.com' }, + ]) + const link4 = resolveHeadIdentifier([ + 'link', + { defer: true, href: 'https://example.com' }, + ]) + const link5 = resolveHeadIdentifier([ + 'link', + { href: 'https://example.com' }, + ]) + const link6 = resolveHeadIdentifier([ + 'link', + { href: 'https://example.com', defer: false }, + ]) + const link7 = resolveHeadIdentifier([ + 'link', + { defer: false, href: 'https://example.com' }, + ]) + + expect(style1).toBe(style2) + expect(link1).toBe(link2) + expect(link1).toBe(link3) + expect(link1).toBe(link4) + expect(link5).toBe(link6) + expect(link5).toBe(link7) + }) + it('should resolve identifiers correctly', () => { const head: HeadConfig[] = [ // 1 From cf701f26b1e190ff672f47041987d2fbe2969d92 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" <mister-hope@outlook.com> Date: Mon, 11 Dec 2023 13:08:11 +0800 Subject: [PATCH 02/10] chore: tweaks --- packages/client/src/setupUpdateHead.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index 759b326c3e..a713d88909 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -25,7 +25,7 @@ export const setupUpdateHead = (): void => { return } - const managedHeadElements: HTMLElement[] = [] + let managedHeadElements: HTMLElement[] = [] // load current head tags from DOM const loadHead = (): void => { @@ -66,7 +66,10 @@ export const setupUpdateHead = (): void => { newHeadElements.forEach((el) => { document.head.appendChild(el) }) - managedHeadElements.push(...newHeadElements) + managedHeadElements = [ + ...managedHeadElements.filter(Boolean), + ...newHeadElements, + ] } provide(updateHeadSymbol, updateHead) From 143ffa36ba3bfdfc5226c8bfbe4dd345ee097b09 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" <mister-hope@outlook.com> Date: Mon, 11 Dec 2023 13:19:07 +0800 Subject: [PATCH 03/10] fix: fix buildServer issue --- packages/client/src/setupUpdateHead.ts | 64 ++++++++++---------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index a713d88909..0b4aa7fbba 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -6,7 +6,6 @@ import { usePageHead, usePageLang, } from './composables/index.js' -import type { UpdateHead } from './composables/index.js' /** * Auto update head and provide as global util @@ -25,63 +24,50 @@ export const setupUpdateHead = (): void => { return } - let managedHeadElements: HTMLElement[] = [] + let managedHeadElements: (HTMLElement | null)[] = [] + let isFirstUpdate = true - // load current head tags from DOM - const loadHead = (): void => { - head.value.map(queryHeadElement).forEach((el) => { - if (el && managedHeadElements.every((head) => !head.isEqualNode(el))) { - managedHeadElements.push(el) - } - }) - } + const updateHeadTags = (newTags: HeadConfig[]): void => { + if (!__VUEPRESS_DEV__ && isFirstUpdate) { + // in production, the initial meta tags are already pre-rendered so we + // skip the first update. + isFirstUpdate = false + return + } - // update html lang attribute and head tags to DOM - const updateHead: UpdateHead = () => { document.documentElement.lang = lang.value - managedHeadElements.forEach((item) => { - if (item.parentNode === document.head) { - document.head.removeChild(item) - } - }) - - const newHeadElements = head.value - .map(createHeadElement) - .filter(Boolean) as HTMLElement[] + const newElements: (HTMLElement | null)[] = newTags.map(createHeadElement) - managedHeadElements.forEach((el, index) => { - const matchedIndex = newHeadElements.findIndex( - (newEl) => newEl?.isEqualNode(el ?? null), + managedHeadElements.forEach((oldEl, oldIndex) => { + const matchedIndex = newElements.findIndex( + (newEl) => newEl?.isEqualNode(oldEl ?? null), ) if (matchedIndex === -1) { - el?.remove() - delete managedHeadElements[index] + oldEl?.remove() + delete managedHeadElements[oldIndex] } else { - delete newHeadElements[matchedIndex] + delete newElements[matchedIndex] } }) - newHeadElements.forEach((el) => { - document.head.appendChild(el) - }) - managedHeadElements = [ - ...managedHeadElements.filter(Boolean), - ...newHeadElements, - ] + newElements.forEach((el) => el && document.head.appendChild(el)) + managedHeadElements = [...managedHeadElements, ...newElements].filter( + Boolean, + ) } - provide(updateHeadSymbol, updateHead) + provide(updateHeadSymbol, () => { + updateHeadTags(head.value) + }) onMounted(() => { - loadHead() - if (__VUEPRESS_DEV__) updateHead() watch( () => head.value, () => { - if (__VUEPRESS_DEV__) loadHead() - updateHead() + updateHeadTags(head.value) }, + { immediate: true }, ) }) } From 15192fe080a09c314c58e2cfe429d10f035658f3 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 13 Dec 2023 00:06:42 +0800 Subject: [PATCH 04/10] chore: tweak comments --- packages/shared/src/utils/resolveHeadIdentifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/utils/resolveHeadIdentifier.ts b/packages/shared/src/utils/resolveHeadIdentifier.ts index 36b35292ed..2160862fd6 100644 --- a/packages/shared/src/utils/resolveHeadIdentifier.ts +++ b/packages/shared/src/utils/resolveHeadIdentifier.ts @@ -14,7 +14,7 @@ export const resolveHeadIdentifier = ([tag, attrs, content]: HeadConfig): return tag } - // avoid tags not allowed + // avoid disallowed tags if (!TAGS_ALLOWED.includes(tag)) { return null } From 256c1126b1e6cd71e5e580f112c2165c67d09f83 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 15:57:05 +0800 Subject: [PATCH 05/10] test: add update-head e2e test case --- e2e/tests/update-head.cy.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 e2e/tests/update-head.cy.ts diff --git a/e2e/tests/update-head.cy.ts b/e2e/tests/update-head.cy.ts new file mode 100644 index 0000000000..b469c35792 --- /dev/null +++ b/e2e/tests/update-head.cy.ts @@ -0,0 +1,35 @@ +describe('updateHead', () => { + it('should update head correctly', () => { + // en-US + cy.visit('/') + + cy.get('head meta[name="foo"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foo') + cy.get('head meta[name="bar"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foobar') + cy.get('head meta[name="baz"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foobar baz') + cy.get('head meta[name="foo-en"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foo-en') + + // navigate to zh-CN + cy.get('.e2e-theme-nav a').contains('zh-CN').click() + + cy.get('head meta[name="foo"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foo') + cy.get('head meta[name="bar"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foobar zh') + cy.get('head meta[name="baz"]') + .should('have.length', 1) + .should('have.attr', 'content', 'baz') + cy.get('head meta[name="foo-zh"]') + .should('have.length', 1) + .should('have.attr', 'content', 'foo-zh') + }) +}) From d33997631bf6faec5ea2faa703c0abb3df6d0169 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 16:06:35 +0800 Subject: [PATCH 06/10] fix: take over prod tags --- packages/client/src/setupUpdateHead.ts | 31 +++++++++----------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index 0b4aa7fbba..e3904f02b0 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -25,19 +25,12 @@ export const setupUpdateHead = (): void => { } let managedHeadElements: (HTMLElement | null)[] = [] - let isFirstUpdate = true - - const updateHeadTags = (newTags: HeadConfig[]): void => { - if (!__VUEPRESS_DEV__ && isFirstUpdate) { - // in production, the initial meta tags are already pre-rendered so we - // skip the first update. - isFirstUpdate = false - return - } + const updateHeadTags = (): void => { document.documentElement.lang = lang.value - const newElements: (HTMLElement | null)[] = newTags.map(createHeadElement) + const newElements: (HTMLElement | null)[] = + head.value.map(createHeadElement) managedHeadElements.forEach((oldEl, oldIndex) => { const matchedIndex = newElements.findIndex( @@ -57,18 +50,16 @@ export const setupUpdateHead = (): void => { Boolean, ) } - provide(updateHeadSymbol, () => { - updateHeadTags(head.value) - }) + provide(updateHeadSymbol, updateHeadTags) onMounted(() => { - watch( - () => head.value, - () => { - updateHeadTags(head.value) - }, - { immediate: true }, - ) + // in production, the initial meta tags are already pre-rendered, + // so we need to skip the first update and take over the existing tags. + if (!__VUEPRESS_DEV__) { + managedHeadElements = head.value.map(queryHeadElement).filter(Boolean) + } + + watch(head, updateHeadTags, { immediate: __VUEPRESS_DEV__ }) }) } From 1ae0f8034e9d85cbbf3e771bafc8d8508971c53f Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 16:48:00 +0800 Subject: [PATCH 07/10] refactor: improve logic and update comments --- packages/client/src/setupUpdateHead.ts | 76 +++++++++++++++++++------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index e3904f02b0..b2825691fa 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -6,6 +6,7 @@ import { usePageHead, usePageLang, } from './composables/index.js' +import type { UpdateHead } from './composables/index.js' /** * Auto update head and provide as global util @@ -24,42 +25,75 @@ export const setupUpdateHead = (): void => { return } - let managedHeadElements: (HTMLElement | null)[] = [] + let managedHeadElements: HTMLElement[] = [] - const updateHeadTags = (): void => { + /** + * Take over the existing head elements + */ + const takeOverHeadElements = (): void => { + head.value.forEach((item) => { + const tag = queryHeadElement(item) + if (tag) managedHeadElements.push(tag) + }) + } + + /** + * Generate head elements from current head config + */ + const generateHeadElements = (): HTMLElement[] => { + const result: HTMLElement[] = [] + head.value.forEach((item) => { + const tag = createHeadElement(item) + if (tag) result.push(tag) + }) + return result + } + + /** + * Update head elements + */ + const updateHead: UpdateHead = () => { + // set html lang attribute document.documentElement.lang = lang.value - const newElements: (HTMLElement | null)[] = - head.value.map(createHeadElement) + // generate new head elements from current head config + const newHeadElements = generateHeadElements() + // find the intersection of old and new head elements managedHeadElements.forEach((oldEl, oldIndex) => { - const matchedIndex = newElements.findIndex( - (newEl) => newEl?.isEqualNode(oldEl ?? null), + const matchedIndex = newHeadElements.findIndex((newEl) => + oldEl.isEqualNode(newEl), ) - + // remove the non-intersection from old elements if (matchedIndex === -1) { - oldEl?.remove() + oldEl.remove() + // use delete to make the index consistent delete managedHeadElements[oldIndex] - } else { - delete newElements[matchedIndex] + } + // keep the intersection in old elements, and remove it from new elements + else { + // use splice to avoid empty item in next loop + newHeadElements.splice(matchedIndex, 1) } }) - - newElements.forEach((el) => el && document.head.appendChild(el)) - managedHeadElements = [...managedHeadElements, ...newElements].filter( - Boolean, - ) + // append the rest new elements to head + newHeadElements.forEach((el) => document.head.appendChild(el)) + // update managed head elements + managedHeadElements = [ + // filter out empty deleted items + ...managedHeadElements.filter((item) => !!item), + ...newHeadElements, + ] } - provide(updateHeadSymbol, updateHeadTags) + provide(updateHeadSymbol, updateHead) onMounted(() => { - // in production, the initial meta tags are already pre-rendered, - // so we need to skip the first update and take over the existing tags. + // in production, the initial head elements are already pre-rendered, + // so we need to skip the first update and take over the existing elements. if (!__VUEPRESS_DEV__) { - managedHeadElements = head.value.map(queryHeadElement).filter(Boolean) + takeOverHeadElements() } - - watch(head, updateHeadTags, { immediate: __VUEPRESS_DEV__ }) + watch(head, updateHead, { immediate: __VUEPRESS_DEV__ }) }) } From 6432119f1a596b4fb9000069dba51d4ffa83097c Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 16:54:45 +0800 Subject: [PATCH 08/10] test: improve update-head e2e case --- e2e/tests/update-head.cy.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/e2e/tests/update-head.cy.ts b/e2e/tests/update-head.cy.ts index b469c35792..acc0f80cdf 100644 --- a/e2e/tests/update-head.cy.ts +++ b/e2e/tests/update-head.cy.ts @@ -3,6 +3,18 @@ describe('updateHead', () => { // en-US cy.visit('/') + // lang + cy.get('html').should('have.attr', 'lang', 'en-US') + // title + cy.title().should('eq', 'VuePress E2E') + cy.get('head title').should('have.text', 'VuePress E2E') + // description + cy.get('head meta[name="description"]').should( + 'have.attr', + 'content', + 'VuePress E2E Test Site', + ) + // head cy.get('head meta[name="foo"]') .should('have.length', 1) .should('have.attr', 'content', 'foo') @@ -19,6 +31,18 @@ describe('updateHead', () => { // navigate to zh-CN cy.get('.e2e-theme-nav a').contains('zh-CN').click() + // lang + cy.get('html').should('have.attr', 'lang', 'zh-CN') + // title + cy.title().should('eq', 'VuePress E2E') + cy.get('head title').should('have.text', 'VuePress E2E') + // description + cy.get('head meta[name="description"]').should( + 'have.attr', + 'content', + 'VuePress E2E 测试站点', + ) + // head cy.get('head meta[name="foo"]') .should('have.length', 1) .should('have.attr', 'content', 'foo') From f677de229d235e8939b238f8e9d10460d1d71d9c Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 16:56:06 +0800 Subject: [PATCH 09/10] test: improve e2e test cases --- e2e/tests/site-data.cy.ts | 24 ++++++++++++------------ e2e/tests/update-head.cy.ts | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/e2e/tests/site-data.cy.ts b/e2e/tests/site-data.cy.ts index cca6f83646..2ed1900b59 100644 --- a/e2e/tests/site-data.cy.ts +++ b/e2e/tests/site-data.cy.ts @@ -10,15 +10,15 @@ describe('siteData', () => { it('title', () => { cy.title().should('eq', 'VuePress E2E') - cy.get('head title').should('have.text', 'VuePress E2E') + cy.get('head title') + .should('have.length', 1) + .should('have.text', 'VuePress E2E') }) it('description', () => { - cy.get('head meta[name="description"]').should( - 'have.attr', - 'content', - 'VuePress E2E Test Site', - ) + cy.get('head meta[name="description"]') + .should('have.length', 1) + .should('have.attr', 'content', 'VuePress E2E Test Site') }) it('head', () => { @@ -48,15 +48,15 @@ describe('siteData', () => { it('title', () => { cy.title().should('eq', 'VuePress E2E') - cy.get('head title').should('have.text', 'VuePress E2E') + cy.get('head title') + .should('have.length', 1) + .should('have.text', 'VuePress E2E') }) it('description', () => { - cy.get('head meta[name="description"]').should( - 'have.attr', - 'content', - 'VuePress E2E 测试站点', - ) + cy.get('head meta[name="description"]') + .should('have.length', 1) + .should('have.attr', 'content', 'VuePress E2E 测试站点') }) it('head', () => { diff --git a/e2e/tests/update-head.cy.ts b/e2e/tests/update-head.cy.ts index acc0f80cdf..0f617c8d37 100644 --- a/e2e/tests/update-head.cy.ts +++ b/e2e/tests/update-head.cy.ts @@ -7,13 +7,13 @@ describe('updateHead', () => { cy.get('html').should('have.attr', 'lang', 'en-US') // title cy.title().should('eq', 'VuePress E2E') - cy.get('head title').should('have.text', 'VuePress E2E') + cy.get('head title') + .should('have.length', 1) + .should('have.text', 'VuePress E2E') // description - cy.get('head meta[name="description"]').should( - 'have.attr', - 'content', - 'VuePress E2E Test Site', - ) + cy.get('head meta[name="description"]') + .should('have.length', 1) + .should('have.attr', 'content', 'VuePress E2E Test Site') // head cy.get('head meta[name="foo"]') .should('have.length', 1) @@ -35,13 +35,13 @@ describe('updateHead', () => { cy.get('html').should('have.attr', 'lang', 'zh-CN') // title cy.title().should('eq', 'VuePress E2E') - cy.get('head title').should('have.text', 'VuePress E2E') + cy.get('head title') + .should('have.length', 1) + .should('have.text', 'VuePress E2E') // description - cy.get('head meta[name="description"]').should( - 'have.attr', - 'content', - 'VuePress E2E 测试站点', - ) + cy.get('head meta[name="description"]') + .should('have.length', 1) + .should('have.attr', 'content', 'VuePress E2E 测试站点') // head cy.get('head meta[name="foo"]') .should('have.length', 1) From 763d74613aa422718c729e656e1c3d1bfa116baf Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Thu, 14 Dec 2023 16:59:52 +0800 Subject: [PATCH 10/10] chore: normalize naming --- packages/client/src/setupUpdateHead.ts | 36 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/client/src/setupUpdateHead.ts b/packages/client/src/setupUpdateHead.ts index b2825691fa..5757426c82 100644 --- a/packages/client/src/setupUpdateHead.ts +++ b/packages/client/src/setupUpdateHead.ts @@ -32,8 +32,10 @@ export const setupUpdateHead = (): void => { */ const takeOverHeadElements = (): void => { head.value.forEach((item) => { - const tag = queryHeadElement(item) - if (tag) managedHeadElements.push(tag) + const headElement = queryHeadElement(item) + if (headElement) { + managedHeadElements.push(headElement) + } }) } @@ -43,8 +45,10 @@ export const setupUpdateHead = (): void => { const generateHeadElements = (): HTMLElement[] => { const result: HTMLElement[] = [] head.value.forEach((item) => { - const tag = createHeadElement(item) - if (tag) result.push(tag) + const headElement = createHeadElement(item) + if (headElement) { + result.push(headElement) + } }) return result } @@ -98,7 +102,7 @@ export const setupUpdateHead = (): void => { } /** - * Query the matched head tag of head config + * Query the matched head element of head config */ export const queryHeadElement = ([ tagName, @@ -118,13 +122,17 @@ export const queryHeadElement = ([ .join('') const selector = `head > ${tagName}${attrsSelector}` - const tags = Array.from(document.querySelectorAll<HTMLElement>(selector)) - const matchedTag = tags.find((item) => item.innerText === content) - return matchedTag || null + const headElements = Array.from( + document.querySelectorAll<HTMLElement>(selector), + ) + const matchedHeadElement = headElements.find( + (item) => item.innerText === content, + ) + return matchedHeadElement || null } /** - * Create head tag from head config + * Create head element from head config */ export const createHeadElement = ([ tagName, @@ -136,23 +144,23 @@ export const createHeadElement = ([ } // create element - const tag = document.createElement(tagName) + const headElement = document.createElement(tagName) // set attributes if (isPlainObject(attrs)) { Object.entries(attrs).forEach(([key, value]) => { if (isString(value)) { - tag.setAttribute(key, value) + headElement.setAttribute(key, value) } else if (value === true) { - tag.setAttribute(key, '') + headElement.setAttribute(key, '') } }) } // set content if (isString(content)) { - tag.appendChild(document.createTextNode(content)) + headElement.appendChild(document.createTextNode(content)) } - return tag + return headElement }