perf(head): drop @vueuse/head dependency (#19519)

This commit is contained in:
Harlan Wilton 2023-03-09 02:32:24 +11:00 committed by GitHub
parent df3ae8cb4e
commit 8732720221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 130 additions and 73 deletions

View File

@ -46,7 +46,8 @@ export default defineNuxtConfig({
## Composable: `useHead`
The `useHead` composable function allows you to manage your head tags in a programmatic and reactive way, powered by [@vueuse/head](https://github.com/vueuse/head).
The `useHead` composable function allows you to manage your head tags in a programmatic and reactive way,
powered by [Unhead](https://unhead.harlanzw.com/).
As with all composables, it can only be used with a components `setup` and lifecycle hooks.
@ -62,7 +63,7 @@ useHead({
bodyAttrs: {
class: 'test'
},
script: [ { children: 'console.log(\'Hello world\')' } ]
script: [ { innerHTML: 'console.log(\'Hello world\')' } ]
})
</script>
```
@ -153,6 +154,7 @@ The below is the non-reactive types used for `useHead`, `app.head` and component
interface MetaObject {
title?: string
titleTemplate?: string | ((title?: string) => string)
templateParams?: Record<string, string | Record<string, string>>
base?: Base
link?: Link[]
meta?: Meta[]
@ -164,7 +166,7 @@ interface MetaObject {
}
```
See [zhead](https://github.com/harlan-zw/zhead/tree/main/packages/schema/src) for more detailed types.
See [@unhead/schema](https://github.com/unjs/unhead/blob/main/packages/schema/src/schema.ts) for more detailed types.
## Features
@ -228,7 +230,7 @@ Now, if you set the title to `My Page` with `useHead` on another page of your si
### Body Tags
You can use the `body: true` option on the `link` and `script` meta tags to append them to the end of the `<body>` tag.
You can use the `tagPosition: 'bodyClose'` option on applicable tags to append them to the end of the `<body>` tag.
For example:
@ -238,7 +240,7 @@ useHead({
script: [
{
src: 'https://third-party-script.com',
body: true
tagPosition: 'bodyClose' // valid options are: 'head' | 'bodyClose' | 'bodyOpen'
}
]
})

View File

@ -4,9 +4,7 @@ description: useHead customizes the head properties of individual pages of your
# `useHead`
Nuxt provides the `useHead` composable to add and customize the head properties of individual pages of your Nuxt app.
`useHead` is powered by [@vueuse/head](https://github.com/vueuse/head), you can find more in-depth documentation [here](https://unhead.harlanzw.com/)
The `useHead` composable function allows you to manage your head tags in a programmatic and reactive way, powered by [Unhead](https://unhead.harlanzw.com/).
::ReadMore{link="/docs/getting-started/seo-meta"}
::
@ -17,7 +15,7 @@ Nuxt provides the `useHead` composable to add and customize the head properties
useHead(meta: MaybeComputedRef<MetaObject>): void
```
Below are the non-reactive types for `useHead`. See [zhead](https://github.com/harlan-zw/zhead/tree/main/packages/schema/src) for more detailed types.
Below are the non-reactive types for `useHead`.
```ts
interface MetaObject {
@ -34,6 +32,8 @@ interface MetaObject {
}
```
See [@unhead/schema](https://github.com/unjs/unhead/blob/main/packages/schema/src/schema.ts) for more detailed types.
::alert{type=info}
The properties of `useHead` can be dynamic, accepting `ref`, `computed` and `reactive` properties. `meta` parameter can also accept a function returning an object to make the entire object reactive.
::

View File

@ -26,6 +26,6 @@ export default defineBuildConfig({
'nuxt/schema',
'@vue/reactivity',
'@vue/shared',
'@vueuse/head'
'@unhead/vue'
]
})

View File

@ -52,10 +52,10 @@
"@nuxt/telemetry": "^2.1.10",
"@nuxt/ui-templates": "^1.1.1",
"@nuxt/vite-builder": "3.2.3",
"@unhead/ssr": "^1.1.16",
"@unhead/ssr": "^1.1.20",
"@unhead/vue": "^1.1.20",
"@vue/reactivity": "^3.2.47",
"@vue/shared": "^3.2.47",
"@vueuse/head": "^1.1.15",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.2",
@ -82,7 +82,6 @@
"ufo": "^1.1.1",
"unctx": "^2.1.2",
"unenv": "^1.2.1",
"unhead": "^1.1.16",
"unimport": "^3.0.2",
"unplugin": "^1.1.0",
"untyped": "^1.2.2",

View File

@ -7,7 +7,7 @@ import escapeRE from 'escape-string-regexp'
import { defu } from 'defu'
import fsExtra from 'fs-extra'
import { dynamicEventHandler } from 'h3'
import { createHeadCore } from 'unhead'
import { createHeadCore } from '@unhead/vue'
import { renderSSRHead } from '@unhead/ssr'
import { distDir } from '../dirs'
import { ImportProtectionPlugin } from './plugins/import-protection'

View File

@ -1,5 +1,5 @@
import { resolve } from 'pathe'
import { addComponent, addPlugin, defineNuxtModule } from '@nuxt/kit'
import { addComponent, addPlugin, defineNuxtModule, tryResolveModule } from '@nuxt/kit'
import { distDir } from '../dirs'
const components = ['NoScript', 'Link', 'Base', 'Title', 'Meta', 'Style', 'Head', 'Html', 'Body']
@ -11,8 +11,8 @@ export default defineNuxtModule({
setup (options, nuxt) {
const runtimeDir = nuxt.options.alias['#head'] || resolve(distDir, 'head/runtime')
// Transpile @nuxt/meta and @vueuse/head
nuxt.options.build.transpile.push('@vueuse/head')
// Transpile @unhead/vue
nuxt.options.build.transpile.push('@unhead/vue')
// Add #head alias
nuxt.options.alias['#head'] = runtimeDir
@ -30,8 +30,14 @@ export default defineNuxtModule({
kebabName: componentName
})
}
// Opt-out feature allowing dependencies using @vueuse/head to work
if (nuxt.options.experimental.polyfillVueUseHead) {
// backwards compatibility
nuxt.options.alias['@vueuse/head'] = tryResolveModule('@unhead/vue') || '@unhead/vue'
addPlugin({ src: resolve(runtimeDir, 'lib/vueuse-head-polyfill.plugin') })
}
// Add library specific plugin
addPlugin({ src: resolve(runtimeDir, 'lib/vueuse-head.plugin') })
// Add library-specific plugin
addPlugin({ src: resolve(runtimeDir, 'lib/unhead.plugin') })
}
})

View File

@ -1,5 +1,5 @@
import type { HeadEntryOptions, UseHeadInput, ActiveHeadEntry } from '@vueuse/head'
import { useSeoMeta as _useSeoMeta } from '@vueuse/head'
import type { HeadEntryOptions, UseHeadInput, ActiveHeadEntry } from '@unhead/vue'
import { useSeoMeta as _useSeoMeta } from '@unhead/vue'
import type { HeadAugmentations } from 'nuxt/schema'
import { useNuxtApp } from '#app/nuxt'

View File

@ -1,4 +1,4 @@
import type { UseHeadInput } from '@vueuse/head'
import type { UseHeadInput } from '@unhead/vue'
import type { HeadAugmentations } from 'nuxt/schema'
export * from './composables'

View File

@ -1,4 +1,4 @@
import { createHead, useHead } from '@vueuse/head'
import { createHead, useHead } from '@unhead/vue'
import { renderSSRHead } from '@unhead/ssr'
import { defineNuxtPlugin } from '#app/nuxt'
// @ts-expect-error untyped
@ -16,25 +16,25 @@ export default defineNuxtPlugin((nuxtApp) => {
const unpauseDom = () => {
pauseDOMUpdates = false
// triggers dom update
head.internalHooks.callHook('entries:updated', head.unhead)
head.hooks.callHook('entries:updated', head)
}
head.internalHooks.hook('dom:beforeRender', (context) => { context.shouldRender = !pauseDOMUpdates })
head.hooks.hook('dom:beforeRender', (context) => { context.shouldRender = !pauseDOMUpdates })
nuxtApp.hooks.hook('page:start', () => { pauseDOMUpdates = true })
// wait for new page before unpausing dom updates (triggered after suspense resolved)
nuxtApp.hooks.hook('page:finish', unpauseDom)
nuxtApp.hooks.hook('app:mounted', unpauseDom)
}
// useHead does not depend on a vue component context, we keep it on the nuxtApp for backwards compatibility
// support backwards compatibility, remove at some point
nuxtApp._useHead = useHead
if (process.server) {
nuxtApp.ssrContext!.renderMeta = async () => {
const meta = await renderSSRHead(head.unhead)
const meta = await renderSSRHead(head)
return {
...meta,
bodyScriptsPrepend: meta.bodyTagsOpen,
// resolves naming difference with NuxtMeta and @vueuse/head
// resolves naming difference with NuxtMeta and Unhead
bodyScripts: meta.bodyTags
}
}

View File

@ -0,0 +1,8 @@
// @ts-expect-error ts failing with type
import { polyfillAsVueUseHead } from '@unhead/vue/polyfill'
import { defineNuxtPlugin } from '#app/nuxt'
export default defineNuxtPlugin((nuxtApp) => {
// avoid breaking ecosystem dependencies using low-level @vueuse/head APIs
polyfillAsVueUseHead(nuxtApp.vueApp._context.provides.usehead)
})

View File

@ -147,6 +147,14 @@ export default defineUntypedSchema({
*
* @see https://github.com/nuxt/nuxt/issues/15592
*/
configSchema: true
configSchema: true,
/**
* Whether or not to add a compatibility layer for modules, plugins or user code relying on the old
* `@vueuse/head` API.
*
* This can be disabled for most Nuxt sites to reduce the client-side bundle by ~0.5kb.
*/
polyfillVueUseHead: true
}
})

View File

@ -418,10 +418,10 @@ importers:
'@nuxt/vite-builder': workspace:*
'@types/fs-extra': ^11.0.1
'@types/hash-sum': ^1.0.0
'@unhead/ssr': ^1.1.16
'@unhead/ssr': ^1.1.20
'@unhead/vue': ^1.1.20
'@vue/reactivity': ^3.2.47
'@vue/shared': ^3.2.47
'@vueuse/head': ^1.1.15
chokidar: ^3.5.3
cookie-es: ^0.5.0
defu: ^6.1.2
@ -449,7 +449,6 @@ importers:
unbuild: ^1.1.2
unctx: ^2.1.2
unenv: ^1.2.1
unhead: ^1.1.16
unimport: ^3.0.2
unplugin: ^1.1.0
untyped: ^1.2.2
@ -464,10 +463,10 @@ importers:
'@nuxt/telemetry': 2.1.10
'@nuxt/ui-templates': 1.1.1
'@nuxt/vite-builder': link:../vite
'@unhead/ssr': 1.1.16
'@unhead/ssr': 1.1.20
'@unhead/vue': 1.1.20_vue@3.2.47
'@vue/reactivity': 3.2.47
'@vue/shared': 3.2.47
'@vueuse/head': 1.1.15_vue@3.2.47
chokidar: 3.5.3
cookie-es: 0.5.0
defu: 6.1.2
@ -494,7 +493,6 @@ importers:
ufo: 1.1.1
unctx: 2.1.2
unenv: 1.2.1
unhead: 1.1.16
unimport: 3.0.2
unplugin: 1.1.0
untyped: 1.2.2
@ -2304,11 +2302,11 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
/@unhead/dom/1.1.16:
resolution: {integrity: sha512-4JlwF4pNVynZsxT5687ntvS2yafGtWg7Y76IAPmLSOVrZ6fpYqF/vUn5+eCbMb0Og4C9ECrkbD9NSIqAiQ/0/Q==}
/@unhead/dom/1.1.20:
resolution: {integrity: sha512-LKd3Wq1myQmhea/5ysa9LsWWYFgtRhJ8D75PQfsIz9SOvjAUs6gNJi2Tv9adXr5gfFGXd0s2EMTLk/0IPTBwdg==}
dependencies:
'@unhead/schema': 1.1.16
'@unhead/shared': 1.1.16
'@unhead/schema': 1.1.20
'@unhead/shared': 1.1.20
dev: false
/@unhead/schema/1.1.16:
@ -2316,29 +2314,37 @@ packages:
dependencies:
hookable: 5.4.2
zhead: 2.0.4
dev: true
/@unhead/shared/1.1.16:
resolution: {integrity: sha512-Fien3han5vZ4sIaH89Tk9o7eensIDEdRynbwnik+WNo0PTfmaN/LCa3EYsacOdNYeZ3eMjjYpmQt6r+FJCqzSA==}
/@unhead/schema/1.1.20:
resolution: {integrity: sha512-XAMPCJjE+AhOW+HCYgxwNT7KX1Ch0y/byduJvM3kdwk4oA5lHRxF7M+s98FTZPpGJmlJr07CdW3l3w+qxmaJLQ==}
dependencies:
'@unhead/schema': 1.1.16
hookable: 5.4.2
zhead: 2.0.4
dev: false
/@unhead/ssr/1.1.16:
resolution: {integrity: sha512-SCay47uCx7jHL30iX/D0chxOLLY5m2/WCU5bepCQlzFO25OFC6cEEYtG6/BQOIRVnyN8a2KvOU38jgL9xQZN9A==}
/@unhead/shared/1.1.20:
resolution: {integrity: sha512-8fJ1hB8bRw7JM9Lq98OSmLeDmajHHA9ymg8y7aDBC/R5lq+/WWSMtmXs+JNs4rsl+1gHPdl4YbjdEHp6OvcnpA==}
dependencies:
'@unhead/schema': 1.1.16
'@unhead/shared': 1.1.16
'@unhead/schema': 1.1.20
dev: false
/@unhead/vue/1.1.16_vue@3.2.47:
resolution: {integrity: sha512-EbE0kqJHDmebF+X6fL7HqGnbTa+b6h6SzIvE+D0i0/IwhD2lXRrPcBy0XyLV9CLBpAbLVG4OByxnBX+7Kk/vUA==}
/@unhead/ssr/1.1.20:
resolution: {integrity: sha512-h+5rH4dMe+SorWJv2JiBVE8uchdXFbuSUfXtKBw/VmlBE69DXlZMEcIa2BDmp12wRiU9W8MtWOSvvPuBQ2Hm3g==}
dependencies:
'@unhead/schema': 1.1.20
'@unhead/shared': 1.1.20
dev: false
/@unhead/vue/1.1.20_vue@3.2.47:
resolution: {integrity: sha512-I6TaAcO+B+czD/W6I6HQ2EFu/aJ7r0/EEz4Ltlg/YrdKL1beqh2nDY+R1Xz43EqKS0yvxGTlJHJ9cBmtSP2b0g==}
peerDependencies:
vue: '>=2.7 || >=3'
dependencies:
'@unhead/schema': 1.1.16
'@unhead/shared': 1.1.16
'@unhead/schema': 1.1.20
'@unhead/shared': 1.1.20
hookable: 5.4.2
unhead: 1.1.16
unhead: 1.1.20
vue: 3.2.47
dev: false
@ -2794,18 +2800,6 @@ packages:
- vue
dev: true
/@vueuse/head/1.1.15_vue@3.2.47:
resolution: {integrity: sha512-LJqvb7dpSqnsdn6YWUxv97vWCnn/s6IfBrE4ih5kRlh8XQXr/HjXJ8IyIxxp0X7QDr3FhOsjRDpJSiQbDYbBdQ==}
peerDependencies:
vue: '>=2.7 || >=3'
dependencies:
'@unhead/dom': 1.1.16
'@unhead/schema': 1.1.16
'@unhead/ssr': 1.1.16
'@unhead/vue': 1.1.16_vue@3.2.47
vue: 3.2.47
dev: false
/@vueuse/integrations/9.12.0_focus-trap@7.3.1:
resolution: {integrity: sha512-bu0hOQAqg7A8S33RHpr49LuzVQJ4tK4oyimEfhPFGUVqmz/MMcwPH8Lde+MbVXvfYh2hrtwNv9S38pCmonRx4w==}
peerDependencies:
@ -8199,12 +8193,12 @@ packages:
node-fetch-native: 1.0.2
pathe: 1.1.0
/unhead/1.1.16:
resolution: {integrity: sha512-m2cPqEkgwHnA/di9P17TRS21p4aAJw4QolwTyXM+gzrlnFPe8gqOYuwEVnLRMS/NgoNWH7iPDOGyf5bbbrnTaw==}
/unhead/1.1.20:
resolution: {integrity: sha512-fic5YOINP2BD5PvyF05FIayIRaRYjujhuJ2uo2dIpLItAIBHYmBsTEkljsrwC+EULNfeC3Rf4UHxmr8u0EewCA==}
dependencies:
'@unhead/dom': 1.1.16
'@unhead/schema': 1.1.16
'@unhead/shared': 1.1.16
'@unhead/dom': 1.1.20
'@unhead/schema': 1.1.20
'@unhead/shared': 1.1.20
hookable: 5.4.2
dev: false

View File

@ -355,7 +355,7 @@ describe('nuxt links', () => {
})
describe('head tags', () => {
it('should render tags', async () => {
it('SSR should render tags', async () => {
const headHtml = await $fetch('/head')
expect(headHtml).toContain('<title>Using a dynamic component - Title Template Fn Change</title>')
@ -377,6 +377,19 @@ describe('head tags', () => {
expect(indexHtml).toContain('<title>Basic fixture</title>')
})
it('SPA should render appHead tags', async () => {
const headHtml = await $fetch('/head', { headers: { 'x-nuxt-no-ssr': '1' } })
expect(headHtml).toContain('<meta name="description" content="Nuxt Fixture">')
expect(headHtml).toContain('<meta charset="utf-8">')
expect(headHtml).toContain('<meta name="viewport" content="width=1024, initial-scale=1">')
})
it('legacy vueuse/head works', async () => {
const headHtml = await $fetch('/vueuse-head')
expect(headHtml).toContain('<title>using provides usehead and updateDOM - VueUse head polyfill test</title>')
})
it('should render http-equiv correctly', async () => {
const html = await $fetch('/head')
// http-equiv should be rendered kebab case

View File

@ -40,10 +40,10 @@ describe.skipIf(isWindows)('minimal nuxt application', () => {
it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect(stats.server.totalBytes).toBeLessThan(93000)
expect(stats.server.totalBytes).toBeLessThan(94400)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(modules.totalBytes).toBeLessThan(2722000)
expect(modules.totalBytes).toBeLessThan(2713000)
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@ -55,7 +55,6 @@ describe.skipIf(isWindows)('minimal nuxt application', () => {
"@unhead/dom",
"@unhead/shared",
"@unhead/ssr",
"@unhead/vue",
"@vue/compiler-core",
"@vue/compiler-dom",
"@vue/compiler-ssr",

View File

@ -18,7 +18,11 @@ export default defineNuxtConfig({
head: {
charset: 'utf-8',
link: [undefined],
meta: [{ name: 'viewport', content: 'width=1024, initial-scale=1' }, { charset: 'utf-8' }]
meta: [
{ name: 'viewport', content: 'width=1024, initial-scale=1' },
{ charset: 'utf-8' },
{ name: 'description', content: 'Nuxt Fixture' }
]
}
},
buildDir: process.env.NITRO_BUILD_DIR,

View File

@ -0,0 +1,24 @@
<script lang="ts" setup>
function useLegacyUseHead () {
useHead({
titleTemplate: '%s - VueUse head polyfill test'
})
}
function useLegacyVueUseHead () {
// get vm
const vm = getCurrentInstance()
const head = vm?.appContext.provides.usehead
head.addHeadObjs({
title: 'using provides usehead and updateDOM'
})
head.updateDOM()
}
useLegacyUseHead()
useLegacyVueUseHead()
</script>
<template>
<div>
<h1>VueUse head polyfill test</h1>
</div>
</template>