diff --git a/packages/next/client/head-manager.js b/packages/next/client/head-manager.js index 5eec36503c0ce6e..08b5c45a527ad74 100644 --- a/packages/next/client/head-manager.js +++ b/packages/next/client/head-manager.js @@ -5,90 +5,6 @@ const DOMAttributeNames = { httpEquiv: 'http-equiv', } -export default class HeadManager { - constructor() { - this.updatePromise = null - } - - updateHead = head => { - const promise = (this.updatePromise = Promise.resolve().then(() => { - if (promise !== this.updatePromise) return - - this.updatePromise = null - this.doUpdateHead(head) - })) - } - - doUpdateHead(head) { - const tags = {} - head.forEach(h => { - const components = tags[h.type] || [] - components.push(h) - tags[h.type] = components - }) - - this.updateTitle(tags.title ? tags.title[0] : null) - - const types = ['meta', 'base', 'link', 'style', 'script'] - types.forEach(type => { - this.updateElements(type, tags[type] || []) - }) - } - - updateTitle(component) { - let title = '' - if (component) { - const { children } = component.props - title = typeof children === 'string' ? children : children.join('') - } - if (title !== document.title) document.title = title - } - - updateElements(type, components) { - const headEl = document.getElementsByTagName('head')[0] - const headCountEl = headEl.querySelector('meta[name=next-head-count]') - if (process.env.NODE_ENV !== 'production') { - if (!headCountEl) { - console.error( - 'Warning: next-head-count is missing. https://err.sh/next.js/next-head-count-missing' - ) - return - } - } - - const headCount = Number(headCountEl.content) - const oldTags = [] - - for ( - let i = 0, j = headCountEl.previousElementSibling; - i < headCount; - i++, j = j.previousElementSibling - ) { - if (j.tagName.toLowerCase() === type) { - oldTags.push(j) - } - } - const newTags = components.map(reactElementToDOM).filter(newTag => { - for (let k = 0, len = oldTags.length; k < len; k++) { - const oldTag = oldTags[k] - if (oldTag.isEqualNode(newTag)) { - oldTags.splice(k, 1) - return false - } - } - return true - }) - - oldTags.forEach(t => t.parentNode.removeChild(t)) - newTags.forEach(t => headEl.insertBefore(t, headCountEl)) - headCountEl.content = ( - headCount - - oldTags.length + - newTags.length - ).toString() - } -} - function reactElementToDOM({ type, props }) { const el = document.createElement(type) for (const p in props) { @@ -110,3 +26,73 @@ function reactElementToDOM({ type, props }) { } return el } + +function updateElements(type, components) { + const headEl = document.getElementsByTagName('head')[0] + const headCountEl = headEl.querySelector('meta[name=next-head-count]') + if (process.env.NODE_ENV !== 'production') { + if (!headCountEl) { + console.error( + 'Warning: next-head-count is missing. https://err.sh/next.js/next-head-count-missing' + ) + return + } + } + + const headCount = Number(headCountEl.content) + const oldTags = [] + + for ( + let i = 0, j = headCountEl.previousElementSibling; + i < headCount; + i++, j = j.previousElementSibling + ) { + if (j.tagName.toLowerCase() === type) { + oldTags.push(j) + } + } + const newTags = components.map(reactElementToDOM).filter(newTag => { + for (let k = 0, len = oldTags.length; k < len; k++) { + const oldTag = oldTags[k] + if (oldTag.isEqualNode(newTag)) { + oldTags.splice(k, 1) + return false + } + } + return true + }) + + oldTags.forEach(t => t.parentNode.removeChild(t)) + newTags.forEach(t => headEl.insertBefore(t, headCountEl)) + headCountEl.content = (headCount - oldTags.length + newTags.length).toString() +} + +export default function initHeadManager() { + let updatePromise = null + + return head => { + const promise = (updatePromise = Promise.resolve().then(() => { + if (promise !== updatePromise) return + + updatePromise = null + const tags = {} + + head.forEach(h => { + const components = tags[h.type] || [] + components.push(h) + tags[h.type] = components + }) + + const titleComponent = tags.title ? tags.title[0] : null + let title = '' + if (titleComponent) { + const { children } = titleComponent.props + title = typeof children === 'string' ? children : children.join('') + } + if (title !== document.title) document.title = title + ;['meta', 'base', 'link', 'style', 'script'].forEach(type => { + updateElements(type, tags[type] || []) + }) + })) + } +} diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 8cd2473aa15c6cc..124b8ab35dc1efc 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -1,7 +1,7 @@ /* global location */ import React from 'react' import ReactDOM from 'react-dom' -import HeadManager from './head-manager' +import initHeadManager from './head-manager' import { createRouter, makePublicRouterInstance } from 'next/router' import mitt from '../next-server/lib/mitt' import { loadGetInitialProps, getURL, ST } from '../next-server/lib/utils' @@ -57,7 +57,7 @@ if (window.__NEXT_P) { window.__NEXT_P = [] window.__NEXT_P.push = register -const headManager = new HeadManager() +const updateHead = initHeadManager() const appElement = document.getElementById('__next') let lastAppProps @@ -397,7 +397,7 @@ function AppContainer({ children }) { } > - + {children} diff --git a/test/integration/size-limit/test/index.test.js b/test/integration/size-limit/test/index.test.js index 0d36408825aa3db..1700dafa5efe463 100644 --- a/test/integration/size-limit/test/index.test.js +++ b/test/integration/size-limit/test/index.test.js @@ -80,7 +80,7 @@ describe('Production response size', () => { ) // These numbers are without gzip compression! - const delta = responseSizesBytes - 238 * 1024 + const delta = responseSizesBytes - 237 * 1024 expect(delta).toBeLessThanOrEqual(1024) // don't increase size more than 1kb expect(delta).toBeGreaterThanOrEqual(-1024) // don't decrease size more than 1kb without updating target })