mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-13 10:12:24 +03:00
fix: better ssr support
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import type { VNodeProps, AllowedComponentProps, ComponentCustomProps } from 'vue'
|
||||
import { getCurrentManager } from './useApi'
|
||||
import type { VNodeProps, AllowedComponentProps, ComponentCustomProps } from 'vue'
|
||||
|
||||
export const MetainfoImpl = defineComponent({
|
||||
name: 'Metainfo',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MetaTagConfigKey, MetaTagName } from '../types'
|
||||
import { tags } from './tags'
|
||||
import type { MetaTagConfigKey, MetaTagName } from '../types'
|
||||
|
||||
export function getTagConfigItem (
|
||||
tagOrName: Array<MetaTagName>,
|
||||
|
||||
+25
-21
@@ -1,13 +1,13 @@
|
||||
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 { createMergedObject, MergedObjectBuilder } from './object-merge'
|
||||
import { renderMeta } from './render'
|
||||
import { metaActiveKey } from './symbols'
|
||||
import { Metainfo } from './Metainfo'
|
||||
import type { ResolveMethod } from './object-merge'
|
||||
import { defaultConfig } from './config/default'
|
||||
import * as defaultResolver from './resolvers/deepest'
|
||||
import type { VNode, ComponentPublicInstance } from 'vue'
|
||||
import type { ResolveMethod } from './object-merge'
|
||||
|
||||
import type {
|
||||
MetaActive,
|
||||
@@ -18,7 +18,6 @@ import type {
|
||||
MetaResolveContext,
|
||||
MetaTeleports,
|
||||
MetaSource,
|
||||
MetaResolverSetup,
|
||||
MetaProxy
|
||||
} from './types'
|
||||
|
||||
@@ -26,10 +25,10 @@ export const ssrAttribute = 'data-vm-ssr'
|
||||
|
||||
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>
|
||||
|
||||
if (__BROWSER__) {
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
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
|
||||
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 {
|
||||
isSSR = false
|
||||
config: MetaConfig
|
||||
target: MergedObjectBuilder<MetaSource>
|
||||
resolver?: MetaResolverSetup
|
||||
resolver?: MetaResolver
|
||||
|
||||
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.target = target
|
||||
|
||||
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) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments)
|
||||
@@ -86,7 +87,7 @@ export class MetaManager {
|
||||
const mergedObject = createMergedObject<MetaSource>(resolve, active)
|
||||
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver)
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver)
|
||||
return manager
|
||||
}
|
||||
|
||||
@@ -107,8 +108,9 @@ export class MetaManager {
|
||||
})
|
||||
|
||||
const resolveContext: MetaResolveContext = { vm }
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext)
|
||||
const { resolver } = this
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext)
|
||||
}
|
||||
|
||||
// TODO: optimize initial compute (once)
|
||||
@@ -169,9 +171,10 @@ export class MetaManager {
|
||||
|
||||
render ({ slots }: { slots?: Slots } = {}): VNode[] {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this
|
||||
|
||||
// cleanup ssr tags if not yet done
|
||||
if (__BROWSER__ && !this.ssrCleanedUp) {
|
||||
if (!isSSR && !this.ssrCleanedUp) {
|
||||
this.ssrCleanedUp = true
|
||||
|
||||
// Listen for DOM loaded because tags in the body couldnt
|
||||
@@ -181,9 +184,9 @@ export class MetaManager {
|
||||
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`)
|
||||
|
||||
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 = {}
|
||||
@@ -192,7 +195,7 @@ export class MetaManager {
|
||||
const config = this.config[key] || {}
|
||||
|
||||
let renderedNodes = renderMeta(
|
||||
{ metainfo: active, slots },
|
||||
{ isSSR, metainfo: active, slots },
|
||||
key,
|
||||
active[key],
|
||||
config
|
||||
@@ -217,7 +220,7 @@ export class MetaManager {
|
||||
}
|
||||
|
||||
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]
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }))
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to])
|
||||
const teleport = teleports[to]
|
||||
return h(Teleport, { to }, teleport)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -197,7 +197,7 @@ export function renderTag (
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content
|
||||
@@ -221,11 +221,11 @@ export function renderAttributes (
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
|
||||
const { attributesFor } = config
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!__BROWSER__) {
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
@@ -257,7 +257,11 @@ export function renderAttributes (
|
||||
const { el, attrs } = cachedElements[attributesFor]
|
||||
|
||||
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 || '')
|
||||
|
||||
|
||||
+5
-6
@@ -1,13 +1,12 @@
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
import type { App } from 'vue'
|
||||
import type { SSRContext } from '@vue/server-renderer'
|
||||
|
||||
export async function renderToStringWithMeta (app: App, ctx: SSRContext = {}): Promise<[string, SSRContext]> {
|
||||
const html = await renderToString(app, ctx)
|
||||
|
||||
export async function renderMetaToString (app: App, ctx: SSRContext = {}): Promise<SSRContext> {
|
||||
// TODO: better way of determining whether meta was rendered with the component or not
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -21,5 +20,5 @@ export async function renderToStringWithMeta (app: App, ctx: SSRContext = {}): P
|
||||
}
|
||||
}
|
||||
|
||||
return [html, ctx]
|
||||
return ctx
|
||||
}
|
||||
|
||||
+4
-5
@@ -42,7 +42,7 @@ export interface MetaActive {
|
||||
* Context passed to the meta resolvers
|
||||
*/
|
||||
export type MetaResolveContext = ResolveContext & {
|
||||
vm: ComponentInternalInstance | undefined
|
||||
vm?: ComponentInternalInstance
|
||||
}
|
||||
|
||||
export type MetaResolveSetup = (context: MetaResolveContext) => void
|
||||
@@ -52,9 +52,7 @@ export type MetaResolver = {
|
||||
resolve: ResolveMethod
|
||||
}
|
||||
|
||||
export type MetaResolverSetup = Modify<MetaResolver, {
|
||||
setup: MetaResolveSetup
|
||||
}>
|
||||
export type MetaResolverSetup = Required<MetaResolver>
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -74,8 +72,9 @@ export interface MetaGuards {
|
||||
* @internal
|
||||
*/
|
||||
export interface MetaRenderContext {
|
||||
slots?: Slots
|
||||
isSSR: boolean
|
||||
metainfo: MetaActive
|
||||
slots?: Slots
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { inject, getCurrentInstance, ComponentInternalInstance, isProxy, watch } from 'vue'
|
||||
import type { MetaManager } from './manager'
|
||||
import { metaActiveKey } from './symbols'
|
||||
import type { MetaActive, MetaSource, MetaProxy } from './types'
|
||||
import { applyDifference } from './utils/diff'
|
||||
import type { MetaManager } from './manager'
|
||||
import type { MetaActive, MetaSource, MetaProxy } from './types'
|
||||
|
||||
export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager | undefined {
|
||||
if (!vm) {
|
||||
|
||||
@@ -3,7 +3,7 @@ export const pluck = <T extends Record<string, any>>(collection: T[], key: strin
|
||||
const plucked: T[] = []
|
||||
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key])
|
||||
|
||||
if (callback) {
|
||||
|
||||
Reference in New Issue
Block a user