Skip to content

Commit

Permalink
fix(client): avoid updating existing head tags, close #1268
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Apr 21, 2023
1 parent 1fe9635 commit f1f9134
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/shared/src/utils/dedupeHead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
37 changes: 30 additions & 7 deletions packages/shared/src/utils/resolveHeadIdentifier.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import type { HeadConfig } from '../types/index.js'

const onlyTags = ['title', 'base']
const allowedTags = ['link', 'meta', 'script', 'style', 'noscript', 'template']

/**
* Resolve identifier of a tag, to avoid duplicated tags in `<head>`
*/
export const resolveHeadIdentifier = ([
tag,
attrs,
content,
]: HeadConfig): string => {
export const resolveHeadIdentifier = ([tag, attrs, content]: HeadConfig):
| string
| null => {
// avoid duplicated `<meta>` with same `name`
if (tag === 'meta' && attrs.name) {
return `${tag}.${attrs.name}`
}

// there should be only one `<title>` or `<base>`
if (['title', 'base'].includes(tag)) {
if (onlyTags.includes(tag)) {
return tag
}

Expand All @@ -23,5 +24,27 @@ export const resolveHeadIdentifier = ([
return `${tag}.${attrs.id}`
}

return JSON.stringify([tag, attrs, content])
if (allowedTags.includes(tag)) {
return JSON.stringify([
tag,
Object.fromEntries(
Object.entries(attrs)
// handle boolean attributes
.map(([key, value]) =>
typeof value === 'boolean'
? value
? [key, '']
: null
: [key, value]
)
.filter((item): item is [string, string] => item != null)
// sort keys
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
),
content,
])
}

// tags are not allowed
return null
}
48 changes: 48 additions & 0 deletions packages/shared/tests/resolveHeadIdentifier.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f1f9134

Please sign in to comment.