feat(nuxt, bridge): support titleTemplate, viewport and charset for useHead (#4221)

This commit is contained in:
Daniel Roe 2022-04-11 10:03:31 +01:00 committed by GitHub
parent 041e8694d1
commit 1091d456a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 31 deletions

View File

@ -1,26 +1,29 @@
# Head Management # Head Management
You can customize the meta tags for your site through several different ways: Out-of-the-box, Nuxt provides good default values for `charset` and `viewport` meta tags, but you can override these if you need to, as well as customizing other meta tags for your site in several different ways.
:ReadMore{link="/api/configuration/nuxt.config#head"}
## `useHead` Composable ## `useHead` Composable
Within your `setup` function, you can call `useHead` with an object of meta properties with keys corresponding to meta tags: `title`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. Alternatively, you can pass a function returning the object for reactive metadata. Within your `setup` function, you can call `useHead` with an object of meta properties with keys corresponding to meta tags: `title`, `titleTemplate`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. There are also two shorthand properties, `charset` and `viewport`, which set the corresponding meta tags. Alternatively, you can pass a function returning the object for reactive metadata.
For example: For example:
```js ```vue
export default { <script setup>
setup () { useHead({
useHead({ titleTemplate: 'My App - %s', // or, title => `My App - ${title}`
meta: [ viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' } charset: 'utf-8',
], meta: [
bodyAttrs: { { name: 'description', content: 'My amazing site.' }
class: 'test' ],
} bodyAttrs: {
}) class: 'test'
} }
} })
</script>
``` ```
::ReadMore{link="/api/composables/use-head"} ::ReadMore{link="/api/composables/use-head"}
@ -68,7 +71,7 @@ export default {
You can use `definePageMeta` along with `useHead` to set metadata based on the current route. You can use `definePageMeta` along with `useHead` to set metadata based on the current route.
For example, to include the page title alongside your app name, first define your page title: For example, you can first set the current page title (this is extracted at build time via a macro, so it can't be set dynamically):
```vue{}[pages/some-page.vue] ```vue{}[pages/some-page.vue]
<script setup> <script setup>
@ -78,14 +81,14 @@ definePageMeta({
</script> </script>
``` ```
And then in your layout file: And then in your layout file you might use the route metadata you've previously set:
```vue{}[layouts/default.vue] ```vue{}[layouts/default.vue]
<script setup> <script setup>
const route = useRoute() const route = useRoute()
useHead({ useHead({
title: computed(() => `App Name - ${route.meta.title}`) meta: [{ name: 'og:title', content: `App Name - ${route.meta.title}` }]
}) })
</script> </script>
``` ```

View File

@ -11,7 +11,7 @@ Nuxt 3 provides several different ways to manage your meta tags.
2. Through the `useHead` [composable](/guide/features/head-management) 2. Through the `useHead` [composable](/guide/features/head-management)
3. Through [global meta components](/guide/features/head-management) 3. Through [global meta components](/guide/features/head-management)
You can customize `title`, `base`, `script`, `style`, `meta`, `link`, `htmlAttrs` and `bodyAttrs`. You can customize `title`, `titleTemplate`, `base`, `script`, `style`, `meta`, `link`, `htmlAttrs` and `bodyAttrs`.
::alert{icon=📦} ::alert{icon=📦}
Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change. Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change.

View File

@ -27,6 +27,7 @@
export default { export default {
setup () { setup () {
useHead({ useHead({
titleTemplate: '%s - useHead example',
bodyAttrs: { bodyAttrs: {
class: 'test' class: 'test'
} }

View File

@ -21,12 +21,11 @@ export default defineNuxtModule({
// Add #head alias // Add #head alias
nuxt.options.alias['#head'] = runtimeDir nuxt.options.alias['#head'] = runtimeDir
// Global meta // Global meta -for Bridge, this is necessary to repeat here
// and in packages/schema/src/config/_app.ts
const globalMeta: MetaObject = defu(nuxt.options.app.head, { const globalMeta: MetaObject = defu(nuxt.options.app.head, {
meta: [ charset: options.charset,
{ charset: options.charset }, viewport: options.viewport
{ name: 'viewport', content: options.viewport }
]
}) })
// Add global meta configuration // Add global meta configuration

View File

@ -1,5 +1,6 @@
import { createHead, renderHeadToString } from '@vueuse/head' import { createHead, renderHeadToString } from '@vueuse/head'
import { ref, watchEffect, onBeforeUnmount, getCurrentInstance } from 'vue' import { computed, ref, unref, watchEffect, onBeforeUnmount, getCurrentInstance, ComputedGetter } from 'vue'
import defu from 'defu'
import type { MetaObject } from '..' import type { MetaObject } from '..'
import { defineNuxtPlugin } from '#app' import { defineNuxtPlugin } from '#app'
@ -14,9 +15,26 @@ export default defineNuxtPlugin((nuxtApp) => {
headReady = true headReady = true
}) })
nuxtApp._useHead = (meta: MetaObject) => { const titleTemplate = ref<MetaObject['titleTemplate']>()
const headObj = ref(meta as any)
head.addHeadObjs(headObj) nuxtApp._useHead = (meta: MetaObject | ComputedGetter<MetaObject>) => {
titleTemplate.value = (unref(meta) as MetaObject).titleTemplate || titleTemplate.value
const headObj = computed(() => {
const overrides: MetaObject = { meta: [] }
const val = unref(meta) as MetaObject
if (titleTemplate.value && 'title' in val) {
overrides.title = typeof titleTemplate.value === 'function' ? titleTemplate.value(val.title) : titleTemplate.value.replace(/%s/g, val.title)
}
if (val.charset) {
overrides.meta!.push({ key: 'charset', charset: val.charset })
}
if (val.viewport) {
overrides.meta!.push({ name: 'viewport', content: val.viewport })
}
return defu(overrides, val)
})
head.addHeadObjs(headObj as any)
if (process.server) { return } if (process.server) { return }

View File

@ -102,6 +102,8 @@ export default {
head: { head: {
$resolve: (val, get) => { $resolve: (val, get) => {
return defu(val, get('meta'), { return defu(val, get('meta'), {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
meta: [], meta: [],
link: [], link: [],
style: [], style: [],

View File

@ -21,4 +21,6 @@ export interface MetaObject extends Record<string, any> {
style?: Array<Record<string, any>> style?: Array<Record<string, any>>
/** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */ /** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */
script?: Array<Record<string, any>> script?: Array<Record<string, any>>
titleTemplate?: string | ((title: string) => string)
} }

View File

@ -36,8 +36,6 @@ describe('pages', () => {
// should render text // should render text
expect(html).toContain('Hello Nuxt 3!') expect(html).toContain('Hello Nuxt 3!')
// should render <Head> components
expect(html).toContain('<title>Basic fixture</title>')
// should inject runtime config // should inject runtime config
expect(html).toContain('RuntimeConfig | testConfig: 123') expect(html).toContain('RuntimeConfig | testConfig: 123')
// composables auto import // composables auto import
@ -119,12 +117,20 @@ describe('pages', () => {
describe('head tags', () => { describe('head tags', () => {
it('should render tags', async () => { it('should render tags', async () => {
const html = await $fetch('/head') const html = await $fetch('/head')
expect(html).toContain('<title>Using a dynamic component</title>') expect(html).toContain('<title>Using a dynamic component - Fixture</title>')
expect(html).not.toContain('<meta name="description" content="first">') expect(html).not.toContain('<meta name="description" content="first">')
expect(html).toContain('<meta charset="utf-16">')
expect(html).not.toContain('<meta charset="utf-8">')
expect(html).toContain('<meta name="description" content="overriding with an inline useHead call">') expect(html).toContain('<meta name="description" content="overriding with an inline useHead call">')
expect(html).toMatch(/<html[^>]*class="html-attrs-test"/) expect(html).toMatch(/<html[^>]*class="html-attrs-test"/)
expect(html).toMatch(/<body[^>]*class="body-attrs-test"/) expect(html).toMatch(/<body[^>]*class="body-attrs-test"/)
expect(html).toContain('script>console.log("works with useMeta too")</script>') expect(html).toContain('script>console.log("works with useMeta too")</script>')
const index = await $fetch('/')
// should render charset by default
expect(index).toContain('<meta charset="utf-8">')
// should render <Head> components
expect(index).toContain('<title>Basic fixture - Fixture</title>')
}) })
}) })

View File

@ -5,7 +5,7 @@ useHead({
}, },
meta: [{ name: 'description', content: 'first' }] meta: [{ name: 'description', content: 'first' }]
}) })
useHead({ meta: [{ name: 'description', content: 'overriding with an inline useHead call' }] }) useHead({ charset: 'utf-16', meta: [{ name: 'description', content: 'overriding with an inline useHead call' }] })
useMeta({ script: [{ children: 'console.log("works with useMeta too")' }] }) useMeta({ script: [{ children: 'console.log("works with useMeta too")' }] })
</script> </script>

View File

@ -1,4 +1,7 @@
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
useHead({
titleTemplate: '%s - Fixture'
})
return { return {
provide: { provide: {
myPlugin: () => 'Injected by my-plugin' myPlugin: () => 'Injected by my-plugin'