UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

278 lines (232 loc) 6.33 kB
import { isRuntimeSsrPreHydration } from '../platform/Platform.js' import extend from '../../utils/extend/extend.js' let updateId = null, currentClientMeta export const clientList = [] function normalize(meta) { if (meta.title) { meta.title = meta.titleTemplate ? meta.titleTemplate(meta.title) : meta.title delete meta.titleTemplate } ;[ ['meta', 'content'], ['link', 'href'] ].forEach(type => { const metaType = meta[type[0]], metaProp = type[1] for (const name in metaType) { const metaLink = metaType[name] if (metaLink.template) { if (Object.keys(metaLink).length === 1) { delete metaType[name] } else { metaLink[metaProp] = metaLink.template(metaLink[metaProp] || '') delete metaLink.template } } } }) } function changed(old, def) { if (Object.keys(old).length !== Object.keys(def).length) { return true } for (const key in old) { if (old[key] !== def[key]) { return true } } } function bodyFilter(name) { return ['class', 'style'].includes(name) === false } function htmlFilter(name) { return ['lang', 'dir'].includes(name) === false } function diff(meta, other) { const add = {}, remove = {} if (meta === void 0) { return { add: other, remove } } if (meta.title !== other.title) { add.title = other.title } ;['meta', 'link', 'script', 'htmlAttr', 'bodyAttr'].forEach(type => { const old = meta[type], cur = other[type] remove[type] = [] if (old === void 0 || old === null) { add[type] = cur return } add[type] = {} for (const key in old) { if (cur.hasOwnProperty(key) === false) { remove[type].push(key) } } for (const key in cur) { if (old.hasOwnProperty(key) === false) { add[type][key] = cur[key] } else if (changed(old[key], cur[key]) === true) { remove[type].push(key) add[type][key] = cur[key] } } }) return { add, remove } } function apply({ add, remove }) { if (add.title) { document.title = add.title } if (Object.keys(remove).length !== 0) { ;['meta', 'link', 'script'].forEach(type => { remove[type].forEach(name => { document.head.querySelector(`${type}[data-qmeta="${name}"]`).remove() }) }) remove.htmlAttr.filter(htmlFilter).forEach(name => { document.documentElement.removeAttribute(name) }) remove.bodyAttr.filter(bodyFilter).forEach(name => { document.body.removeAttribute(name) }) } ;['meta', 'link', 'script'].forEach(type => { const metaType = add[type] for (const name in metaType) { const tag = document.createElement(type) for (const att in metaType[name]) { if (att !== 'innerHTML') { tag.setAttribute(att, metaType[name][att]) } } tag.setAttribute('data-qmeta', name) if (type === 'script') { tag.innerHTML = metaType[name].innerHTML || '' } document.head.appendChild(tag) } }) Object.keys(add.htmlAttr) .filter(htmlFilter) .forEach(name => { document.documentElement.setAttribute(name, add.htmlAttr[name] || '') }) Object.keys(add.bodyAttr) .filter(bodyFilter) .forEach(name => { document.body.setAttribute(name, add.bodyAttr[name] || '') }) } function getAttr(seed) { return att => { const val = seed[att] return att + (val !== true && val !== void 0 ? `="${val}"` : '') } } function getHead(meta) { let output = '' if (meta.title) { output += `<title>${meta.title}</title>` } ;['meta', 'link', 'script'].forEach(type => { const metaType = meta[type] for (const att in metaType) { const attrs = Object.keys(metaType[att]) .filter(item => item !== 'innerHTML') .map(getAttr(metaType[att])) output += `<${type} ${attrs.join(' ')} data-qmeta="${att}">` if (type === 'script') { output += (metaType[att].innerHTML || '') + '</script>' } } }) return output } function injectServerMeta(ssrContext) { const data = { title: '', titleTemplate: null, meta: {}, link: {}, htmlAttr: {}, bodyAttr: {}, noscript: {} } const list = ssrContext.__qMetaList for (let i = 0; i < list.length; i++) { extend(true, data, list[i]) } normalize(data) const nonce = ssrContext.nonce !== void 0 ? ` nonce="${ssrContext.nonce}"` : '' const ctx = ssrContext._meta const htmlAttr = Object.keys(data.htmlAttr).filter(htmlFilter) if (htmlAttr.length !== 0) { ctx.htmlAttrs += (ctx.htmlAttrs.length !== 0 ? ' ' : '') + htmlAttr.map(getAttr(data.htmlAttr)).join(' ') } ctx.headTags += getHead(data) const bodyAttr = Object.keys(data.bodyAttr).filter(bodyFilter) if (bodyAttr.length !== 0) { ctx.bodyAttrs += (ctx.bodyAttrs.length !== 0 ? ' ' : '') + bodyAttr.map(getAttr(data.bodyAttr)).join(' ') } ctx.bodyTags += Object.keys(data.noscript) .map( name => `<noscript data-qmeta="${name}">${data.noscript[name]}</noscript>` ) .join('') + `<script${nonce} id="qmeta-init">window.__Q_META__=${delete data.noscript && JSON.stringify(data)}</script>` } function updateClientMeta() { updateId = null const data = { title: '', titleTemplate: null, meta: {}, link: {}, script: {}, htmlAttr: {}, bodyAttr: {} } for (let i = 0; i < clientList.length; i++) { const { active, val } = clientList[i] if (active === true) { extend(true, data, val) } } normalize(data) apply(diff(currentClientMeta, data)) currentClientMeta = data } export function planClientUpdate() { if (updateId !== null) clearTimeout(updateId) updateId = setTimeout(updateClientMeta, 50) } export default { install(opts) { if (__QUASAR_SSR_SERVER__) { const { ssrContext } = opts ssrContext.__qMetaList = [] ssrContext.onRendered(() => { injectServerMeta(ssrContext) }) } else if ( this.__installed !== true && isRuntimeSsrPreHydration.value === true ) { currentClientMeta = window.__Q_META__ document.getElementById('qmeta-init').remove() } } }