refactor(nuxt3,bridge)!: rename `useMeta` to `useHead` (#4066)

This commit is contained in:
Daniel Roe 2022-04-05 15:02:29 +01:00 committed by GitHub
parent 7054b9849f
commit e90b8c28d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 163 additions and 81 deletions

View File

@ -198,13 +198,13 @@ If you want to use the new Nuxt composables (such as `useNuxtApp` or `useRuntime
Although a compatibility interface is provided via `nuxtApp.vueApp` you should avoid registering plugins, directives, mixins or components this way without adding your own logic to ensure they are not installed more than once, or this may cause a memory leak.
::
## New `useMeta` (optional)
## New `useHead` (optional)
Nuxt Bridge provides a new Nuxt 3 meta API that can be accessed with a new `useMeta` composable.
Nuxt Bridge provides a new Nuxt 3 meta API that can be accessed with a new `useHead` composable.
```vue
<script setup>
useMeta({
useHead({
title: 'My Nuxt App',
})
</script>
@ -222,10 +222,10 @@ export default defineNuxtConfig({
})
```
This `useMeta` composable uses `@vueuse/head` under the hood (rather than `vue-meta`) to manipulate your `<head>`.
Accordingly, we recommend not to use both the native Nuxt 2 `head()` properties as well as `useMeta`, as they may conflict.
This `useHead` composable uses `@vueuse/head` under the hood (rather than `vue-meta`) to manipulate your `<head>`.
Accordingly, we recommend not to use both the native Nuxt 2 `head()` properties as well as `useHead`, as they may conflict.
For more information on how to use this composable, see [the docs](/docs/usage/meta-tags#usemeta-composable).
For more information on how to use this composable, see [the docs](/docs/usage/meta-tags#usehead-composable).
## Feature Flags
@ -244,7 +244,7 @@ export default defineNuxtConfig({
// Use Vite as the bundler instead of webpack 4
// vite: true,
// Enable Nuxt 3 compatible useMeta
// Enable Nuxt 3 compatible useHead
// meta: true,
// -- Default features --

View File

@ -250,12 +250,12 @@ title.value = 'new title'
Be careful not to use both `useNuxt2Meta()` and the Options API `head()` within the same component, as behavior may be unpredictable.
::
Nuxt Bridge also provides a Nuxt 3-compatible meta implementation that can be accessed with the `useMeta` composable.
Nuxt Bridge also provides a Nuxt 3-compatible meta implementation that can be accessed with the `useHead` composable.
```diff
<script setup>
- import { useMeta } from '@nuxtjs/composition-api'
useMeta({
useHead({
title: 'My Nuxt App',
})
</script>
@ -272,6 +272,6 @@ export default defineNuxtConfig({
})
```
This `useMeta` composable uses `@vueuse/head` under the hood (rather than `vue-meta`) to manipulate your `<head>`. Accordingly, it is recommended not to use both the native Nuxt 2 `head()` properties as well as `useMeta`, as they may conflict.
This `useHead` composable uses `@vueuse/head` under the hood (rather than `vue-meta`) to manipulate your `<head>`. Accordingly, it is recommended not to use both the native Nuxt 2 `head()` properties as well as `useHead`, as they may conflict.
For more information on how to use this composable, see [the Nuxt 3 docs](/docs/usage/meta-tags#usemeta-composable).
For more information on how to use this composable, see [the Nuxt 3 docs](/docs/usage/meta-tags#usehead-composable).

View File

@ -2,16 +2,16 @@
You can customize the meta tags for your site through several different ways:
## `useMeta` Composable
## `useHead` Composable
Within your `setup` function, you can call `useMeta` 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`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. Alternatively, you can pass a function returning the object for reactive metadata.
For example:
```js
export default {
setup () {
useMeta({
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
],
@ -63,7 +63,7 @@ export default {
## Example: usage with definePageMeta
You can use `definePageMeta` along with `useMeta` 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:
@ -81,7 +81,7 @@ And then in your layout file:
<script setup>
const route = useRoute()
useMeta({
useHead({
title: computed(() => `App Name - ${route.meta.title}`)
})
</script>

View File

@ -3,7 +3,7 @@
Nuxt 3 provides several different ways to manage your meta tags.
1. Through your `nuxt.config`.
1. Through the `useMeta` [composable](/docs/usage/meta-tags#usemeta-composable)
1. Through the `useHead` [composable](/docs/usage/meta-tags#usehead-composable)
1. Through [global meta components](/docs/usage/meta-tags#meta-components)
You can customize `title`, `base`, `script`, `style`, `meta`, `link`, `htmlAttrs` and `bodyAttrs`.
@ -17,9 +17,9 @@ Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage yo
## Migration
1. In your `nuxt.config`, rename `head` to `meta`. Consider moving this shared meta configuration into your `app.vue` instead. (Note that objects no longer have a `hid` key for deduplication.)
1. In your components, rename your `head` option to `meta`. If you need to access the component state, you should migrate to using `useMeta`. You might also consider using the built-in meta-components.
1. If you need to access the component state with `head`, you should migrate to using `useHead`. You might also consider using the built-in meta-components.
### Example: `useMeta`
### Example: `useHead`
::code-group
@ -50,7 +50,7 @@ const title = ref('My App')
const description = ref('My App Description')
// This will be reactive even you change title/description above
useMeta({
useHead({
title,
meta: [{
name: 'description',

View File

@ -2,12 +2,12 @@
template: Example
---
# useMeta
# useHead
This example shows how to use `useMeta` and Nuxt built-in components to bind meta data to the head of the page.
This example shows how to use `useHead` and Nuxt built-in components to bind meta data to the head of the page.
::alert{type=info icon=👉}
Learn more about [meta tags](/docs/usage/meta-tags).
::
::sandbox{repo="nuxt/framework" branch="main" dir="examples/composables/use-meta" file="app.vue"}
::sandbox{repo="nuxt/framework" branch="main" dir="examples/composables/use-head" file="app.vue"}

View File

@ -8,7 +8,7 @@
| `use-async-data` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-async-data) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-async-data?file=app.vue&terminal=dev) |
| `use-cookie` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-cookie) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-cookie?file=app.vue&terminal=dev) |
| `use-fetch` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-fetch) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-fetch?file=app.vue&terminal=dev) |
| `use-meta` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-meta) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-meta?file=app.vue&terminal=dev) |
| `use-head` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-head) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-head?file=app.vue&terminal=dev) |
| `use-state` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/use-state) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/use-state?file=app.vue&terminal=dev) |
| `components` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/components) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/components?file=app.vue&terminal=dev) |
| `composables` | [GitHub](https://github.com/nuxt/framework/tree/main/examples/composables) | [Play Online](https://stackblitz.com/github/nuxt/framework/tree/main/examples/composables?file=app.vue&terminal=dev) |

View File

@ -1,5 +1,5 @@
<template>
<NuxtExampleLayout example="composables/use-meta">
<NuxtExampleLayout example="composables/use-head">
<div
class="bg-gray-400/10 border-2 border-dashed border-gray-400/50 rounded-xl py-8 px-2 op-80"
>
@ -26,7 +26,7 @@
<script>
export default {
setup () {
useMeta({
useHead({
bodyAttrs: {
class: 'test'
}

View File

@ -1,5 +1,5 @@
{
"name": "example-use-meta",
"name": "example-use-head",
"private": true,
"scripts": {
"build": "nuxi build",

View File

@ -1,6 +1,6 @@
import { resolve } from 'pathe'
import { addTemplate, useNuxt, installModule } from '@nuxt/kit'
import metaModule from '../../nuxt3/src/meta/module'
import metaModule from '../../nuxt3/src/head/module'
import { distDir } from './dirs'
const checkDocsMsg = 'Please see https://v3.nuxtjs.org for more information.'
@ -16,9 +16,9 @@ export const setupMeta = async (opts: SetupMetaOptions) => {
if (opts.needsExplicitEnable) {
const metaPath = addTemplate({
filename: 'meta.mjs',
getContents: () => `export const useMeta = () => console.warn('${msgPrefix} To use \`useMeta\`, please set \`bridge.meta\` to \`true\` in your \`nuxt.config\`. ${checkDocsMsg}')`
getContents: () => `export const useHead = () => console.warn('${msgPrefix} To use \`useHead\`, please set \`bridge.meta\` to \`true\` in your \`nuxt.config\`. ${checkDocsMsg}')`
})
nuxt.options.alias['#meta'] = metaPath.dst
nuxt.options.alias['#head'] = metaPath.dst
return
}
@ -26,8 +26,8 @@ export const setupMeta = async (opts: SetupMetaOptions) => {
throw new TypeError(`${msgPrefix} The head() function in \`nuxt.config\` has been deprecated and in Nuxt 3 will need to be moved to \`app.vue\`. ${checkDocsMsg}`)
}
const runtimeDir = resolve(distDir, 'runtime/meta')
nuxt.options.alias['#meta'] = runtimeDir
const runtimeDir = resolve(distDir, 'runtime/head')
nuxt.options.alias['#head'] = runtimeDir
await installModule(metaModule)
}

View File

@ -0,0 +1 @@
../../../nuxt3/src/head/runtime/

View File

@ -1 +0,0 @@
../../../nuxt3/src/meta/runtime

View File

@ -17,7 +17,7 @@
},
"imports": {
"#app": "./dist/app/index.mjs",
"#meta": "./dist/meta/runtime/index.mjs",
"#head": "./dist/head/runtime/index.mjs",
"#pages": "./dist/pages/runtime/index.mjs"
},
"files": [

View File

@ -7,8 +7,8 @@ export * from './components'
// eslint-disable-next-line import/no-restricted-paths
export type { PageMeta } from '../pages/runtime'
// eslint-disable-next-line import/no-restricted-paths
export type { MetaObject } from '../meta/runtime'
export { useMeta } from '#meta'
export type { MetaObject } from '../head/runtime'
export { useHead, useMeta } from '#head'
export const isVue2 = false
export const isVue3 = true

View File

@ -1,10 +1,11 @@
import { defineUnimportPreset, Preset } from 'unimport'
export const commonPresets: Preset[] = [
// #meta
// #head
defineUnimportPreset({
from: '#meta',
from: '#head',
imports: [
'useHead',
'useMeta'
]
}),

View File

@ -5,7 +5,7 @@ import { loadNuxtConfig, LoadNuxtOptions, nuxtCtx, installModule, addComponent,
// Temporary until finding better placement
/* eslint-disable import/no-restricted-paths */
import pagesModule from '../pages/module'
import metaModule from '../meta/module'
import metaModule from '../head/module'
import componentsModule from '../components/module'
import autoImportsModule from '../auto-imports/module'
/* eslint-enable */

View File

@ -13,16 +13,16 @@ export default defineNuxtModule({
viewport: 'width=device-width, initial-scale=1'
},
setup (options, nuxt) {
const runtimeDir = nuxt.options.alias['#meta'] || resolve(distDir, 'meta/runtime')
const runtimeDir = nuxt.options.alias['#head'] || resolve(distDir, 'head/runtime')
// Transpile @nuxt/meta and @vueuse/head
nuxt.options.build.transpile.push('@vueuse/head')
// Add #meta alias
nuxt.options.alias['#meta'] = runtimeDir
// Add #head alias
nuxt.options.alias['#head'] = runtimeDir
// Global meta
const globalMeta: MetaObject = defu(nuxt.options.meta, {
const globalMeta: MetaObject = defu(nuxt.options.app.head, {
meta: [
{ charset: options.charset },
{ name: 'viewport', content: options.viewport }

View File

@ -1,6 +1,6 @@
import { defineComponent } from 'vue'
import type { SetupContext } from 'vue'
import { useMeta } from './composables'
import { useHead } from './composables'
type Props = Readonly<Record<string, any>>
@ -8,7 +8,7 @@ const removeUndefinedProps = (props: Props) =>
Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined))
const setupForUseMeta = (metaFactory: (props: Props, ctx: SetupContext) => Record<string, any>, renderChild?: boolean) => (props: Props, ctx: SetupContext) => {
useMeta(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx))
useHead(() => metaFactory({ ...removeUndefinedProps(props), ...ctx.attrs }, ctx))
return () => renderChild ? ctx.slots.default?.() : null
}

View File

@ -11,7 +11,21 @@ import { useNuxtApp } from '#app'
* Alternatively, for reactive meta state, you can pass in a function
* that returns a meta object.
*/
export function useMeta (meta: MetaObject | ComputedGetter<MetaObject>) {
export function useHead (meta: MetaObject | ComputedGetter<MetaObject>) {
const resolvedMeta = isFunction(meta) ? computed(meta) : meta
useNuxtApp()._useMeta(resolvedMeta)
useNuxtApp()._useHead(resolvedMeta)
}
const _warned = {}
const warnOnce = (id: string, message: string) => {
if (!_warned[id]) {
console.warn(message)
_warned[id] = true
}
}
// TODO: remove useMeta support when Nuxt 3 is stable
/** @deprecated */
export function useMeta (meta: MetaObject | ComputedGetter<MetaObject>) {
warnOnce('useMeta', '[meta] useMeta has been renamed to useHead.')
return useHead(meta)
}

View File

@ -8,7 +8,7 @@ export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(manager)
nuxtApp._useMeta = (meta: MetaObject) => manager.addMeta(meta)
nuxtApp._useHead = (meta: MetaObject) => manager.addMeta(meta)
if (process.client) {
const teleportTarget = document.createElement('div')

View File

@ -14,7 +14,7 @@ export default defineNuxtPlugin((nuxtApp) => {
headReady = true
})
nuxtApp._useMeta = (meta: MetaObject) => {
nuxtApp._useHead = (meta: MetaObject) => {
const headObj = ref(meta as any)
head.addHeadObjs(headObj)

View File

@ -1,6 +1,6 @@
import { computed, getCurrentInstance } from 'vue'
import * as Components from './components'
import { useMeta } from './composables'
import { useHead } from './composables'
import { defineNuxtPlugin, useNuxtApp } from '#app'
// @ts-ignore
import metaConfig from '#build/meta.config.mjs'
@ -23,12 +23,12 @@ const metaMixin = {
? computed(() => options.head(nuxtApp))
: options.head
useMeta(source)
useHead(source)
}
}
export default defineNuxtPlugin((nuxtApp) => {
useMeta(metaConfig.globalMeta)
useHead(metaConfig.globalMeta)
nuxtApp.vueApp.mixin(metaMixin)

View File

@ -66,9 +66,48 @@ export default {
*/
cdnURL: {
$resolve: (val, get) => get('dev') ? null : val || null
}
},
/**
* Set default configuration for `<head>` on every page.
*
* @example
* ```js
* app: {
* head: {
* meta: [
* // <meta name="viewport" content="width=device-width, initial-scale=1">
* { name: 'viewport', content: 'width=device-width, initial-scale=1' }
* ],
* script: [
* // <script src="https://myawesome-lib.js"></script>
* { src: 'https://awesome-lib.js' }
* ],
* link: [
* // <link rel="stylesheet" href="https://myawesome-lib.css">
* { rel: 'stylesheet', href: 'https://awesome-lib.css' }
* ],
* // please note that this is an area that is likely to change
* style: [
* // <style type="text/css">:root { color: red }</style>
* { children: ':root { color: red }', type: 'text/css' }
* ]
* }
* }
* ```
* @type {typeof import('../src/types/meta').MetaObject}
* @version 3
*/
head: {
$resolve: (val, get) => {
return defu(val, get('meta'), {
meta: [],
link: [],
style: [],
script: []
})
}
},
},
/**
* The path to a templated HTML file for rendering Nuxt responses.
* Uses `<srcDir>/app.html` if it exists or the Nuxt default template if not.
@ -140,32 +179,9 @@ export default {
},
/**
* Set default configuration for `<head>` on every page.
*
* @example
* ```js
* meta: {
* meta: [
* // <meta name="viewport" content="width=device-width, initial-scale=1">
* { name: 'viewport', content: 'width=device-width, initial-scale=1' }
* ],
* script: [
* // <script src="https://myawesome-lib.js"></script>
* { src: 'https://awesome-lib.js' }
* ],
* link: [
* // <link rel="stylesheet" href="https://myawesome-lib.css">
* { rel: 'stylesheet', href: 'https://awesome-lib.css' }
* ],
* // please note that this is an area that is likely to change
* style: [
* // <style type="text/css">:root { color: red }</style>
* { children: ':root { color: red }', type: 'text/css' }
* ]
* }
* ```
* @type {typeof import('../src/types/meta').MetaObject}
* @version 3
* @deprecated - use `head` instead
*/
meta: {
meta: [],

View File

@ -744,6 +744,15 @@ export default {
* @version 3
*/
publicRuntimeConfig: {
$resolve: (val: Record<string, any> = {}, get) => ({ ...val, app: defu(val.app, get('app')) })
$resolve: (val: Record<string, any> = {}, get) => ({
...val,
app: {
baseURL: get('app.baseURL'),
buildAssetsDir: get('app.buildAssetsDir'),
assetsPath: get('app.assetsPath'),
cdnURL: get('app.cdnURL'),
...val.app || {},
}
})
}
}

View File

@ -101,6 +101,18 @@ describe('pages', () => {
})
})
describe('head tags', () => {
it('should render tags', async () => {
const html = await $fetch('/head')
expect(html).toContain('<title>Using a dynamic component</title>')
expect(html).not.toContain('<meta name="description" content="first">')
expect(html).toContain('<meta name="description" content="overriding with an inline useHead call">')
expect(html).toMatch(/<html[^>]*class="html-attrs-test"/)
expect(html).toMatch(/<body[^>]*class="body-attrs-test"/)
expect(html).toContain('script>console.log("works with useMeta too")</script>')
})
})
describe('navigate', () => {
it('should redirect to index with navigateTo', async () => {
const html = await $fetch('/navigate-to/')

30
test/fixtures/basic/pages/head.vue vendored Normal file
View File

@ -0,0 +1,30 @@
<script setup>
useHead({
bodyAttrs: {
class: 'body-attrs-test'
},
meta: [{ name: 'description', content: 'first' }]
})
useHead({ meta: [{ name: 'description', content: 'overriding with an inline useHead call' }] })
useMeta({ script: [{ children: 'console.log("works with useMeta too")' }] })
</script>
<script>
export default {
head () {
return {
htmlAttrs: {
class: 'html-attrs-test'
}
}
}
}
</script>
<template>
<div>
<Head>
<Title>Using a dynamic component</Title>
</Head>
</div>
</template>

View File

@ -10895,9 +10895,9 @@ __metadata:
languageName: unknown
linkType: soft
"example-use-meta@workspace:examples/composables/use-meta":
"example-use-head@workspace:examples/composables/use-head":
version: 0.0.0-use.local
resolution: "example-use-meta@workspace:examples/composables/use-meta"
resolution: "example-use-head@workspace:examples/composables/use-head"
dependencies:
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
nuxt3: latest