Skip to content

Commit

Permalink
fix: don't recreate tags if they didn't change, closes #44
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Nov 18, 2021
1 parent d702fda commit 74990f0
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
"typescript.tsdk": "node_modules/typescript/lib"
}
2 changes: 2 additions & 0 deletions example/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const Counter = defineComponent({
const count = ref(0)
useHead({
title: computed(() => `count: ${count.value}`),
script: [{ children: 'console.log("a")', key: 'a' }],
link: [{ href: '/foo', rel: 'stylesheet' }],
})
return () => (
<button
Expand Down
34 changes: 28 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from './constants'
import { createElement } from './create-element'
import { stringifyAttrs } from './stringify-attrs'
import { isEqualNode } from './utils'

type MaybeRef<T> = T | Ref<T>

Expand Down Expand Up @@ -164,6 +165,7 @@ const insertTags = (tags: HeadTag[], document = window.document) => {
const headCount = headCountEl
? Number(headCountEl.getAttribute('content'))
: 0
const uncontrolledElements: Element[] = []
const oldElements: Element[] = []
if (headCountEl) {
for (
Expand All @@ -182,7 +184,7 @@ const insertTags = (tags: HeadTag[], document = window.document) => {
head.append(headCountEl)
}

const newElements: Element[] = []
let newElements: Element[] = []
let title: string | undefined
let htmlAttrs: HeadAttrs = {}
let bodyAttrs: HeadAttrs = {}
Expand Down Expand Up @@ -211,7 +213,7 @@ const insertTags = (tags: HeadTag[], document = window.document) => {
]
for (const el of elementList) {
if (!oldElements.includes(el)) {
oldElements.push(el)
uncontrolledElements.push(el)
}
}
}
Expand All @@ -220,23 +222,43 @@ const insertTags = (tags: HeadTag[], document = window.document) => {
newElements.push(createElement(tag.tag, tag.props, document))
}

newElements = newElements.filter((newEl) => {
for (let k = 0, len = oldElements.length; k < len; k++) {
const oldEl = oldElements[k]
if (isEqualNode(oldEl, newEl)) {
oldElements.splice(k, 1)
return false
}
}
return true
})

uncontrolledElements.forEach((el) => {
el.remove()
})

oldElements.forEach((el) => {
// Remove the next text node first, almost certainly a line break
if (el.nextSibling && el.nextSibling.nodeType === Node.TEXT_NODE) {
el.nextSibling.remove()
}
el.remove()
})

newElements.forEach((el) => {
head.insertBefore(el, headCountEl)
})

if (title !== undefined) {
document.title = title
}
setAttrs(document.documentElement, htmlAttrs)
setAttrs(document.body, bodyAttrs)

newElements.forEach((el) => {
head.insertBefore(el, headCountEl)
})
headCountEl.setAttribute('content', '' + newElements.length)
headCountEl.setAttribute(
'content',
'' + (headCount - oldElements.length + newElements.length),
)
}

export const createHead = () => {
Expand Down
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Shamelessly taken from Next.js
export function isEqualNode(oldTag: Element, newTag: Element) {
if (oldTag instanceof HTMLElement && newTag instanceof HTMLElement) {
const nonce = newTag.getAttribute('nonce')
// Only strip the nonce if `oldTag` has had it stripped. An element's nonce attribute will not
// be stripped if there is no content security policy response header that includes a nonce.
if (nonce && !oldTag.getAttribute('nonce')) {
const cloneTag = newTag.cloneNode(true) as typeof newTag
cloneTag.setAttribute('nonce', '')
cloneTag.nonce = nonce
return nonce === oldTag.nonce && oldTag.isEqualNode(cloneTag)
}
}

return oldTag.isEqualNode(newTag)
}
20 changes: 20 additions & 0 deletions tests/snapshots/test.tsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,23 @@ Generated by [AVA](https://avajs.dev).
tag: 'style',
},
]

## browser

> Snapshot 1
`␊
<script type="module" src="/@vite/client"></script>␊
<meta charset="UTF-8">␊
<meta http-equiv="X-UA-Compatible" content="IE=edge">␊
<meta name="viewport" content="width=device-width, initial-scale=1.0">␊
<title>count: 0</title>␊
<base href="/"><style>body {background: red}</style><meta name="description" content="desc 2"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="zh"><script>console.log("a")</script><link href="/foo" rel="stylesheet"><meta name="head:count" content="7">`

## server

> Snapshot 1
'<title>hello</title><meta name="description" content="desc 2"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="zh"><script src="foo.js"></script><meta name="head:count" content="4">'
Binary file modified tests/snapshots/test.tsx.snap
Binary file not shown.
17 changes: 2 additions & 15 deletions tests/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ test('server', async (t) => {
await renderToString(app)

const headResult = renderHeadToString(head)
t.is(
headResult.headTags,
`<title>hello</title><meta name="description" content="desc 2"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="zh"><script src="foo.js"></script><meta name="head:count" content="4">`,
)
t.snapshot(headResult.headTags)
t.is(headResult.htmlAttrs, ` lang="zh" data-head-attrs="lang"`)
})

Expand All @@ -80,17 +77,7 @@ test('browser', async (t) => {
await page.goto(`http://localhost:3000`)
const headHTML = await page.evaluate(() => document.head.innerHTML)

t.is(
headHTML,
`
<script type="module" src="/@vite/client"></script>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>count: 0</title>
<base href="/"><style>body {background: red}</style><meta name="description" content="desc 2"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="zh"><meta name="head:count" content="5">`,
)
t.snapshot(headHTML)

await page.click('button.counter')
t.is(await page.title(), 'count: 1')
Expand Down

0 comments on commit 74990f0

Please sign in to comment.