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

feat(nuxt, schema): official @vueuse/head v1 support #8975

Merged
merged 21 commits into from Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 3 additions & 5 deletions docs/content/3.api/1.composables/use-head.md
Expand Up @@ -4,11 +4,9 @@ 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. It uses [@vueuse/head](https://github.com/vueuse/head) under the hood.
Nuxt provides the `useHead` composable to add and customize the head properties of individual pages of your Nuxt app.

::alert{icon=πŸ‘‰}
`useHead` only works during `setup` or `Lifecycle Hooks`.
::
`useHead` is powered by [@vueuse/head](https://github.com/vueuse/head), you can find more in-depth documentation [here](https://unhead.harlanzw.com/)

::ReadMore{link="/getting-started/seo-meta"}
::
Expand All @@ -19,7 +17,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 `useMeta`. 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`. See [zhead](https://github.com/harlan-zw/zhead/tree/main/packages/schema/src) for more detailed types.

```ts
interface MetaObject {
Expand Down
4 changes: 3 additions & 1 deletion packages/nuxt/package.json
Expand Up @@ -44,7 +44,9 @@
"@nuxt/vite-builder": "3.0.0-rc.13",
"@vue/reactivity": "^3.2.45",
"@vue/shared": "^3.2.45",
"@vueuse/head": "~1.0.0-rc.14",
"@vueuse/head": "^1.0.13",
"unhead": "^0.6.7",
"@unhead/ssr": "^0.6.7",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.0",
Expand Down
8 changes: 8 additions & 0 deletions packages/nuxt/src/core/nitro.ts
Expand Up @@ -9,6 +9,8 @@ import defu from 'defu'
import fsExtra from 'fs-extra'
import { dynamicEventHandler } from 'h3'
import type { Plugin } from 'rollup'
import { createHeadCore } from 'unhead'
import { renderSSRHead } from '@unhead/ssr'
import { distDir } from '../dirs'
import { ImportProtectionPlugin } from './plugins/import-protection'

Expand Down Expand Up @@ -122,6 +124,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
}
})

// Add head chunk for SPA renders
const head = createHeadCore()
head.push(nuxt.options.app.head)
const headChunk = await renderSSRHead(head)
nitroConfig.virtual!['#head-static'] = `export default ${JSON.stringify(headChunk)}`

// Add fallback server for `ssr: false`
if (!nuxt.options.ssr) {
nitroConfig.virtual!['#build/dist/server/server.mjs'] = 'export default () => {}'
Expand Down
5 changes: 4 additions & 1 deletion packages/nuxt/src/core/runtime/nitro/renderer.ts
Expand Up @@ -41,6 +41,9 @@ const getClientManifest: () => Promise<Manifest> = () => import('#build/dist/ser
.then(r => r.default || r)
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>

// @ts-ignore
const getStaticRenderedHead = () : Promise<NuxtMeta> => import('#head-static').then(r => r.default || r)

// @ts-ignore
const getServerEntry = () => import('#build/dist/server/server.mjs').then(r => r.default || r)

Expand Down Expand Up @@ -102,7 +105,7 @@ const getSPARenderer = lazyCachedFunction(async () => {
data: {},
state: {}
}
ssrContext!.renderMeta = ssrContext!.renderMeta ?? (() => ({}))
ssrContext!.renderMeta = ssrContext!.renderMeta ?? getStaticRenderedHead
return Promise.resolve(result)
}

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 { HeadEntryOptions, UseHeadInput, ActiveHeadEntry } from '@vueuse/head'
import type { HeadAugmentations } from '@nuxt/schema'
import { useNuxtApp } from '#app'

/**
Expand All @@ -9,6 +9,6 @@ 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>) {
useNuxtApp()._useHead(meta)
export function useHead<T extends HeadAugmentations> (input: UseHeadInput<T>, options?: HeadEntryOptions): ActiveHeadEntry<UseHeadInput<T>> | void {
return useNuxtApp()._useHead(input, options)
}
6 changes: 5 additions & 1 deletion packages/nuxt/src/head/runtime/index.ts
@@ -1,2 +1,6 @@
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>
58 changes: 17 additions & 41 deletions packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
@@ -1,63 +1,39 @@
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 })
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.internalHooks.callHook('entries:updated', head.unhead)
}

const cleanUp = head.addReactiveEntry(_meta, options)

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

onBeforeUnmount(() => {
cleanUp()
head.updateDOM()
})
head.internalHooks.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
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.unhead)
return {
...meta,
bodyScriptsPrepend: meta.bodyTagsOpen,
// resolves naming difference with NuxtMeta and @vueuse/head
bodyScripts: meta.bodyTags
}
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/build.config.ts
Expand Up @@ -22,7 +22,7 @@ export default defineBuildConfig({
'vue-meta',
'vue-router',
'vue-bundle-renderer',
'@vueuse/head',
'@unhead/schema',
'vue',
'hookable',
'nitropack',
Expand Down
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.6.7",
"nitropack": "^1.0.0-0",
"unbuild": "latest",
"vite": "~3.2.3"
Expand Down
74 changes: 4 additions & 70 deletions packages/schema/src/types/meta.ts
@@ -1,6 +1,6 @@
import type { HeadObjectPlain, HeadObject } from '@vueuse/head'
import type { Head, MergeHead } from '@unhead/schema'

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

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

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

export interface MetaObject {
/**
* 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']
}