-
Notifications
You must be signed in to change notification settings - Fork 100
/
meta.ts
140 lines (115 loc) · 3.35 KB
/
meta.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import defu from 'defu'
import {
computed,
reactive,
ref,
toRefs,
watch,
Ref,
isReactive,
isRef,
toRaw,
UnwrapRef,
} from 'vue'
import type { MetaInfo } from 'vue-meta'
import { getCurrentInstance } from './utils'
export type ReactiveHead<T = Record<string, unknown>> = UnwrapRef<
Ref<MetaInfo & T>
>
type MetaInfoMapper<T> = {
[P in keyof T]: P extends 'base'
? T[P] | undefined
: T[P] extends () => any
? T[P] | undefined
: T[P] extends Array<any> | Record<string, unknown>
? T[P]
: T[P] | undefined
}
export function createEmptyMeta(): MetaInfoMapper<Required<MetaInfo>> {
return {
titleTemplate: null as unknown as undefined,
__dangerouslyDisableSanitizers: [],
__dangerouslyDisableSanitizersByTagID: {},
title: undefined,
htmlAttrs: {},
headAttrs: {},
bodyAttrs: {},
base: undefined,
meta: [],
link: [],
style: [],
script: [],
noscript: [],
changed: undefined,
afterNavigation: undefined,
}
}
type ComputedHead = Array<MetaInfo | Ref<MetaInfo>>
export const getHeadOptions = (options: { head: () => MetaInfo }) => {
const head = function (this: {
head?: MetaInfo | (() => MetaInfo)
_computedHead?: ComputedHead
}) {
const optionHead =
options.head instanceof Function ? options.head.call(this) : options.head
if (!this._computedHead) return optionHead
const computedHead = this._computedHead.map(h => {
if (isReactive(h)) return toRaw(h)
if (isRef(h)) return h.value
return h
})
return defu({} as MetaInfo, ...computedHead.reverse(), optionHead)
}
return { head }
}
type ToRefs<T extends Record<string, any>> = {
[P in keyof T]: Ref<T[P]>
}
/**
* `useMeta` lets you interact directly with [`head()` properties](https://nuxtjs.org/api/pages-head/) in `setup`. **Make sure you set `head: {}` in your component options.**
* @example
```ts
import { defineComponent, useMeta, computed } from '@nuxtjs/composition-api'
export default defineComponent({
head: {},
setup() {
const { title } = useMeta()
title.value = 'My page'
})
})
```
* @param init Whatever defaults you want to set for `head` properties.
*/
export const useMeta = <
T extends MetaInfo,
MetaRefs extends ToRefs<ReturnType<typeof createEmptyMeta> & T>
>(
init?: T | (() => T)
) => {
const vm = getCurrentInstance() as ReturnType<typeof getCurrentInstance> & {
_computedHead?: ComputedHead
_metaRefs?: MetaRefs
}
if (!vm) throw new Error('useMeta must be called within a component.')
if (!('head' in vm.$options))
throw new Error(
'In order to enable `useMeta`, please make sure you include `head: {}` within your component definition, and you are using the `defineComponent` exported from @nuxtjs/composition-api.'
)
const refreshMeta = () => vm.$meta().refresh()
if (!vm._computedHead) {
const metaRefs = reactive(createEmptyMeta())
vm._computedHead = [metaRefs]
vm._metaRefs = toRefs(metaRefs) as MetaRefs
if (process.client) {
watch(Object.values(vm._metaRefs), refreshMeta, { immediate: true })
}
}
if (init) {
const initRef = init instanceof Function ? computed(init) : ref(init)
vm._computedHead.push(initRef)
if (process.client) {
watch(initRef, refreshMeta, { immediate: true })
}
}
return vm._metaRefs!
}