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

Commit

Permalink
feat(nuxt, schema): official @vueuse/head v1 support (#8975)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw authored and danielroe committed Jan 21, 2023
1 parent 3f1cea0 commit e4a8498
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 211 deletions.
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']
}

0 comments on commit e4a8498

Please sign in to comment.