mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-17 16:00:34 +03:00
feat: add support for attributes (wip)
support slots for head/body make config fully external (while providing defaults users can use) type fixes
This commit is contained in:
+44
-15
@@ -9,7 +9,7 @@ import {
|
||||
watch
|
||||
} from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createManager, useMeta, useMetainfo } from 'vue-meta'
|
||||
import { defaultConfig, createManager, useMeta, useMetainfo } from 'vue-meta'
|
||||
// import About from './about.vue'
|
||||
|
||||
const metaUpdated = 'no'
|
||||
@@ -76,22 +76,18 @@ const App = {
|
||||
title: 'Twitter Title'
|
||||
},
|
||||
noscript: [
|
||||
//'<!-- // A code comment -->',
|
||||
{ tag: 'link', rel: 'stylesheet', href: 'style.css' }
|
||||
],
|
||||
otherNoscript: {
|
||||
tag: 'noscript',
|
||||
'data-test': 'hello',
|
||||
children: [
|
||||
//'<!-- // Another code comment -->',
|
||||
{ tag: 'link', rel: 'stylesheet', href: 'style2.css' }
|
||||
]
|
||||
},
|
||||
body: 'body-script1.js',
|
||||
script: [
|
||||
//'<!--[if IE]>',
|
||||
{ src: 'head-script1.js' },
|
||||
//'<![endif]-->',
|
||||
{ src: 'body-script2.js', to: 'body' },
|
||||
{ src: 'body-script3.js', to: '#put-it-here' }
|
||||
],
|
||||
@@ -120,23 +116,58 @@ const App = {
|
||||
|
||||
const metadata = useMetainfo()
|
||||
|
||||
window.$metainfo = metadata
|
||||
window.$metadata = metadata
|
||||
|
||||
watch(metadata, (newValue, oldValue) => {
|
||||
console.log('UPDATE', newValue)
|
||||
})
|
||||
|
||||
/* let i = 0
|
||||
const walk = (data, path = []) => {
|
||||
for (const key in data) {
|
||||
const newPath = [...path, key]
|
||||
if (typeof data[key] === 'object') {
|
||||
walk(data[key], newPath)
|
||||
} else {
|
||||
console.log(newPath.join('.'))
|
||||
}
|
||||
i++
|
||||
if (i > 50) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => walk(metadata), 1000) */
|
||||
|
||||
return {
|
||||
metadata
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<metainfo :metainfo="metadata">
|
||||
<metainfo :metainfo="metadata" :body-class="'theme-dark'" :html-lang="['en','nl']" html-amp>
|
||||
<template v-slot:base="{ content, metainfo }">http://nuxt.dev:3000{{ content }}</template>
|
||||
<template v-slot:title="{ content, metainfo }">{{ content }} - {{ metainfo.description }} - Hello</template>
|
||||
<template v-slot:og(title)="{ content, metainfo, og }">
|
||||
{{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again
|
||||
</template>
|
||||
|
||||
<script src="lalala1.js"></script>
|
||||
<script src="lalala2.js"></script>
|
||||
|
||||
<template v-slot:head="{ metainfo }">
|
||||
<!--[if IE]>
|
||||
// -> Reactivity is not supported by Vue, all comments are ignored
|
||||
<script :src="metainfo.script[0].src" ></script>
|
||||
// -> but a static file should work
|
||||
<script src="lalala3.js" ></script>
|
||||
// -> altho Vue probably strips comments in production builds (but can be configged afaik)
|
||||
<![endif]-->
|
||||
<script :src="metainfo.script[0].src" ></script>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<script src="lalala4.js"></script>
|
||||
</template>
|
||||
</metainfo>
|
||||
|
||||
<div id="app">
|
||||
@@ -172,15 +203,13 @@ function decisionMaker5000000 (key, pathSegments, getOptions, getCurrentValue) {
|
||||
}
|
||||
|
||||
const metaManager = createManager({
|
||||
resolver: decisionMaker5000000,
|
||||
config: {
|
||||
esi: {
|
||||
group: true,
|
||||
namespaced: true,
|
||||
attributes: ['src', 'test', 'text']
|
||||
}
|
||||
...defaultConfig,
|
||||
esi: {
|
||||
group: true,
|
||||
namespaced: true,
|
||||
attributes: ['src', 'test', 'text']
|
||||
}
|
||||
})
|
||||
}, decisionMaker5000000)
|
||||
|
||||
/* useMeta(
|
||||
{
|
||||
|
||||
+48
-8
@@ -1,5 +1,5 @@
|
||||
import { h, defineComponent, Teleport, PropType, VNode, VNodeProps } from 'vue'
|
||||
import { isArray } from '@vue/shared'
|
||||
import { h, watchEffect, defineComponent, Teleport, PropType, VNode, VNodeProps } from 'vue'
|
||||
import { isArray, isFunction } from '@vue/shared'
|
||||
import { renderMeta } from './render'
|
||||
import { getCurrentManager } from './useApi'
|
||||
import { MetainfoActive } from './types'
|
||||
@@ -8,23 +8,59 @@ export interface MetainfoProps {
|
||||
metainfo: MetainfoActive
|
||||
}
|
||||
|
||||
export function addVnode (teleports: any, to: string, vnode: VNode) {
|
||||
export function addVnode (teleports: any, to: string, vnode: VNode | Array<VNode>) {
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = []
|
||||
}
|
||||
|
||||
if (isArray(vnode)) {
|
||||
teleports[to].push(...vnode)
|
||||
return
|
||||
}
|
||||
|
||||
teleports[to].push(vnode)
|
||||
}
|
||||
|
||||
export const MetainfoImpl = defineComponent({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
metainfo: {
|
||||
type: Object as PropType<MetainfoActive>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup ({ metainfo }, { slots }) {
|
||||
setup ({ metainfo }, { attrs, slots }) {
|
||||
const tags: { [key: string]: Element } = {}
|
||||
|
||||
watchEffect(() => {
|
||||
const attributes = Object.keys(attrs)
|
||||
|
||||
for (const tagName of ['html', 'head', 'body']) {
|
||||
const tagAttrs = attributes.filter(attr => attr.startsWith(tagName + '-'))
|
||||
|
||||
if (!tagAttrs.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!tags[tagName]) {
|
||||
const foundTag = document.querySelector(tagName)
|
||||
|
||||
if (foundTag) {
|
||||
tags[tagName] = foundTag
|
||||
}
|
||||
}
|
||||
|
||||
const tag: Element = tags[tagName]
|
||||
|
||||
for (const tagAttr of tagAttrs) {
|
||||
const attr: string = tagAttr.slice(5)
|
||||
|
||||
tag.setAttribute(attr, `${attrs[tagAttr] || ''}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
const teleports: any = {}
|
||||
|
||||
@@ -39,24 +75,28 @@ export const MetainfoImpl = defineComponent({
|
||||
metainfo[key],
|
||||
config
|
||||
)
|
||||
console.log('RENDERED VNODES', vnodes)
|
||||
|
||||
const defaultTo =
|
||||
(key !== 'base' && metainfo[key].to) || config.to || 'head'
|
||||
|
||||
if (isArray(vnodes)) {
|
||||
for (const { to, vnode } of vnodes) {
|
||||
console.log('VNODE 1', vnode)
|
||||
addVnode(teleports, to || defaultTo, vnode)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const { to, vnode } = vnodes
|
||||
console.log('VNODE 2', vnode)
|
||||
addVnode(teleports, to || defaultTo, vnode)
|
||||
}
|
||||
|
||||
console.log('TARGETS', teleports)
|
||||
for (const tag of ['default', 'head', 'body']) {
|
||||
const slotFn = slots[tag]
|
||||
if (isFunction(slotFn)) {
|
||||
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo }))
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to])
|
||||
})
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { isArray } from '@vue/shared'
|
||||
import { tags } from './config/tags'
|
||||
import { TODO } from './types'
|
||||
|
||||
export interface ConfigOption {
|
||||
tag?: string
|
||||
to?: string
|
||||
group?: boolean
|
||||
keyAttribute?: string
|
||||
valueAttribute?: string
|
||||
nameless?: boolean
|
||||
namespaced?: boolean
|
||||
namespacedAttribute?: boolean
|
||||
}
|
||||
|
||||
const defaultMapping: { [key: string]: ConfigOption } = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
}
|
||||
}
|
||||
|
||||
export { defaultMapping }
|
||||
|
||||
export function hasConfig (name: string): boolean {
|
||||
return !!tags[name] || !!defaultMapping[name]
|
||||
}
|
||||
|
||||
export function getConfigKey (
|
||||
tagOrName: string | Array<string>,
|
||||
key: string,
|
||||
config: TODO
|
||||
): any {
|
||||
if (config && key in config) {
|
||||
return config[key]
|
||||
}
|
||||
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName]
|
||||
return tag[key]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
export interface ConfigOption {
|
||||
tag?: string
|
||||
to?: string
|
||||
group?: boolean
|
||||
keyAttribute?: string
|
||||
valueAttribute?: string
|
||||
nameless?: boolean
|
||||
namespaced?: boolean
|
||||
namespacedAttribute?: boolean
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
[key: string]: ConfigOption
|
||||
}
|
||||
|
||||
const defaultConfig: Config = {
|
||||
body: {
|
||||
tag: 'script',
|
||||
to: 'body'
|
||||
},
|
||||
base: {
|
||||
valueAttribute: 'href'
|
||||
},
|
||||
charset: {
|
||||
tag: 'meta',
|
||||
nameless: true,
|
||||
valueAttribute: 'charset'
|
||||
},
|
||||
description: {
|
||||
tag: 'meta'
|
||||
},
|
||||
og: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta',
|
||||
keyAttribute: 'property'
|
||||
},
|
||||
twitter: {
|
||||
group: true,
|
||||
namespacedAttribute: true,
|
||||
tag: 'meta'
|
||||
}
|
||||
}
|
||||
|
||||
export { defaultConfig }
|
||||
@@ -0,0 +1,32 @@
|
||||
import { isArray } from '@vue/shared'
|
||||
import { Config } from './default'
|
||||
import { tags } from './tags'
|
||||
|
||||
export function hasConfig (name: string, config: Config): boolean {
|
||||
return !!config[name] || !!tags[name]
|
||||
}
|
||||
|
||||
export function getConfigKey (
|
||||
tagOrName: string | Array<string>,
|
||||
key: string,
|
||||
config: Config
|
||||
): any {
|
||||
if (config && key in config) {
|
||||
return config[key]
|
||||
}
|
||||
|
||||
if (isArray(tagOrName)) {
|
||||
for (const name of tagOrName) {
|
||||
if (name && name in tags) {
|
||||
return tags[name][key]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (tagOrName in tags) {
|
||||
const tag = tags[tagOrName]
|
||||
return tag[key]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './default'
|
||||
export * from './helpers'
|
||||
export * from './tags'
|
||||
@@ -1,2 +1,3 @@
|
||||
export { defaultConfig } from './config'
|
||||
export { createManager } from './manager'
|
||||
export * from './useApi'
|
||||
|
||||
+7
-17
@@ -1,30 +1,23 @@
|
||||
import { App } from 'vue'
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { defaultMapping } from './config'
|
||||
import { Config } from './config'
|
||||
import { applyMetaPlugin } from './install'
|
||||
import * as deepestResolver from './resolvers/deepest'
|
||||
import { TODO, ActiveResolverObject, MetaContext, PathSegments } from './types'
|
||||
|
||||
export type ManagerOptions = {
|
||||
install(app: App): void
|
||||
}
|
||||
import { ManagerResolverObject, ActiveResolverObject, MetaContext, PathSegments } from './types'
|
||||
|
||||
export type Manager = {
|
||||
readonly config: TODO
|
||||
readonly config: Config
|
||||
|
||||
resolver: ActiveResolverObject
|
||||
resolver: ManagerResolverObject
|
||||
install(app: App): void
|
||||
}
|
||||
|
||||
export function createManager (options: TODO = {}): Manager {
|
||||
const { resolver = deepestResolver } = options
|
||||
|
||||
export function createManager (config: Config, resolver: ActiveResolverObject = deepestResolver): Manager {
|
||||
// TODO: validate resolver
|
||||
|
||||
const manager: Manager = {
|
||||
resolver: {
|
||||
setup (context: MetaContext) {
|
||||
if (!resolver || isFunction(resolver)) {
|
||||
if (!resolver || !resolver.setup || isFunction(resolver)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,10 +35,7 @@ export function createManager (options: TODO = {}): Manager {
|
||||
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
||||
}
|
||||
},
|
||||
config: {
|
||||
...defaultMapping,
|
||||
...options.config
|
||||
},
|
||||
config,
|
||||
install (app: App) {
|
||||
applyMetaPlugin(app, this)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ export interface ActiveResolverObject {
|
||||
resolve: ActiveResolverMethod
|
||||
}
|
||||
|
||||
export interface ManagerResolverObject {
|
||||
setup: ActiveResolverSetup
|
||||
resolve: ActiveResolverMethod
|
||||
}
|
||||
|
||||
export interface ShadowNode {
|
||||
[key: string]: TODO
|
||||
}
|
||||
|
||||
+1
-4
@@ -10,7 +10,6 @@ let contextCounter: number = 0
|
||||
export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
||||
const vm = getCurrentInstance()
|
||||
if (vm) {
|
||||
console.log(vm)
|
||||
manager = vm.appContext.config.globalProperties.$metaManager
|
||||
}
|
||||
|
||||
@@ -30,9 +29,7 @@ export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
||||
onUnmounted(unmount)
|
||||
}
|
||||
|
||||
if (manager.resolver.setup) {
|
||||
manager.resolver.setup(context)
|
||||
}
|
||||
manager.resolver.setup(context)
|
||||
|
||||
setByObject(context, obj)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user