diff --git a/src/Metainfo.ts b/src/Metainfo.ts index 19cdfca..95221a8 100644 --- a/src/Metainfo.ts +++ b/src/Metainfo.ts @@ -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', diff --git a/src/config/helpers.ts b/src/config/helpers.ts index d5eaf74..81aceff 100644 --- a/src/config/helpers.ts +++ b/src/config/helpers.ts @@ -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, diff --git a/src/manager.ts b/src/manager.ts index 53c1587..22a3562 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -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): void { +export function addVnode (isSSR: boolean, teleports: MetaTeleports, to: string, vnodes: VNode | Array): void { const nodes = (isArray(vnodes) ? vnodes : [vnodes]) as Array - 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 - resolver?: MetaResolverSetup + resolver?: MetaResolver ssrCleanedUp: boolean = false - constructor (config: MetaConfig, target: MergedObjectBuilder, resolver: MetaResolver | ResolveMethod) { + constructor (isSSR: boolean, config: MetaConfig, target: MergedObjectBuilder, 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(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) }) } } diff --git a/src/render.ts b/src/render.ts index 1740ec6..abc338b 100644 --- a/src/render.ts +++ b/src/render.ts @@ -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 || '') diff --git a/src/ssr.ts b/src/ssr.ts index 9fa5280..5c51ff3 100644 --- a/src/ssr.ts +++ b/src/ssr.ts @@ -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 { // 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 } diff --git a/src/types/index.ts b/src/types/index.ts index 4880ce5..27eb4cb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -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 +export type MetaResolverSetup = Required /** * @internal @@ -74,8 +72,9 @@ export interface MetaGuards { * @internal */ export interface MetaRenderContext { - slots?: Slots + isSSR: boolean metainfo: MetaActive + slots?: Slots } /** diff --git a/src/useApi.ts b/src/useApi.ts index 98e1e0a..27ed553 100644 --- a/src/useApi.ts +++ b/src/useApi.ts @@ -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) { diff --git a/src/utils/collection.ts b/src/utils/collection.ts index 5d65dd6..dfca8b7 100644 --- a/src/utils/collection.ts +++ b/src/utils/collection.ts @@ -3,7 +3,7 @@ export const pluck = >(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) {