/** * Optimized Mermaid utility with lazy loading, caching, and performance improvements */ import mermaid from "mermaid"; // Mermaid configuration const mermaidConfig = { startOnLoad: false, securityLevel: "loose" as const, flowchart: { useMaxWidth: true, htmlLabels: true, }, }; // Cache for rendered diagrams to avoid re-rendering on theme changes const diagramCache = new Map(); // Intersection Observer for lazy loading let intersectionObserver: IntersectionObserver | null = null; // Initialize Mermaid with current theme function initializeMermaid(): void { const isDark = document.documentElement.classList.contains("dark"); const theme = isDark ? "dark" : "default"; mermaid.initialize({ ...mermaidConfig, theme: theme, }); } // Create intersection observer for lazy loading function createIntersectionObserver(): IntersectionObserver { if (intersectionObserver) { return intersectionObserver; } intersectionObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const diagram = entry.target as HTMLElement; renderDiagram(diagram); intersectionObserver?.unobserve(diagram); } }); }, { rootMargin: "50px", // Start loading 50px before entering viewport threshold: 0.1, } ); return intersectionObserver; } // Render a single diagram with caching async function renderDiagram(diagram: HTMLElement): Promise { const source = diagram.getAttribute("data-mermaid-source"); if (!source) return; const decodedSource = decodeURIComponent(source); const currentTheme = document.documentElement.classList.contains("dark") ? "dark" : "default"; const cacheKey = `${decodedSource}-${currentTheme}`; // Check cache first if (diagramCache.has(cacheKey)) { const cached = diagramCache.get(cacheKey)!; const contentDiv = diagram.querySelector(".mermaid-diagram-content"); if (contentDiv) { contentDiv.innerHTML = cached.svg; } return; } // Show loading state const contentDiv = diagram.querySelector(".mermaid-diagram-content"); if (contentDiv) { contentDiv.innerHTML = `
Loading diagram...
`; } try { // Initialize Mermaid with current theme initializeMermaid(); const diagramId = `mermaid-${Date.now()}-${Math.random() .toString(36) .substr(2, 9)}`; const { svg } = await mermaid.render(diagramId, decodedSource); // Cache the rendered diagram diagramCache.set(cacheKey, { svg, theme: currentTheme }); // Insert the rendered SVG if (contentDiv) { contentDiv.innerHTML = svg; } } catch (error) { if (contentDiv) { contentDiv.innerHTML = `

Diagram rendering failed

Show source
${decodedSource}
`; } } } // Render all diagrams with lazy loading async function renderAllDiagrams(): Promise { const diagrams = document.querySelectorAll( ".mermaid-diagram[data-mermaid-source]" ); if (diagrams.length === 0) { return; } // Initialize Mermaid initializeMermaid(); // Set up intersection observer for lazy loading const observer = createIntersectionObserver(); diagrams.forEach((diagram) => { observer.observe(diagram); }); } // Handle theme changes with caching function handleThemeChange(): void { const diagrams = document.querySelectorAll( ".mermaid-diagram[data-mermaid-source]" ); const currentTheme = document.documentElement.classList.contains("dark") ? "dark" : "default"; diagrams.forEach((diagram) => { const source = diagram.getAttribute("data-mermaid-source"); if (!source) return; const decodedSource = decodeURIComponent(source); const cacheKey = `${decodedSource}-${currentTheme}`; // Check if we have a cached version for this theme if (diagramCache.has(cacheKey)) { const cached = diagramCache.get(cacheKey)!; const contentDiv = diagram.querySelector(".mermaid-diagram-content"); if (contentDiv) { contentDiv.innerHTML = cached.svg; } } else { // Re-render if no cache available renderDiagram(diagram as HTMLElement); } }); } // Clear cache (useful for development) function clearCache(): void { diagramCache.clear(); } // Initialize Mermaid when DOM is ready function initializeMermaidOnLoad(): void { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", renderAllDiagrams); } else { renderAllDiagrams(); } } // Export functions for global access export { initializeMermaid, renderAllDiagrams, handleThemeChange, initializeMermaidOnLoad, clearCache, }; // Make functions globally available for Swup compatibility if (typeof window !== "undefined") { (window as any).initializeMermaid = renderAllDiagrams; (window as any).handleMermaidThemeChange = handleThemeChange; (window as any).clearMermaidCache = clearCache; }