Skip to content

Commit

Permalink
feat(bridge): add useNuxt2Meta() composable (nuxt#1789)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Nov 9, 2021
1 parent 95cbe67 commit 29599f0
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 13 deletions.
63 changes: 51 additions & 12 deletions docs/content/1.getting-started/4.bridge-composition-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,58 @@ You can continue to use `useAsync` and `useFetch` by importing them from `@nuxtj

### `useMeta`

In order to use vue-meta, you can continue importing `useMeta` and `defineComponent` from `@nuxtjs/composition-api`, which is shimmed by Nuxt Bridge.
In order to interact with `vue-meta`, you may use `useNuxt2Meta`, which will work in Nuxt Bridge (but not Nuxt 3) and will allow you to manipulate your meta tags in a `vue-meta`-compatible way.

However, note that the existing limitations of `useMeta` continue, so it cannot be used in `<script setup>` and you must include a `head: {}` object within your `defineComponent`.
```diff
<script setup>
- import { useMeta } from '@nuxtjs/composition-api'
+ import { useNuxt2Meta } from '#app'
useNuxt2Meta({
title: 'My Nuxt App',
})
</script>
```

You can also pass in computed values or refs, and the meta values will be updated reactively:

```ts
<script setup>
import { useNuxt2Meta } from '#app'
const title = ref('my title')
useNuxt2Meta({
title,
})
title.value = 'new title'
</script>
```

::alert
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.

```diff
<script setup>
- import { useMeta } from '@nuxtjs/composition-api'
+ import { useMeta } from '#app'
useMeta({
title: 'My Nuxt App',
})
</script>
```

You will also need to enable it explicitly in your `nuxt.config`:

```js
import { defineComponent, useMeta } from '@nuxtjs/composition-api'

export default defineComponent({
// You need to define an empty head to activate this functionality
head: {},
setup() {
// This will allow you to set the title in head - but won't allow you to read its state outside of this component.
const { title } = useMeta()
title.value = 'My page'
},
import { defineNuxtConfig } from '@nuxt/bridge'
export default defineNuxtConfig({
bridge: {
meta: true
}
})
```

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.

For more information on how to use this composable, see [the Nuxt 3 docs](/docs/usage/meta-tags#usemeta-composable).
3 changes: 3 additions & 0 deletions packages/bridge/src/auto-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export async function setupAutoImports () {
autoImport.disabled = true
}
}

// Add bridge-only auto-imports
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })
})

await installModule(nuxt, autoImports)
Expand Down
56 changes: 55 additions & 1 deletion packages/bridge/src/runtime/composables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { reactive, toRef, isReactive, Ref } from '@vue/composition-api'
import { getCurrentInstance, onBeforeUnmount, isRef, watch, reactive, toRef, isReactive, Ref } from '@vue/composition-api'
import type { CombinedVueInstance } from 'vue/types/vue'
import type { MetaInfo } from 'vue-meta'
import type VueRouter from 'vue-router'
import type { Route } from 'vue-router'
import { useNuxtApp } from './app'
Expand Down Expand Up @@ -55,3 +57,55 @@ export const useState = <T>(key: string, init?: (() => T)): Ref<T> => {
}
return state
}

type Reffed<T extends Record<string, any>> = {
[P in keyof T]: T[P] extends Array<infer A> ? Ref<Array<Reffed<A>>> | Array<Reffed<A>> : T[P] extends Record<string, any> ? Reffed<T[P]> | Ref<Reffed<T[P]>> : T[P] | Ref<T[P]>
}

function unwrap (value: any): Record<string, any> {
if (!value || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number') { return value }
if (Array.isArray(value)) { return value.map(i => unwrap(i)) }
if (isRef(value)) { return unwrap(value.value) }
if (typeof value === 'object') {
return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, unwrap(value)]))
}
return value
}

type AugmentedComponent = CombinedVueInstance<Vue, object, object, object, Record<never, any>> & {
_vueMeta?: boolean
$metaInfo?: MetaInfo
}

export const useNuxt2Meta = (metaOptions: Reffed<MetaInfo> | (() => Reffed<MetaInfo>)) => {
const vm = getCurrentInstance()!.proxy as AugmentedComponent
const meta = vm.$meta()
const $root = vm.$root

if (!vm._vueMeta) {
vm._vueMeta = true

let parent = vm.$parent as AugmentedComponent
while (parent && parent !== $root) {
if (parent._vueMeta === undefined) {
parent._vueMeta = false
}
parent = parent.$parent
}
}
// @ts-ignore
vm.$options.head = vm.$options.head || {}

const metaSource = metaOptions instanceof Function ? metaOptions : () => metaOptions
const unwatch = watch(metaSource, (metaInfo: MetaInfo) => {
vm.$metaInfo = {
...vm.$metaInfo || {},
...unwrap(metaInfo)
}
if (process.client) {
meta.refresh()
}
}, { immediate: true, deep: true })

onBeforeUnmount(unwatch)
}

0 comments on commit 29599f0

Please sign in to comment.