fix: only call scripts one per page

This commit is contained in:
saberzero1 2026-02-27 10:54:22 +01:00
parent 912642590c
commit 34480c686d
No known key found for this signature in database
2 changed files with 38 additions and 9 deletions

View File

@ -18,6 +18,7 @@ export interface RegisteredComponent {
class ComponentRegistry { class ComponentRegistry {
private components = new Map<string, RegisteredComponent>() private components = new Map<string, RegisteredComponent>()
private instanceCache = new Map<string, QuartzComponent>()
register( register(
name: string, name: string,
@ -40,6 +41,30 @@ class ComponentRegistry {
return new Map(this.components) return new Map(this.components)
} }
/**
* Instantiate a component constructor with options, returning a cached instance
* if the same constructor was already called with equivalent options.
* This prevents duplicate afterDOMLoaded scripts when the same component
* appears in multiple page-type layouts.
*/
instantiate(constructor: QuartzComponentConstructor, options?: unknown): QuartzComponent {
const optsKey = options !== undefined ? JSON.stringify(options) : ""
// Use constructor identity + serialized options as cache key
// We store constructor name as a hint but rely on a unique id for identity
const ctorId =
(constructor as unknown as { __cacheId?: string }).__cacheId ??
((constructor as unknown as { __cacheId: string }).__cacheId =
`ctor_${this.instanceCache.size}`)
const cacheKey = `${ctorId}:${optsKey}`
const cached = this.instanceCache.get(cacheKey)
if (cached) return cached
const instance = constructor(options)
this.instanceCache.set(cacheKey, instance)
return instance
}
getAllComponents(): QuartzComponent[] { getAllComponents(): QuartzComponent[] {
// Deduplicate by component reference (same constructor may be registered under multiple keys) // Deduplicate by component reference (same constructor may be registered under multiple keys)
const seen = new Set<QuartzComponent | QuartzComponentConstructor>() const seen = new Set<QuartzComponent | QuartzComponentConstructor>()
@ -50,7 +75,7 @@ class ComponentRegistry {
try { try {
let instance: QuartzComponent let instance: QuartzComponent
if (typeof r.component === "function") { if (typeof r.component === "function") {
instance = (r.component as QuartzComponentConstructor)(undefined) instance = this.instantiate(r.component as QuartzComponentConstructor, undefined)
} else { } else {
instance = r.component as QuartzComponent instance = r.component as QuartzComponent
} }

View File

@ -3,7 +3,7 @@ import path from "path"
import YAML from "yaml" import YAML from "yaml"
import { styleText } from "util" import { styleText } from "util"
import { QuartzConfig, GlobalConfiguration, FullPageLayout } from "../../cfg" import { QuartzConfig, GlobalConfiguration, FullPageLayout } from "../../cfg"
import { QuartzComponent } from "../../components/types" import { QuartzComponent, QuartzComponentConstructor } from "../../components/types"
import { PluginTypes } from "../types" import { PluginTypes } from "../types"
import { import {
PluginManifest, PluginManifest,
@ -561,11 +561,12 @@ export async function loadQuartzLayout(layoutOverrides?: {
const footerReg = componentRegistry.get("footer") ?? componentRegistry.get("Footer") const footerReg = componentRegistry.get("footer") ?? componentRegistry.get("Footer")
if (footerReg) { if (footerReg) {
if (typeof footerReg.component === "function" && !("displayName" in footerReg.component)) { if (typeof footerReg.component === "function" && !("displayName" in footerReg.component)) {
// It's a constructor, instantiate with options // It's a constructor — use registry cache for consistent instances
const opts = { ...footerEntry.options } const opts = { ...footerEntry.options }
footer = (footerReg.component as Function)( footer = componentRegistry.instantiate(
footerReg.component as QuartzComponentConstructor,
Object.keys(opts).length > 0 ? opts : undefined, Object.keys(opts).length > 0 ? opts : undefined,
) as QuartzComponent )
} else { } else {
footer = footerReg.component as QuartzComponent footer = footerReg.component as QuartzComponent
} }
@ -648,11 +649,14 @@ function buildLayoutForEntries(
let component: QuartzComponent let component: QuartzComponent
if (typeof reg.component === "function" && !("displayName" in reg.component)) { if (typeof reg.component === "function" && !("displayName" in reg.component)) {
// It's a constructor, instantiate with options // It's a constructor — use registry cache to avoid duplicate instances
// (and duplicate afterDOMLoaded scripts) across page-type layouts
const opts = { ...entry.options } const opts = { ...entry.options }
component = (reg.component as Function)( const optsArg = Object.keys(opts).length > 0 ? opts : undefined
Object.keys(opts).length > 0 ? opts : undefined, component = componentRegistry.instantiate(
) as QuartzComponent reg.component as QuartzComponentConstructor,
optsArg,
)
} else { } else {
component = reg.component as QuartzComponent component = reg.component as QuartzComponent
} }