Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat: @vueuse/head v1 #8908

Closed
wants to merge 12 commits into from
2 changes: 1 addition & 1 deletion packages/nuxt/package.json
Expand Up @@ -44,7 +44,7 @@
"@nuxt/vite-builder": "3.0.0-rc.13",
"@vue/reactivity": "^3.2.44",
"@vue/shared": "^3.2.44",
"@vueuse/head": "~1.0.0-rc.14",
"@unhead/head": "~1.0.0-next.2",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.0",
Expand Down
3 changes: 0 additions & 3 deletions packages/nuxt/src/head/module.ts
Expand Up @@ -29,9 +29,6 @@ export default defineNuxtModule({
})
}

// Add mixin plugin
addPlugin({ src: resolve(runtimeDir, 'mixin-plugin') })

// Add library specific plugin
addPlugin({ src: resolve(runtimeDir, 'lib/vueuse-head.plugin') })
}
Expand Down
8 changes: 4 additions & 4 deletions packages/nuxt/src/head/runtime/composables.ts
@@ -1,5 +1,5 @@
import type { MetaObject } from '@nuxt/schema'
import type { MaybeComputedRef } from '@vueuse/head'
import type { UseHeadInput } from '@vueuse/head'
import type { HeadAugmentations } from '@nuxt/schema'
import { useNuxtApp } from '#app'

/**
Expand All @@ -9,12 +9,12 @@ import { useNuxtApp } from '#app'
* Alternatively, for reactive meta state, you can pass in a function
* that returns a meta object.
*/
export function useHead (meta: MaybeComputedRef<MetaObject>) {
export function useHead<T extends HeadAugmentations> (meta: UseHeadInput<T>) {
useNuxtApp()._useHead(meta)
}

// TODO: remove useMeta support when Nuxt 3 is stable
/** @deprecated Please use new `useHead` composable instead */
export function useMeta (meta: MaybeComputedRef<MetaObject>) {
export function useMeta<T extends HeadAugmentations> (meta: UseHeadInput<T>) {
return useHead(meta)
}
5 changes: 4 additions & 1 deletion packages/nuxt/src/head/runtime/index.ts
@@ -1,2 +1,5 @@
import type { UseHeadInput } from '@vueuse/head'
import type { HeadAugmentations } from '@nuxt/schema'
export * from './composables'
export type { MetaObject } from '@nuxt/schema'

export type MetaObject = UseHeadInput<HeadAugmentations>
63 changes: 24 additions & 39 deletions packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
@@ -1,64 +1,49 @@
import type { HeadEntryOptions, MaybeComputedRef } from '@vueuse/head'
import { createHead, renderHeadToString } from '@vueuse/head'
import { onBeforeUnmount, getCurrentInstance } from 'vue'
import type { MetaObject } from '@nuxt/schema'
import { defineNuxtPlugin, useRouter } from '#app'
import { createHead, useHead } from '@vueuse/head'
import { defineNuxtPlugin } from '#app'
// @ts-expect-error untyped
import { appHead } from '#build/nuxt.config.mjs'

export default defineNuxtPlugin((nuxtApp) => {
const head = createHead()

head.addEntry(appHead, { resolved: true })
// we can server side render the head without having the client hydrate it
if (process.server) {
head.push(appHead, { mode: 'server' })
}

if (process.client && nuxtApp.ssrContext?.noSSR) {
head.push(appHead)
}

nuxtApp.vueApp.use(head)

if (process.client) {
// pause dom updates until page is ready and between page transitions
let pauseDOMUpdates = true
head.hooks['before:dom'].push(() => !pauseDOMUpdates)
nuxtApp.hooks.hookOnce('app:mounted', () => {
const unpauseDom = () => {
pauseDOMUpdates = false
head.updateDOM()

// start pausing DOM updates when route changes (trigger immediately)
useRouter().beforeEach(() => {
pauseDOMUpdates = true
})
// watch for new route before unpausing dom updates (triggered after suspense resolved)
useRouter().afterEach(() => {
// only if we have paused (clicking on a link to the current route triggers this)
if (pauseDOMUpdates) {
pauseDOMUpdates = false
head.updateDOM()
}
})
})
}

nuxtApp._useHead = (_meta: MaybeComputedRef<MetaObject>, options: HeadEntryOptions) => {
if (process.server) {
head.addEntry(_meta, options)
return
// triggers dom update
head.hooks.callHook('entries:updated', head)
}

const cleanUp = head.addReactiveEntry(_meta, options)

const vm = getCurrentInstance()
if (!vm) { return }

onBeforeUnmount(() => {
cleanUp()
head.updateDOM()
head.hooks.hook('dom:beforeRender', (context) => {
context.shouldRender = !pauseDOMUpdates
})
nuxtApp.hooks.hook('page:start', () => { pauseDOMUpdates = true })
// watch for new route before unpausing dom updates (triggered after suspense resolved)
nuxtApp.hooks.hook('page:finish', unpauseDom)
nuxtApp.hooks.hook('app:mounted', unpauseDom)
}

nuxtApp._useHead = useHead

if (process.server) {
nuxtApp.ssrContext!.renderMeta = async () => {
const meta = await renderHeadToString(head)
const { renderSSRHead } = await import('@unhead/ssr')
const meta = await renderSSRHead(head)
return {
...meta,
// resolves naming difference with NuxtMeta and @vueuse/head
bodyScriptsPrepend: meta.bodyTagsOpen,
bodyScripts: meta.bodyTags
}
}
Expand Down
24 changes: 0 additions & 24 deletions packages/nuxt/src/head/runtime/mixin-plugin.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/schema/package.json
Expand Up @@ -17,7 +17,7 @@
"@types/lodash.template": "^4",
"@types/semver": "^7",
"@vitejs/plugin-vue": "^3.2.0",
"@vueuse/head": "~1.0.0-rc.14",
"@unhead/schema": "~0.4.7",
"nitropack": "^0.6.1",
"unbuild": "latest",
"vite": "~3.2.3"
Expand Down
71 changes: 2 additions & 69 deletions packages/schema/src/types/meta.ts
@@ -1,4 +1,4 @@
import type { HeadObjectPlain, HeadObject } from '@vueuse/head'
import type { Head } from '@unhead/schema'

export interface HeadAugmentations {
// runtime type modifications
Expand All @@ -12,7 +12,7 @@ export interface HeadAugmentations {
bodyAttrs?: {}
}

export type MetaObjectRaw = HeadObjectPlain<HeadAugmentations>
export type MetaObjectRaw = Head<HeadAugmentations>

export type AppHeadMetaObject = MetaObjectRaw & {
/**
Expand All @@ -29,70 +29,3 @@ export type AppHeadMetaObject = MetaObjectRaw & {
*/
viewport?: string
}

export interface MetaObject {
Copy link
Collaborator Author

@harlan-zw harlan-zw Nov 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out why this needed to be exposed by @nuxt/schema exactly. The reactive object is only passed used as an argument for the useHead function, nuxt.config.ts uses the non-reactive schema.

The nuxt app itself exposes it as well. If we remove it then we don't need to depend on import the full module for the types

/**
* The <title> HTML element defines the document's title that is shown in a browser's title bar or a page's tab.
* It only contains text; tags within the element are ignored.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title
*/
title?: HeadObject<HeadAugmentations>['title']
/**
* Generate the title from a template.
*/
titleTemplate?: HeadObject<HeadAugmentations>['titleTemplate']
/**
* The <base> HTML element specifies the base URL to use for all relative URLs in a document.
* There can be only one <base> element in a document.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
*/
base?: HeadObject<HeadAugmentations>['base']
/**
* The <link> HTML element specifies relationships between the current document and an external resource.
* This element is most commonly used to link to stylesheets, but is also used to establish site icons
* (both "favicon" style icons and icons for the home screen and apps on mobile devices) among other things.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as
*/
link?: HeadObject<HeadAugmentations>['link']
/**
* The <meta> element represents metadata that cannot be expressed in other HTML elements, like <link> or <script>.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
*/
meta?: HeadObject<HeadAugmentations>['meta']
/**
* The <style> HTML element contains style information for a document, or part of a document.
* It contains CSS, which is applied to the contents of the document containing the <style> element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style
*/
style?: HeadObject<HeadAugmentations>['style']
/**
* The <script> HTML element is used to embed executable code or data; this is typically used to embed or refer to JavaScript code.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*/
script?: HeadObject<HeadAugmentations>['script']
/**
* The <noscript> HTML element defines a section of HTML to be inserted if a script type on the page is unsupported
* or if scripting is currently turned off in the browser.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript
*/
noscript?: HeadObject<HeadAugmentations>['noscript']
/**
* Attributes for the <html> HTML element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html
*/
htmlAttrs?: HeadObject<HeadAugmentations>['htmlAttrs']
/**
* Attributes for the <body> HTML element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body
*/
bodyAttrs?: HeadObject<HeadAugmentations>['bodyAttrs']
}
13 changes: 13 additions & 0 deletions playground/app.vue
@@ -1,4 +1,17 @@
<script setup lang="ts">
useHead({
title: 'Hello World',
htmlAttrs: {
lang: 'en',
style: 'background-color: salmon;'
},
meta: [
{
name: 'description',
content: 'Hello World'
}
]
})
</script>

<template>
Expand Down
18 changes: 17 additions & 1 deletion playground/nuxt.config.ts
@@ -1,3 +1,19 @@
export default defineNuxtConfig({

app: {
head: {
title: 'nuxt',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ key: 'description', name: 'description', content: '' }
],
script: [
{
src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js',
body: true
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}
}
})