2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-24 11:00:34 +03:00

fix: better ssr support

This commit is contained in:
pimlie
2021-05-17 02:09:34 +02:00
parent 8069449119
commit 1d847870e9
8 changed files with 47 additions and 41 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import type { VNodeProps, AllowedComponentProps, ComponentCustomProps } from 'vue'
import { getCurrentManager } from './useApi' import { getCurrentManager } from './useApi'
import type { VNodeProps, AllowedComponentProps, ComponentCustomProps } from 'vue'
export const MetainfoImpl = defineComponent({ export const MetainfoImpl = defineComponent({
name: 'Metainfo', name: 'Metainfo',
+1 -1
View File
@@ -1,5 +1,5 @@
import type { MetaTagConfigKey, MetaTagName } from '../types'
import { tags } from './tags' import { tags } from './tags'
import type { MetaTagConfigKey, MetaTagName } from '../types'
export function getTagConfigItem ( export function getTagConfigItem (
tagOrName: Array<MetaTagName>, tagOrName: Array<MetaTagName>,
+25 -21
View File
@@ -1,13 +1,13 @@
import { h, reactive, onUnmounted, App, Teleport, Comment, getCurrentInstance, ComponentInternalInstance, Slots } from 'vue' import { h, reactive, onUnmounted, App, Teleport, Comment, getCurrentInstance, ComponentInternalInstance, Slots } from 'vue'
import type { VNode, ComponentPublicInstance } from 'vue'
import { isArray, isFunction } from '@vue/shared' import { isArray, isFunction } from '@vue/shared'
import { createMergedObject, MergedObjectBuilder } from './object-merge' import { createMergedObject, MergedObjectBuilder } from './object-merge'
import { renderMeta } from './render' import { renderMeta } from './render'
import { metaActiveKey } from './symbols' import { metaActiveKey } from './symbols'
import { Metainfo } from './Metainfo' import { Metainfo } from './Metainfo'
import type { ResolveMethod } from './object-merge'
import { defaultConfig } from './config/default' import { defaultConfig } from './config/default'
import * as defaultResolver from './resolvers/deepest' import * as defaultResolver from './resolvers/deepest'
import type { VNode, ComponentPublicInstance } from 'vue'
import type { ResolveMethod } from './object-merge'
import type { import type {
MetaActive, MetaActive,
@@ -18,7 +18,6 @@ import type {
MetaResolveContext, MetaResolveContext,
MetaTeleports, MetaTeleports,
MetaSource, MetaSource,
MetaResolverSetup,
MetaProxy MetaProxy
} from './types' } from './types'
@@ -26,10 +25,10 @@ export const ssrAttribute = 'data-vm-ssr'
export const active: MetaActive = reactive({}) export const active: MetaActive = reactive({})
export function addVnode (teleports: MetaTeleports, to: string, vnodes: VNode | Array<VNode>): void { export function addVnode (isSSR: boolean, teleports: MetaTeleports, to: string, vnodes: VNode | Array<VNode>): void {
const nodes = (isArray(vnodes) ? vnodes : [vnodes]) as Array<VNode> const nodes = (isArray(vnodes) ? vnodes : [vnodes]) as Array<VNode>
if (__BROWSER__) { if (!isSSR) {
// Comments shouldnt have any use on the client as they are not reactive anyway // Comments shouldnt have any use on the client as they are not reactive anyway
nodes.forEach((vnode, idx) => { nodes.forEach((vnode, idx) => {
if (vnode.type === Comment) { if (vnode.type === Comment) {
@@ -54,27 +53,29 @@ export function addVnode (teleports: MetaTeleports, to: string, vnodes: VNode |
} }
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
export type createMetaManagerMethod = (config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager export type CreateMetaManagerMethod = (isSSR: boolean, config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager
export const createMetaManager = (config?: MetaConfig, resolver?: MetaResolver): MetaManager => MetaManager.create(config || defaultConfig, resolver || (defaultResolver as MetaResolver)) export const createMetaManager = (isSSR = false, config?: MetaConfig, resolver?: MetaResolver): MetaManager => MetaManager.create(isSSR, config || defaultConfig, resolver || (defaultResolver as MetaResolver))
export class MetaManager { export class MetaManager {
isSSR = false
config: MetaConfig config: MetaConfig
target: MergedObjectBuilder<MetaSource> target: MergedObjectBuilder<MetaSource>
resolver?: MetaResolverSetup resolver?: MetaResolver
ssrCleanedUp: boolean = false ssrCleanedUp: boolean = false
constructor (config: MetaConfig, target: MergedObjectBuilder<MetaSource>, resolver: MetaResolver | ResolveMethod) { constructor (isSSR: boolean, config: MetaConfig, target: MergedObjectBuilder<MetaSource>, resolver: MetaResolver | ResolveMethod) {
this.isSSR = isSSR
this.config = config this.config = config
this.target = target this.target = target
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
this.resolver = resolver as unknown as MetaResolverSetup this.resolver = resolver
} }
} }
static create: createMetaManagerMethod = (config, resolver) => { static create: CreateMetaManagerMethod = (isSSR, config, resolver) => {
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => { const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => {
if (isFunction(resolver)) { if (isFunction(resolver)) {
return resolver(options, contexts, active, key, pathSegments) return resolver(options, contexts, active, key, pathSegments)
@@ -86,7 +87,7 @@ export class MetaManager {
const mergedObject = createMergedObject<MetaSource>(resolve, active) const mergedObject = createMergedObject<MetaSource>(resolve, active)
// TODO: validate resolver // TODO: validate resolver
const manager = new MetaManager(config, mergedObject, resolver) const manager = new MetaManager(isSSR, config, mergedObject, resolver)
return manager return manager
} }
@@ -107,8 +108,9 @@ export class MetaManager {
}) })
const resolveContext: MetaResolveContext = { vm } const resolveContext: MetaResolveContext = { vm }
if (this.resolver) { const { resolver } = this
this.resolver.setup(resolveContext) if (resolver && resolver.setup) {
resolver.setup(resolveContext)
} }
// TODO: optimize initial compute (once) // TODO: optimize initial compute (once)
@@ -169,9 +171,10 @@ export class MetaManager {
render ({ slots }: { slots?: Slots } = {}): VNode[] { render ({ slots }: { slots?: Slots } = {}): VNode[] {
// TODO: clean this method // TODO: clean this method
const { isSSR } = this
// cleanup ssr tags if not yet done // cleanup ssr tags if not yet done
if (__BROWSER__ && !this.ssrCleanedUp) { if (!isSSR && !this.ssrCleanedUp) {
this.ssrCleanedUp = true this.ssrCleanedUp = true
// Listen for DOM loaded because tags in the body couldnt // Listen for DOM loaded because tags in the body couldnt
@@ -181,9 +184,9 @@ export class MetaManager {
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`) const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`)
if (ssrTags && ssrTags.length) { if (ssrTags && ssrTags.length) {
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)) ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el))
} }
}) }, { once: true })
} }
const teleports: MetaTeleports = {} const teleports: MetaTeleports = {}
@@ -192,7 +195,7 @@ export class MetaManager {
const config = this.config[key] || {} const config = this.config[key] || {}
let renderedNodes = renderMeta( let renderedNodes = renderMeta(
{ metainfo: active, slots }, { isSSR, metainfo: active, slots },
key, key,
active[key], active[key],
config config
@@ -217,7 +220,7 @@ export class MetaManager {
} }
for (const { to, vnode } of renderedNodes) { for (const { to, vnode } of renderedNodes) {
addVnode(teleports, to || defaultTo || 'head', vnode) addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode)
} }
} }
@@ -232,13 +235,14 @@ export class MetaManager {
const slot = slots[slotName] const slot = slots[slotName]
if (isFunction(slot)) { if (isFunction(slot)) {
addVnode(teleports, tagName, slot({ metainfo: active })) addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }))
} }
} }
} }
return Object.keys(teleports).map((to) => { return Object.keys(teleports).map((to) => {
return h(Teleport, { to }, teleports[to]) const teleport = teleports[to]
return h(Teleport, { to }, teleport)
}) })
} }
} }
+8 -4
View File
@@ -197,7 +197,7 @@ export function renderTag (
// console.info('FINAL TAG', finalTag) // console.info('FINAL TAG', finalTag)
// console.log(' ATTRIBUTES', attributes) // console.log(' ATTRIBUTES', attributes)
// console.log(' CONTENT', content) // console.log(' CONTENT', content)
// // console.log(data, attributes, config) // console.log(data, attributes, config)
if (isRaw && content) { if (isRaw && content) {
attributes.innerHTML = content attributes.innerHTML = content
@@ -221,11 +221,11 @@ export function renderAttributes (
// console.info('renderAttributes', key, data, config) // console.info('renderAttributes', key, data, config)
const { attributesFor } = config const { attributesFor } = config
if (!attributesFor) { if (!attributesFor || !data) {
return return
} }
if (!__BROWSER__) { if (context.isSSR) {
// render attributes in a placeholder vnode so Vue // render attributes in a placeholder vnode so Vue
// will render the string for us // will render the string for us
return { return {
@@ -257,7 +257,11 @@ export function renderAttributes (
const { el, attrs } = cachedElements[attributesFor] const { el, attrs } = cachedElements[attributesFor]
for (const attr in data) { for (const attr in data) {
const content = getSlotContent(context, `${key}(${attr})`, data[attr], data) let content = getSlotContent(context, `${key}(${attr})`, data[attr], data)
if (isArray(content)) {
content = content.join(',')
}
el.setAttribute(attr, content || '') el.setAttribute(attr, content || '')
+5 -6
View File
@@ -1,13 +1,12 @@
import { renderToString } from '@vue/server-renderer'
import type { App } from 'vue' import type { App } from 'vue'
import type { SSRContext } from '@vue/server-renderer' import type { SSRContext } from '@vue/server-renderer'
export async function renderToStringWithMeta (app: App, ctx: SSRContext = {}): Promise<[string, SSRContext]> { export async function renderMetaToString (app: App, ctx: SSRContext = {}): Promise<SSRContext> {
const html = await renderToString(app, ctx)
// TODO: better way of determining whether meta was rendered with the component or not // TODO: better way of determining whether meta was rendered with the component or not
if (!ctx.teleports || !ctx.teleports.head) { if (!ctx.teleports || !ctx.teleports.head) {
const teleports = app.config.globalProperties.$metaManager.render() const { renderToString } = await import('@vue/server-renderer')
const teleports = app.config.globalProperties.$metaManager?.render()
await Promise.all(teleports.map((teleport: any) => renderToString(teleport, ctx))) await Promise.all(teleports.map((teleport: any) => renderToString(teleport, ctx)))
} }
@@ -21,5 +20,5 @@ export async function renderToStringWithMeta (app: App, ctx: SSRContext = {}): P
} }
} }
return [html, ctx] return ctx
} }
+4 -5
View File
@@ -42,7 +42,7 @@ export interface MetaActive {
* Context passed to the meta resolvers * Context passed to the meta resolvers
*/ */
export type MetaResolveContext = ResolveContext & { export type MetaResolveContext = ResolveContext & {
vm: ComponentInternalInstance | undefined vm?: ComponentInternalInstance
} }
export type MetaResolveSetup = (context: MetaResolveContext) => void export type MetaResolveSetup = (context: MetaResolveContext) => void
@@ -52,9 +52,7 @@ export type MetaResolver = {
resolve: ResolveMethod resolve: ResolveMethod
} }
export type MetaResolverSetup = Modify<MetaResolver, { export type MetaResolverSetup = Required<MetaResolver>
setup: MetaResolveSetup
}>
/** /**
* @internal * @internal
@@ -74,8 +72,9 @@ export interface MetaGuards {
* @internal * @internal
*/ */
export interface MetaRenderContext { export interface MetaRenderContext {
slots?: Slots isSSR: boolean
metainfo: MetaActive metainfo: MetaActive
slots?: Slots
} }
/** /**
+2 -2
View File
@@ -1,8 +1,8 @@
import { inject, getCurrentInstance, ComponentInternalInstance, isProxy, watch } from 'vue' import { inject, getCurrentInstance, ComponentInternalInstance, isProxy, watch } from 'vue'
import type { MetaManager } from './manager'
import { metaActiveKey } from './symbols' import { metaActiveKey } from './symbols'
import type { MetaActive, MetaSource, MetaProxy } from './types'
import { applyDifference } from './utils/diff' import { applyDifference } from './utils/diff'
import type { MetaManager } from './manager'
import type { MetaActive, MetaSource, MetaProxy } from './types'
export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager | undefined { export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager | undefined {
if (!vm) { if (!vm) {
+1 -1
View File
@@ -3,7 +3,7 @@ export const pluck = <T extends Record<string, any>>(collection: T[], key: strin
const plucked: T[] = [] const plucked: T[] = []
for (const row of collection) { for (const row of collection) {
if (key in row) { if (row && key in row) {
plucked.push(row[key]) plucked.push(row[key])
if (callback) { if (callback) {