mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 10:40:35 +03:00
feat: first work on vue v3 composition metainfo app
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
__build__
|
||||||
|
dist
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
<!-- li><a href="basic">Basic</a></li>
|
<!-- li><a href="basic">Basic</a></li>
|
||||||
<li><a href="basic-render">Basic Render</a></li>
|
<li><a href="basic-render">Basic Render</a></li>
|
||||||
<li><a href="keep-alive">Keep alive</a></li>
|
<li><a href="keep-alive">Keep alive</a></li>
|
||||||
<li><a href="multiple-apps">Usage with multiple apps</a></li -->
|
<li><a href="multiple-apps">Usage with multiple apps</a></li>
|
||||||
<li><a href="ssr">SSR</a></li>
|
<li><a href="ssr">SSR</a></li -->
|
||||||
<li><a href="vue-router">Usage with vue-router</a></li>
|
<li><a href="vue-router">Usage with vue-router</a></li>
|
||||||
<!-- li><a href="vuex">Usage with vuex</a></li>
|
<!-- li><a href="vuex">Usage with vuex</a></li>
|
||||||
<li><a href="vuex-async">Usage with vuex + async actions</a></li>
|
<li><a href="vuex-async">Usage with vuex + async actions</a></li>
|
||||||
|
|||||||
+18
-18
@@ -1,23 +1,23 @@
|
|||||||
window.users.push({
|
window.users.push({
|
||||||
'id': 1,
|
id: 1,
|
||||||
'name': 'Leanne Graham',
|
name: 'Leanne Graham',
|
||||||
'username': 'Bret',
|
username: 'Bret',
|
||||||
'email': 'Sincere@april.biz',
|
email: 'Sincere@april.biz',
|
||||||
'address': {
|
address: {
|
||||||
'street': 'Kulas Light',
|
street: 'Kulas Light',
|
||||||
'suite': 'Apt. 556',
|
suite: 'Apt. 556',
|
||||||
'city': 'Gwenborough',
|
city: 'Gwenborough',
|
||||||
'zipcode': '92998-3874',
|
zipcode: '92998-3874',
|
||||||
'geo': {
|
geo: {
|
||||||
'lat': '-37.3159',
|
lat: '-37.3159',
|
||||||
'lng': '81.1496'
|
lng: '81.1496'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'phone': '1-770-736-8031 x56442',
|
phone: '1-770-736-8031 x56442',
|
||||||
'website': 'hildegard.org',
|
website: 'hildegard.org',
|
||||||
'company': {
|
company: {
|
||||||
'name': 'Romaguera-Crona',
|
name: 'Romaguera-Crona',
|
||||||
'catchPhrase': 'Multi-layered client-server neural-net',
|
catchPhrase: 'Multi-layered client-server neural-net',
|
||||||
'bs': 'harness real-time e-markets'
|
bs: 'harness real-time e-markets'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+18
-18
@@ -1,23 +1,23 @@
|
|||||||
window.users.push({
|
window.users.push({
|
||||||
'id': 2,
|
id: 2,
|
||||||
'name': 'Ervin Howell',
|
name: 'Ervin Howell',
|
||||||
'username': 'Antonette',
|
username: 'Antonette',
|
||||||
'email': 'Shanna@melissa.tv',
|
email: 'Shanna@melissa.tv',
|
||||||
'address': {
|
address: {
|
||||||
'street': 'Victor Plains',
|
street: 'Victor Plains',
|
||||||
'suite': 'Suite 879',
|
suite: 'Suite 879',
|
||||||
'city': 'Wisokyburgh',
|
city: 'Wisokyburgh',
|
||||||
'zipcode': '90566-7771',
|
zipcode: '90566-7771',
|
||||||
'geo': {
|
geo: {
|
||||||
'lat': '-43.9509',
|
lat: '-43.9509',
|
||||||
'lng': '-34.4618'
|
lng: '-34.4618'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'phone': '010-692-6593 x09125',
|
phone: '010-692-6593 x09125',
|
||||||
'website': 'anastasia.net',
|
website: 'anastasia.net',
|
||||||
'company': {
|
company: {
|
||||||
'name': 'Deckow-Crist',
|
name: 'Deckow-Crist',
|
||||||
'catchPhrase': 'Proactive didactic contingency',
|
catchPhrase: 'Proactive didactic contingency',
|
||||||
'bs': 'synergize scalable supply-chains'
|
bs: 'synergize scalable supply-chains'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+18
-18
@@ -1,23 +1,23 @@
|
|||||||
window.users.push({
|
window.users.push({
|
||||||
'id': 3,
|
id: 3,
|
||||||
'name': 'Clementine Bauch',
|
name: 'Clementine Bauch',
|
||||||
'username': 'Samantha',
|
username: 'Samantha',
|
||||||
'email': 'Nathan@yesenia.net',
|
email: 'Nathan@yesenia.net',
|
||||||
'address': {
|
address: {
|
||||||
'street': 'Douglas Extension',
|
street: 'Douglas Extension',
|
||||||
'suite': 'Suite 847',
|
suite: 'Suite 847',
|
||||||
'city': 'McKenziehaven',
|
city: 'McKenziehaven',
|
||||||
'zipcode': '59590-4157',
|
zipcode: '59590-4157',
|
||||||
'geo': {
|
geo: {
|
||||||
'lat': '-68.6102',
|
lat: '-68.6102',
|
||||||
'lng': '-47.0653'
|
lng: '-47.0653'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'phone': '1-463-123-4447',
|
phone: '1-463-123-4447',
|
||||||
'website': 'ramiro.info',
|
website: 'ramiro.info',
|
||||||
'company': {
|
company: {
|
||||||
'name': 'Romaguera-Jacobson',
|
name: 'Romaguera-Jacobson',
|
||||||
'catchPhrase': 'Face to face bifurcated interface',
|
catchPhrase: 'Face to face bifurcated interface',
|
||||||
'bs': 'e-enable strategic applications'
|
bs: 'e-enable strategic applications'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+18
-18
@@ -1,23 +1,23 @@
|
|||||||
window.users.push({
|
window.users.push({
|
||||||
'id': 4,
|
id: 4,
|
||||||
'name': 'Patricia Lebsack',
|
name: 'Patricia Lebsack',
|
||||||
'username': 'Karianne',
|
username: 'Karianne',
|
||||||
'email': 'Julianne.OConner@kory.org',
|
email: 'Julianne.OConner@kory.org',
|
||||||
'address': {
|
address: {
|
||||||
'street': 'Hoeger Mall',
|
street: 'Hoeger Mall',
|
||||||
'suite': 'Apt. 692',
|
suite: 'Apt. 692',
|
||||||
'city': 'South Elvis',
|
city: 'South Elvis',
|
||||||
'zipcode': '53919-4257',
|
zipcode: '53919-4257',
|
||||||
'geo': {
|
geo: {
|
||||||
'lat': '29.4572',
|
lat: '29.4572',
|
||||||
'lng': '-164.2990'
|
lng: '-164.2990'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'phone': '493-170-9623 x156',
|
phone: '493-170-9623 x156',
|
||||||
'website': 'kale.biz',
|
website: 'kale.biz',
|
||||||
'company': {
|
company: {
|
||||||
'name': 'Robel-Corkery',
|
name: 'Robel-Corkery',
|
||||||
'catchPhrase': 'Multi-tiered zero tolerance productivity',
|
catchPhrase: 'Multi-tiered zero tolerance productivity',
|
||||||
'bs': 'transition cutting-edge web services'
|
bs: 'transition cutting-edge web services'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ const {
|
|||||||
processIf,
|
processIf,
|
||||||
getBaseTransformPreset,
|
getBaseTransformPreset,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createObjectProperty,
|
createObjectProperty
|
||||||
} = require('@vue/compiler-core')
|
} = require('@vue/compiler-core')
|
||||||
|
|
||||||
const { parse } = require('@vue/compiler-dom')
|
const { parse } = require('@vue/compiler-dom')
|
||||||
|
|
||||||
function headTransform(node, context) {
|
function headTransform (node, context) {
|
||||||
console.log('NODE', node)
|
console.log('NODE', node)
|
||||||
if (node.type === 1 /* NodeTypes.ELEMENT */) {
|
if (node.type === 1 /* NodeTypes.ELEMENT */) {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -24,7 +24,7 @@ function headTransform(node, context) {
|
|||||||
node.children.length === 1 ? node.children[0] : 'null'
|
node.children.length === 1 ? node.children[0] : 'null'
|
||||||
)
|
)
|
||||||
|
|
||||||
//options.properties.push(option)
|
// options.properties.push(option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ function headTransform(node, context) {
|
|||||||
module.exports = function (source, map) {
|
module.exports = function (source, map) {
|
||||||
// TODO: add options
|
// TODO: add options
|
||||||
const ast = parse(source)
|
const ast = parse(source)
|
||||||
//console.log('AST', ast)
|
// console.log('AST', ast)
|
||||||
|
|
||||||
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset({
|
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset({
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@@ -49,7 +49,7 @@ module.exports = function (source, map) {
|
|||||||
|
|
||||||
const result = generate(ast, { mode: 'module' })
|
const result = generate(ast, { mode: 'module' })
|
||||||
|
|
||||||
console.log(result.code)
|
console.log(result.code)
|
||||||
|
|
||||||
this.callback(null, `
|
this.callback(null, `
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|||||||
@@ -77,6 +77,6 @@ setTimeout(() => {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('trigger app 4')
|
console.log('trigger app 4')
|
||||||
const App = Vue.extend({ template: `<div>app 4</div>` })
|
const App = Vue.extend({ template: '<div>app 4</div>' })
|
||||||
new App().$mount()
|
new App().$mount()
|
||||||
}, 10000)
|
}, 10000)
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
import { h, ref, computed, inject, Teleport } from 'vue'
|
||||||
|
import { renderMeta } from './render'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
metainfo: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const mapping = inject('__vueMetaConfig')
|
||||||
|
|
||||||
|
return {
|
||||||
|
mapping
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const targets = {}
|
||||||
|
|
||||||
|
for (const key in this.metainfo) {
|
||||||
|
const config = this.mapping[key] || {}
|
||||||
|
|
||||||
|
const vnodes = renderMeta(this, key, this.metainfo[key], config)
|
||||||
|
let target = (key !== 'base' && this.metainfo[key].target) || config.target || 'head'
|
||||||
|
|
||||||
|
if (Array.isArray(vnodes)) {
|
||||||
|
for (const vnode of vnodes) {
|
||||||
|
if (vnode.__vm_target) {
|
||||||
|
target = vnode.__vm_target
|
||||||
|
delete vnode.__vm_target
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targets[target]) {
|
||||||
|
targets[target] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
targets[target].push(vnode)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targets[target]) {
|
||||||
|
targets[target] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
targets[target].push(vnodes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
console.log('TARGETS', targets)
|
||||||
|
return Object.keys(targets).map(target => {
|
||||||
|
return h(Teleport, { to: target }, targets[target])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
const defaults = {
|
||||||
|
title: {
|
||||||
|
contentAttributes: false
|
||||||
|
},
|
||||||
|
base: {
|
||||||
|
contentAttributes: [
|
||||||
|
'href',
|
||||||
|
'target'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
nameAttribute: 'name',
|
||||||
|
contentAttributes: [
|
||||||
|
'content',
|
||||||
|
'name',
|
||||||
|
'http-equiv',
|
||||||
|
'charset'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
contentAttributes: [
|
||||||
|
'href',
|
||||||
|
'crossorigin',
|
||||||
|
'rel',
|
||||||
|
'media',
|
||||||
|
'integrity',
|
||||||
|
'hreflang',
|
||||||
|
'type',
|
||||||
|
'referrerpolicy',
|
||||||
|
'sizes',
|
||||||
|
'imagesrcset',
|
||||||
|
'imagesizes',
|
||||||
|
'as',
|
||||||
|
'color'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
contentAttributes: [
|
||||||
|
'media'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
script: {
|
||||||
|
contentAttributes: [
|
||||||
|
'src',
|
||||||
|
'type',
|
||||||
|
'nomodule',
|
||||||
|
'async',
|
||||||
|
'defer',
|
||||||
|
'crossorigin',
|
||||||
|
'integrity',
|
||||||
|
'referrerpolicy'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
noscript: {
|
||||||
|
contentAttributes: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMapping = {
|
||||||
|
body: {
|
||||||
|
tag: 'script',
|
||||||
|
target: 'body'
|
||||||
|
},
|
||||||
|
base: {
|
||||||
|
contentAttribute: 'href'
|
||||||
|
},
|
||||||
|
charset: {
|
||||||
|
tag: 'meta',
|
||||||
|
nameless: true,
|
||||||
|
contentAttribute: 'charset'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
tag: 'meta'
|
||||||
|
},
|
||||||
|
og: {
|
||||||
|
group: true,
|
||||||
|
namespacedAttribute: true,
|
||||||
|
tag: 'meta',
|
||||||
|
nameAttribute: 'property'
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
group: true,
|
||||||
|
namespacedAttribute: true,
|
||||||
|
tag: 'meta'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
defaults,
|
||||||
|
defaultMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasConfig (name) {
|
||||||
|
return !!defaults[name] || !!defaultMapping[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfigKey (name, key, config, dontLog) {
|
||||||
|
if (!dontLog) {
|
||||||
|
// console.log('getConfigKey', name, key, getConfigKey(name, key, config, true), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config && key in config) {
|
||||||
|
return config[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
for (const _name of name) {
|
||||||
|
if (_name && _name in defaults) {
|
||||||
|
return defaults[_name][key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name in defaults) {
|
||||||
|
return defaults[name][key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
+19
-14
@@ -1,24 +1,30 @@
|
|||||||
import { markRaw, reactive, computed, onMounted } from 'vue'
|
import { markRaw, reactive, onMounted } from 'vue'
|
||||||
|
import { defaultMapping } from './config'
|
||||||
|
|
||||||
const apps = {}
|
const apps = {}
|
||||||
|
let appId = 1
|
||||||
|
|
||||||
export function createMeta () {
|
export function createMeta ({ config }) {
|
||||||
const id = Symbol()
|
const id = Symbol(`vue-meta-${appId++}`)
|
||||||
|
|
||||||
const Meta = {
|
const Meta = {
|
||||||
id,
|
id,
|
||||||
|
|
||||||
install(app) {
|
install (app) {
|
||||||
let watchersAdded = false
|
let watchersAdded = false
|
||||||
|
|
||||||
app.mixin({
|
app.provide('__vueMetaConfig', {
|
||||||
created() {
|
...defaultMapping,
|
||||||
if (this === this.$root) {
|
...config
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mixin({
|
||||||
|
created () {
|
||||||
|
if (this === this.$root) {
|
||||||
watchersAdded = true
|
watchersAdded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.metaData) {
|
if (!this.metaData || watchersAdded) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +39,7 @@ export function createMeta () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const __meta = markRaw({
|
this.__meta = markRaw({
|
||||||
depth
|
depth
|
||||||
})
|
})
|
||||||
console.log('CREATED', this, this.metaData, depth)
|
console.log('CREATED', this, this.metaData, depth)
|
||||||
@@ -49,15 +55,14 @@ export function createMeta () {
|
|||||||
return Meta
|
return Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMeta () {
|
export function useMeta (rawMetainfo) {
|
||||||
onMounted(vmMounted)
|
onMounted(vmMounted)
|
||||||
|
|
||||||
const metaData = reactive([])
|
const metainfo = reactive(rawMetainfo)
|
||||||
console.log(this)
|
|
||||||
|
|
||||||
return metaData
|
return metainfo
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmMounted() {
|
function vmMounted () {
|
||||||
console.log('MOUNTED', this, arguments)
|
console.log('MOUNTED', this, arguments)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { h } from 'vue'
|
||||||
|
import { getConfigKey } from './config'
|
||||||
|
|
||||||
|
export function renderMeta (ctx, key, data, config) {
|
||||||
|
console.info('renderMeta', key, data, config)
|
||||||
|
|
||||||
|
if (config.group) {
|
||||||
|
return renderGroup(ctx, key, data, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderTag(ctx, key, data, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderGroup (ctx, key, data, config) {
|
||||||
|
console.info('renderGroup', key, data, config)
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
config.contentAttributes = getConfigKey([key, config.tag], 'contentAttributes', config)
|
||||||
|
return data.map(_data => renderTag(ctx, key, _data, config))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(data).map((childKey) => {
|
||||||
|
const groupConfig = {
|
||||||
|
group: key,
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.namespaced || config.namespacedAttribute) {
|
||||||
|
let namespace
|
||||||
|
if (config.namespaced) {
|
||||||
|
namespace = config.namespaced === true ? key : config.namespaced
|
||||||
|
groupConfig.tagNamespace = namespace
|
||||||
|
} else {
|
||||||
|
namespace = config.namespacedAttribute === true ? key : config.namespacedAttribute
|
||||||
|
groupConfig.fullName = `${namespace}:${childKey}`
|
||||||
|
groupConfig.slotName = `${namespace}(${childKey})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderTag(ctx, key, data[childKey], config, groupConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderTag (ctx, key, data, config = {}, groupConfig = {}) {
|
||||||
|
if (!config.group && Array.isArray(data)) {
|
||||||
|
return renderTag(ctx, key, { content: data }, config, groupConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag = config.tag || key } = data
|
||||||
|
const {
|
||||||
|
slotName = key,
|
||||||
|
fullName = key
|
||||||
|
} = groupConfig
|
||||||
|
|
||||||
|
let content, hasChilds
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((child) => {
|
||||||
|
return renderTag(ctx, key, child, config, groupConfig)
|
||||||
|
})
|
||||||
|
} else if (data.content && Array.isArray(data.content)) {
|
||||||
|
content = data.content.map((child) => {
|
||||||
|
if (typeof child === 'string') {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
return renderTag(ctx, key, child, config, groupConfig)
|
||||||
|
})
|
||||||
|
hasChilds = true
|
||||||
|
} else {
|
||||||
|
content = data
|
||||||
|
}
|
||||||
|
|
||||||
|
let { attrs: attributes } = data
|
||||||
|
if (!attributes && typeof data === 'object') {
|
||||||
|
attributes = {
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
delete attributes.tag
|
||||||
|
delete attributes.content
|
||||||
|
delete attributes.target
|
||||||
|
} else {
|
||||||
|
attributes = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChilds) {
|
||||||
|
content = getSlotContent(ctx, slotName, content, config, data)
|
||||||
|
} else {
|
||||||
|
const contentAttributes = getConfigKey(tag, 'contentAttributes', config)
|
||||||
|
|
||||||
|
if (contentAttributes) {
|
||||||
|
if (!config.nameless) {
|
||||||
|
const nameAttribute = getConfigKey(tag, 'nameAttribute', config)
|
||||||
|
if (nameAttribute) {
|
||||||
|
attributes[nameAttribute] = fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentAttribute = config.contentAttribute || contentAttributes[0]
|
||||||
|
attributes[contentAttribute] = getSlotContent(ctx, slotName, attributes[contentAttribute] || content, config, groupConfig)
|
||||||
|
content = undefined
|
||||||
|
} else {
|
||||||
|
content = getSlotContent(ctx, slotName, content, config, data, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalTag = groupConfig.tagNamespace
|
||||||
|
? `${groupConfig.tagNamespace}:${tag}`
|
||||||
|
: tag
|
||||||
|
|
||||||
|
console.info('FINAL TAG', finalTag)
|
||||||
|
console.log(' ATTRIBUTES', attributes)
|
||||||
|
console.log(' CONTENT', content)
|
||||||
|
// console.log(data, attributes, config)
|
||||||
|
|
||||||
|
if (hasChilds) {
|
||||||
|
for (const child of content) {
|
||||||
|
if (typeof child === 'string') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.type === finalTag) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vnode = h(finalTag, attributes, content)
|
||||||
|
|
||||||
|
if (data.target) {
|
||||||
|
vnode.__vm_target = data.target
|
||||||
|
}
|
||||||
|
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSlotContent ({ metainfo, $slots }, slotName, content, config, groupConfig) {
|
||||||
|
if (!$slots[slotName]) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
const slotProps = {
|
||||||
|
content,
|
||||||
|
metainfo
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupConfig.group) {
|
||||||
|
slotProps[groupConfig.group] = groupConfig.data
|
||||||
|
}
|
||||||
|
|
||||||
|
content = $slots[slotName](slotProps)
|
||||||
|
return content[0].children
|
||||||
|
}
|
||||||
+5
-6
@@ -1,11 +1,10 @@
|
|||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||||
import VueMeta from '../../'
|
|
||||||
|
|
||||||
/*Vue.use(Router)
|
/* Vue.use(Router)
|
||||||
Vue.use(VueMeta, {
|
Vue.use(VueMeta, {
|
||||||
tagIDKeyName: 'hid'
|
tagIDKeyName: 'hid'
|
||||||
})*/
|
}) */
|
||||||
|
|
||||||
export default function createMyApp () {
|
export default function createMyApp () {
|
||||||
const Home = {
|
const Home = {
|
||||||
@@ -116,7 +115,7 @@ export default function createMyApp () {
|
|||||||
users: process.server ? [] : window.users
|
users: process.server ? [] : window.users
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted () {
|
||||||
const { set, remove } = this.$meta().addApp('client-only')
|
const { set, remove } = this.$meta().addApp('client-only')
|
||||||
set({
|
set({
|
||||||
bodyAttrs: { class: 'client-only' }
|
bodyAttrs: { class: 'client-only' }
|
||||||
@@ -149,11 +148,11 @@ export default function createMyApp () {
|
|||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
/*const { set } = app.$meta().addApp('custom')
|
/* const { set } = app.$meta().addApp('custom')
|
||||||
set({
|
set({
|
||||||
bodyAttrs: { class: 'custom-app' },
|
bodyAttrs: { class: 'custom-app' },
|
||||||
meta: [{ charset: 'utf-8' }]
|
meta: [{ charset: 'utf-8' }]
|
||||||
})*/
|
}) */
|
||||||
|
|
||||||
return { app, router }
|
return { app, router }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import template from 'lodash/template'
|
import template from 'lodash/template'
|
||||||
const { renderToString } = require('@vue/server-renderer')
|
|
||||||
import createApp from './App'
|
import createApp from './App'
|
||||||
|
const { renderToString } = require('@vue/server-renderer')
|
||||||
|
|
||||||
const templateFile = path.resolve(__dirname, 'app.template.html')
|
const templateFile = path.resolve(__dirname, 'app.template.html')
|
||||||
const templateContent = fs.readFileSync(templateFile, { encoding: 'utf8' })
|
const templateContent = fs.readFileSync(templateFile, { encoding: 'utf8' })
|
||||||
@@ -18,12 +18,12 @@ export async function renderPage ({ url }) {
|
|||||||
await router.push(url.substr(4))
|
await router.push(url.substr(4))
|
||||||
|
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
/*console.log(router)
|
/* console.log(router)
|
||||||
const matchedComponents = router.getMatchedComponents()
|
const matchedComponents = router.getMatchedComponents()
|
||||||
// no matched routes, reject with 404
|
// no matched routes, reject with 404
|
||||||
if (!matchedComponents.length) {
|
if (!matchedComponents.length) {
|
||||||
return reject({ code: 404 })
|
return reject({ code: 404 })
|
||||||
}*/
|
} */
|
||||||
|
|
||||||
const appHtml = await renderToString(app)
|
const appHtml = await renderToString(app)
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export async function renderPage ({ url }) {
|
|||||||
head: () => {},
|
head: () => {},
|
||||||
bodyPrepend: () => {},
|
bodyPrepend: () => {},
|
||||||
bodyAppend: () => {}
|
bodyAppend: () => {}
|
||||||
//...app.$meta().inject()
|
// ...app.$meta().inject()
|
||||||
})
|
})
|
||||||
|
|
||||||
return pageHtml
|
return pageHtml
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
<p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p>
|
<p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
<head type="template">
|
<head type="template">
|
||||||
<title v-if="title">{{ title }}</title>
|
<title v-if="title">{{ title }}</title>
|
||||||
<meta v-for="meta in metas" :name="meta.name" :content="meta.content" />
|
<meta v-for="meta in metas" :name="meta.name" :content="meta.content" />
|
||||||
</head>
|
</head>
|
||||||
|
*/
|
||||||
<script>
|
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
|||||||
+103
-74
@@ -1,27 +1,17 @@
|
|||||||
import { createApp, defineComponent, reactive, toRefs, h, onMounted } from 'vue'
|
import { createApp, defineComponent, reactive, toRefs, h } from 'vue'
|
||||||
import VueMeta from 'vue-meta'
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import About from './about.vue'
|
import Metainfo from '../next/Metainfo.vue'
|
||||||
import { createMeta, useMeta } from '../next'
|
import { createMeta, useMeta } from '../next'
|
||||||
|
import About from './about.vue'
|
||||||
/*Vue.use(VueMeta, {
|
|
||||||
refreshOnceOnNavigation: true
|
|
||||||
})*/
|
|
||||||
|
|
||||||
const meta = createMeta({
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
let metaUpdated = 'no'
|
let metaUpdated = 'no'
|
||||||
|
|
||||||
const ChildComponent = defineComponent({
|
const ChildComponent = defineComponent({
|
||||||
name: 'child-component',
|
name: 'child-component',
|
||||||
props: {
|
props: {
|
||||||
page: String
|
page: String
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<metainfo>
|
|
||||||
<title>Another Title</title>
|
|
||||||
</metainfo>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>You're looking at the <strong>{{ page }}</strong> page</h3>
|
<h3>You're looking at the <strong>{{ page }}</strong> page</h3>
|
||||||
<p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p>
|
<p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p>
|
||||||
@@ -38,41 +28,20 @@ const ChildComponent = defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
console.log(this)
|
//console.log(this)
|
||||||
},
|
},
|
||||||
setup () {
|
setup () {
|
||||||
const metaData = useMeta({
|
|
||||||
})
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
date: null,
|
date: null,
|
||||||
metaUpdated
|
metaUpdated
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
onMounted(function vmMounted() {
|
|
||||||
console.log('MOUNTED', this, arguments)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metaData,
|
|
||||||
...toRefs(state)
|
...toRefs(state)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
/*mounted () {
|
|
||||||
this.interval = setInterval(() => {
|
|
||||||
this.date = new Date()
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
destroyed () {
|
|
||||||
clearInterval(this.interval)
|
|
||||||
}*/
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// this wrapper function is not a requirement for vue-router,
|
|
||||||
// just a demonstration that render-function style components also work.
|
|
||||||
// See https://github.com/nuxt/vue-meta/issues/9 for more info.
|
|
||||||
function view (page) {
|
function view (page) {
|
||||||
return {
|
return {
|
||||||
name: `section-${page}`,
|
name: `section-${page}`,
|
||||||
@@ -82,6 +51,86 @@ function view (page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup () {
|
||||||
|
const metainfo = useMeta({
|
||||||
|
base: { href: '/vue-router', target: '_blank' },
|
||||||
|
charset: 'utf8',
|
||||||
|
title: 'My Title',
|
||||||
|
description: 'The Description',
|
||||||
|
og: {
|
||||||
|
title: 'Og Title',
|
||||||
|
description: 'Bla bla',
|
||||||
|
image: [
|
||||||
|
'https://picsum.photos/600/400/?image=80',
|
||||||
|
'https://picsum.photos/600/400/?image=82'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
title: 'Twitter Title'
|
||||||
|
},
|
||||||
|
noscript: [
|
||||||
|
'<!-- // A code comment -->',
|
||||||
|
{ tag: 'link', rel: 'stylesheet', href: 'style.css' }
|
||||||
|
],
|
||||||
|
otherNoscript: {
|
||||||
|
tag: 'noscript',
|
||||||
|
'data-test': 'hello',
|
||||||
|
content: [
|
||||||
|
'<!-- // 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', target: 'body' },
|
||||||
|
{ src: 'body-script3.js', target: '#put-it-here' }
|
||||||
|
],
|
||||||
|
esi: {
|
||||||
|
content: [{
|
||||||
|
tag: 'choose',
|
||||||
|
content: [{
|
||||||
|
tag: 'when',
|
||||||
|
test: '$(HTTP_COOKIE{group})=="Advanced"',
|
||||||
|
content: [{
|
||||||
|
tag: 'include',
|
||||||
|
src: 'http://www.example.com/advanced.html'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => (metainfo.title = 'My Updated Title'), 2000)
|
||||||
|
|
||||||
|
return {
|
||||||
|
metainfo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<metainfo :metainfo="metainfo">
|
||||||
|
<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>
|
||||||
|
</metainfo>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<h1>vue-router</h1>
|
||||||
|
<router-link to="/">Home</router-link>
|
||||||
|
<!-- router-link to="/about">About</router-link -->
|
||||||
|
<transition name="page" mode="out-in">
|
||||||
|
<router-view></router-view>
|
||||||
|
</transition>
|
||||||
|
<p>Inspect Element to see the meta info</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory('/vue-router'),
|
history: createWebHistory('/vue-router'),
|
||||||
routes: [
|
routes: [
|
||||||
@@ -90,45 +139,27 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const Metadata = {
|
const meta = createMeta({
|
||||||
template: `
|
config: {
|
||||||
<teleport to="head">
|
esi: {
|
||||||
<slot />
|
group: true,
|
||||||
</teleport>
|
namespaced: true,
|
||||||
|
contentAttributes: [
|
||||||
<teleport to="body">
|
'src',
|
||||||
<slot name="body" />
|
'test',
|
||||||
</teleport>
|
'text'
|
||||||
`
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const App = {
|
})
|
||||||
template: `
|
|
||||||
<metainfo>
|
|
||||||
<title>My Title</title>
|
|
||||||
<meta name="charset" content="utf8" />
|
|
||||||
|
|
||||||
<template v-slot:body>
|
|
||||||
<script>var a = 1</script>
|
|
||||||
</template>
|
|
||||||
</metainfo>
|
|
||||||
|
|
||||||
<div id="app">
|
|
||||||
<h1>vue-router</h1>
|
|
||||||
<router-link to="/">Home</router-link>
|
|
||||||
<router-link to="/about">About</router-link>
|
|
||||||
<transition name="page" mode="out-in">
|
|
||||||
<router-view></router-view>
|
|
||||||
</transition>
|
|
||||||
<p>Inspect Element to see the meta info</p>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.component('metainfo', Metadata)
|
app.component('metainfo', Metainfo)
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(meta)
|
app.use(meta)
|
||||||
|
app.mount('#app')
|
||||||
|
|
||||||
|
// old stuff:
|
||||||
/*
|
/*
|
||||||
const { set, remove } = app.$meta().addApp('custom')
|
const { set, remove } = app.$meta().addApp('custom')
|
||||||
|
|
||||||
@@ -142,8 +173,6 @@ set({
|
|||||||
})
|
})
|
||||||
setTimeout(() => remove(), 3000)
|
setTimeout(() => remove(), 3000)
|
||||||
*/
|
*/
|
||||||
app.mount('#app')
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const waitFor = time => new Promise(r => setTimeout(r, time || 1000))
|
const waitFor = time => new Promise(r => setTimeout(r, time || 1000))
|
||||||
const o = {
|
const o = {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<a href="/">← Examples index</a>
|
<a href="/">← Examples index</a>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<div id="put-it-here"></div>
|
||||||
<script src="/__build__/vue-router.js"></script>
|
<script src="/__build__/vue-router.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import webpack from 'webpack'
|
|||||||
import WebpackBar from 'webpackbar'
|
import WebpackBar from 'webpackbar'
|
||||||
import { VueLoaderPlugin } from 'vue-loader'
|
import { VueLoaderPlugin } from 'vue-loader'
|
||||||
|
|
||||||
const srcDir = path.join(__dirname, '..', 'src')
|
// const srcDir = path.join(__dirname, '..', 'src')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
@@ -63,7 +63,7 @@ export default {
|
|||||||
// is a simple `export * from '@vue/runtime-dom`. However having this
|
// is a simple `export * from '@vue/runtime-dom`. However having this
|
||||||
// extra re-export somehow causes webpack to always invalidate the module
|
// extra re-export somehow causes webpack to always invalidate the module
|
||||||
// on the first HMR update and causes the page to reload.
|
// on the first HMR update and causes the page to reload.
|
||||||
'vue': 'vue/dist/vue.esm.js',
|
vue: 'vue/dist/vue.esm-bundler.js',
|
||||||
'vue-meta': path.resolve(__dirname, './next/')
|
'vue-meta': path.resolve(__dirname, './next/')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@
|
|||||||
"dev": "cd examples && yarn dev && cd ..",
|
"dev": "cd examples && yarn dev && cd ..",
|
||||||
"docs": "vuepress dev --host 0.0.0.0 --port 3000 docs",
|
"docs": "vuepress dev --host 0.0.0.0 --port 3000 docs",
|
||||||
"docs:build": "vuepress build docs",
|
"docs:build": "vuepress build docs",
|
||||||
"lint": "eslint src test",
|
"lint": "eslint src test examples",
|
||||||
"prerelease": "git checkout master && git pull -r",
|
"prerelease": "git checkout master && git pull -r",
|
||||||
"release": "yarn lint && yarn test && standard-version",
|
"release": "yarn lint && yarn test && standard-version",
|
||||||
"test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
|
"test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
|
||||||
|
|||||||
Reference in New Issue
Block a user