From 1091d456a8ba6d5d0b57c37bfb959451b0cbce2b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 11 Apr 2022 10:03:31 +0100 Subject: [PATCH] feat(nuxt, bridge): support `titleTemplate`, `viewport` and `charset` for `useHead` (#4221) --- .../2.guide/2.features/4.head-management.md | 37 ++++++++++--------- docs/content/migration/4.meta.md | 2 +- examples/composables/use-head/app.vue | 1 + packages/nuxt3/src/head/module.ts | 9 ++--- .../head/runtime/lib/vueuse-head.plugin.ts | 26 +++++++++++-- packages/schema/src/config/_app.ts | 2 + packages/schema/src/types/meta.ts | 2 + test/basic.test.ts | 12 ++++-- test/fixtures/basic/pages/head.vue | 2 +- test/fixtures/basic/plugins/my-plugin.ts | 3 ++ 10 files changed, 65 insertions(+), 31 deletions(-) diff --git a/docs/content/2.guide/2.features/4.head-management.md b/docs/content/2.guide/2.features/4.head-management.md index 0bb78fad1b..7997fbdddc 100644 --- a/docs/content/2.guide/2.features/4.head-management.md +++ b/docs/content/2.guide/2.features/4.head-management.md @@ -1,26 +1,29 @@ # 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 -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: -```js -export default { - setup () { - useHead({ - meta: [ - { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' } - ], - bodyAttrs: { - class: 'test' - } - }) +```vue + ``` ::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. -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] ``` -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] ``` diff --git a/docs/content/migration/4.meta.md b/docs/content/migration/4.meta.md index cd49aa187c..beb31a055d 100644 --- a/docs/content/migration/4.meta.md +++ b/docs/content/migration/4.meta.md @@ -11,7 +11,7 @@ Nuxt 3 provides several different ways to manage your meta tags. 2. Through the `useHead` [composable](/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=📦} Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change. diff --git a/examples/composables/use-head/app.vue b/examples/composables/use-head/app.vue index ee991987b9..335ffbd8f9 100644 --- a/examples/composables/use-head/app.vue +++ b/examples/composables/use-head/app.vue @@ -27,6 +27,7 @@ export default { setup () { useHead({ + titleTemplate: '%s - useHead example', bodyAttrs: { class: 'test' } diff --git a/packages/nuxt3/src/head/module.ts b/packages/nuxt3/src/head/module.ts index 7c33f48f49..f366bb4aea 100644 --- a/packages/nuxt3/src/head/module.ts +++ b/packages/nuxt3/src/head/module.ts @@ -21,12 +21,11 @@ export default defineNuxtModule({ // Add #head alias 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, { - meta: [ - { charset: options.charset }, - { name: 'viewport', content: options.viewport } - ] + charset: options.charset, + viewport: options.viewport }) // Add global meta configuration diff --git a/packages/nuxt3/src/head/runtime/lib/vueuse-head.plugin.ts b/packages/nuxt3/src/head/runtime/lib/vueuse-head.plugin.ts index ead35fd18c..86b7826aa0 100644 --- a/packages/nuxt3/src/head/runtime/lib/vueuse-head.plugin.ts +++ b/packages/nuxt3/src/head/runtime/lib/vueuse-head.plugin.ts @@ -1,5 +1,6 @@ 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 { defineNuxtPlugin } from '#app' @@ -14,9 +15,26 @@ export default defineNuxtPlugin((nuxtApp) => { headReady = true }) - nuxtApp._useHead = (meta: MetaObject) => { - const headObj = ref(meta as any) - head.addHeadObjs(headObj) + const titleTemplate = ref() + + nuxtApp._useHead = (meta: MetaObject | ComputedGetter) => { + 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 } diff --git a/packages/schema/src/config/_app.ts b/packages/schema/src/config/_app.ts index 3288fc3e7e..ea3288ba71 100644 --- a/packages/schema/src/config/_app.ts +++ b/packages/schema/src/config/_app.ts @@ -102,6 +102,8 @@ export default { head: { $resolve: (val, get) => { return defu(val, get('meta'), { + charset: 'utf-8', + viewport: 'width=device-width, initial-scale=1', meta: [], link: [], style: [], diff --git a/packages/schema/src/types/meta.ts b/packages/schema/src/types/meta.ts index 7a7381f570..f25ea68e5a 100644 --- a/packages/schema/src/types/meta.ts +++ b/packages/schema/src/types/meta.ts @@ -21,4 +21,6 @@ export interface MetaObject extends Record { style?: Array> /** Each item in the array maps to a newly-created `') + + const index = await $fetch('/') + // should render charset by default + expect(index).toContain('') + // should render components + expect(index).toContain('Basic fixture - Fixture') }) }) diff --git a/test/fixtures/basic/pages/head.vue b/test/fixtures/basic/pages/head.vue index a8c592d95f..d5b8a1029f 100644 --- a/test/fixtures/basic/pages/head.vue +++ b/test/fixtures/basic/pages/head.vue @@ -5,7 +5,7 @@ useHead({ }, 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")' }] }) diff --git a/test/fixtures/basic/plugins/my-plugin.ts b/test/fixtures/basic/plugins/my-plugin.ts index 264cd636ff..434af55e32 100644 --- a/test/fixtures/basic/plugins/my-plugin.ts +++ b/test/fixtures/basic/plugins/my-plugin.ts @@ -1,4 +1,7 @@ export default defineNuxtPlugin(() => { + useHead({ + titleTemplate: '%s - Fixture' + }) return { provide: { myPlugin: () => 'Injected by my-plugin'