New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Autosort <head> #36
Comments
Syncing code with actual Capo.js repo, now get full match on production site nuxt/src/server/plugins/capo.tsimport type { NitroApp } from 'nitropack'
import { JSDOM } from 'jsdom'
import { getSortedHead } from '@/utils/capo'
export default defineNitroPlugin((nitroApp: NitroApp) => {
nitroApp.hooks.hook('render:response', (response) => {
if (response.headers && !response.headers['content-type']?.startsWith('text/html')) {
return
}
const dom = new JSDOM(response.body)
const sortedHead = getSortedHead(dom.window.document)
response.body = response.body?.replace(/<head>(?:.|\n)*<\/head>/, sortedHead.outerHTML.trim())
})
}) nuxt/src/utils/capo.tsconst ElementWeights: { [key: string]: number } = {
META: 10,
TITLE: 9,
PRECONNECT: 8,
ASYNC_SCRIPT: 7,
IMPORT_STYLES: 6,
SYNC_SCRIPT: 5,
SYNC_STYLES: 4,
PRELOAD: 3,
DEFER_SCRIPT: 2,
PREFETCH_PRERENDER: 1,
OTHER: 0,
}
const ElementDetectors: { [key: string]: (element: Element) => boolean } = {
META: isMeta,
TITLE: isTitle,
PRECONNECT: isPreconnect,
ASYNC_SCRIPT: isAsyncScript,
IMPORT_STYLES: isImportStyles,
SYNC_SCRIPT: isSyncScript,
SYNC_STYLES: isSyncStyles,
PRELOAD: isPreload,
DEFER_SCRIPT: isDeferScript,
PREFETCH_PRERENDER: isPrefetchPrerender,
}
function isMeta (element: Element) {
return element.matches('meta:is([charset], [http-equiv], [name=viewport]), base')
}
function isTitle (element: Element) {
return element.matches('title')
}
function isPreconnect (element: Element) {
return element.matches('link[rel=preconnect]')
}
function isAsyncScript (element: Element) {
return element.matches('script[src][async]')
}
function isImportStyles (element: Element) {
const importRe = /@import/
if (element.matches('style')) {
return importRe.test(element.textContent || '')
}
return false
}
function isSyncScript (element: Element) {
return element.matches('script:not([src][defer],[src][type=module],[src][async],[type*=json])')
}
function isSyncStyles (element: Element) {
return element.matches('link[rel=stylesheet],style')
}
function isPreload (element: Element) {
return element.matches('link:is([rel=preload], [rel=modulepreload])')
}
function isDeferScript (element: Element) {
return element.matches('script[src][defer], script:not([src][async])[src][type=module]')
}
function isPrefetchPrerender (element: Element) {
return element.matches('link:is([rel=prefetch], [rel=dns-prefetch], [rel=prerender])')
}
function getWeight (element: Element) {
for (const [id, detector] of Object.entries(ElementDetectors)) {
if (detector(element)) {
return ElementWeights[id]
}
}
return ElementWeights.OTHER
}
function getHeadWeights (document: Document) {
const headChildren = Array.from(document.head.children)
return headChildren.map((element): [Element, number] => {
return [element, getWeight(element)]
})
}
function getSortedHead (document: Document) {
const headWeights = getHeadWeights(document)
headWeights.sort((a, b) => {
return b[1] - a[1]
})
const sortedHead = document.createElement('head')
const sortedWeights = [...headWeights].sort((a, b) => b[1] - a[1])
sortedWeights.forEach(([element]) => sortedHead.appendChild(element.cloneNode(true)))
return sortedHead
}
export { getSortedHead } |
This would be very interesting, and this is exactly the kind of discussion I was hoping for. I don't know if it's possible to do without deeper integration with unhead though (cc: @harlan-zw). |
I wrote this module for my production site, but after a careful analysis of the output of Capo.js and how Nuxt forms the head, I came to the conclusion that Nuxt forms the head quite well on its own. The difference lies only in the location of the meta tags (opengraph and others), which are actually recommended to be placed at the beginning, but Capo puts them at the end. Harry Roberts |
So briefly, Unhead will sort tags that are critical (for functionality) to be in the right position. The ordering is as follows: -2 - Where the default order is 10. The capo sorting improvements seem good, but Is there any details on how stable Capo.js is? I know it performs better theoretically but these hard and fast rules seem brittle. (like the above). Anyway, you could use the const ElementWeights = {
META: 10,
TITLE: 9,
PRECONNECT: 8,
ASYNC_SCRIPT: 7,
IMPORT_STYLES: 6,
SYNC_SCRIPT: 5,
SYNC_STYLES: 4,
PRELOAD: 3,
DEFER_SCRIPT: 2,
PREFETCH_PRERENDER: 1,
OTHER: 0
}; I'll create a demo when I have a chance |
One of the challenges is that Nuxt bundle renderer also renders resource hints outside unhead. Maybe there is a way we can integrate the two so sorting can be consistent? |
I think it would make sense that any head tags Nuxt wants to render SSR goes through Unhead, then the user can make use of Unhead plugins to customize the output however they like without relying on HTML regex. I think this requires coupling the renderer directly with the Unhead though, currently, there's a Stuck at the airport so I gave the capo.js demo a go: https://stackblitz.com/edit/nuxt-starter-ybmzxf?file=plugins%2Fcapo.ts |
This can be just wrapped in a plugin for this |
With Nuxt v3.7 it will now be possible to implement this 馃憤 |
馃啋 Your use case
The whole idea of using Capo is to cleverly sort the elements of the
<head>
tag.I'm wondering why this hasn't been done before?
I wrote a plugin for the Nitro server for myself, you can see below. Mostly consists of copy-paste, because types in
logger.mjs
are not exported.The plugin is executed on every hit, and this is actually not correct, as I understand it. Therefore, I want to hear what are the possibilities for optimization and other things? Then I can make a PR request if you're interested.
馃啎 The solution you'd like
nuxt/src/server/plugins/capo.ts
馃攳 Alternatives you've considered
No response
鈩癸笍 Additional info
After sorting view in Capo plugin for chrome:
real production site without autosort
real production site with autosort
I think need to update capo functions to match plugin result.
The text was updated successfully, but these errors were encountered: