|
| 1 | +import { defineComponent } from 'vue' |
| 2 | +import type { VNode } from 'vue' |
| 3 | +import { Vue2 } from './env' |
| 4 | +import type { ReactiveHead, UseHeadInput } from './types' |
| 5 | +import { useHead } from './composables' |
| 6 | + |
| 7 | +export type Props = Readonly<Record<string, any>> |
| 8 | + |
| 9 | +const addVNodeToHeadObj = (node: VNode, obj: ReactiveHead) => { |
| 10 | + // @ts-expect-error vue2 vnode API |
| 11 | + const nodeType = Vue2 ? node.tag : node.type |
| 12 | + const type |
| 13 | + = nodeType === 'html' |
| 14 | + ? 'htmlAttrs' |
| 15 | + : nodeType === 'body' |
| 16 | + ? 'bodyAttrs' |
| 17 | + : (nodeType as keyof ReactiveHead) |
| 18 | + |
| 19 | + if (typeof type !== 'string' || !(type in obj)) |
| 20 | + return |
| 21 | + |
| 22 | + // @ts-expect-error untyped |
| 23 | + const nodeData = Vue2 ? node.data : node |
| 24 | + const props: Record<string, any> = (Vue2 ? nodeData.attrs : node.props) || {} |
| 25 | + // handle class and style attrs |
| 26 | + if (Vue2) { |
| 27 | + if (nodeData.staticClass) |
| 28 | + props.class = nodeData.staticClass |
| 29 | + if (nodeData.staticStyle) |
| 30 | + props.style = Object.entries(nodeData.staticStyle).map(([key, value]) => `${key}:${value}`).join(';') |
| 31 | + } |
| 32 | + if (node.children) { |
| 33 | + const childrenAttr = Vue2 ? 'text' : 'children' |
| 34 | + props.children = Array.isArray(node.children) |
| 35 | + // @ts-expect-error untyped |
| 36 | + ? node.children[0]![childrenAttr] |
| 37 | + // @ts-expect-error vue2 vnode API |
| 38 | + : node[childrenAttr] |
| 39 | + } |
| 40 | + if (Array.isArray(obj[type])) { |
| 41 | + // @ts-expect-error untyped |
| 42 | + obj[type].push(props) |
| 43 | + } |
| 44 | + else if (type === 'title') { obj.title = props.children } |
| 45 | + else { |
| 46 | + // @ts-expect-error untyped |
| 47 | + obj[type] = props |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +const vnodesToHeadObj = (nodes: VNode[]) => { |
| 52 | + const obj: UseHeadInput = { |
| 53 | + title: undefined, |
| 54 | + htmlAttrs: undefined, |
| 55 | + bodyAttrs: undefined, |
| 56 | + base: undefined, |
| 57 | + meta: [], |
| 58 | + link: [], |
| 59 | + style: [], |
| 60 | + script: [], |
| 61 | + noscript: [], |
| 62 | + } |
| 63 | + for (const node of nodes) { |
| 64 | + if (typeof node.type === 'symbol' && Array.isArray(node.children)) { |
| 65 | + for (const childNode of node.children) |
| 66 | + addVNodeToHeadObj(childNode as VNode, obj) |
| 67 | + } |
| 68 | + else { |
| 69 | + addVNodeToHeadObj(node, obj) |
| 70 | + } |
| 71 | + } |
| 72 | + return obj |
| 73 | +} |
| 74 | + |
| 75 | +export const Head = /* @__PURE__ */ defineComponent({ |
| 76 | + // eslint-disable-next-line vue/no-reserved-component-names |
| 77 | + name: 'Head', |
| 78 | + |
| 79 | + setup(_, { slots }) { |
| 80 | + return () => { |
| 81 | + // @ts-expect-error untyped |
| 82 | + useHead(() => vnodesToHeadObj(slots.default())) |
| 83 | + return null |
| 84 | + } |
| 85 | + }, |
| 86 | +}) |
0 commit comments