|
1 | 1 | import type { CSSProperties, ReactNode } from "react"; |
2 | 2 | import React, { useContext, useEffect, useRef, useState } from "react"; |
3 | | -import mermaid from "mermaid"; |
4 | 3 | import { StreamingContext } from "./StreamingContext"; |
5 | 4 | import { usePersistedState } from "@/hooks/usePersistedState"; |
6 | 5 |
|
7 | 6 | const MIN_HEIGHT = 300; |
8 | 7 | const MAX_HEIGHT = 1200; |
9 | 8 |
|
10 | | -// Initialize mermaid |
11 | | -mermaid.initialize({ |
12 | | - startOnLoad: false, |
13 | | - theme: "dark", |
14 | | - layout: "elk", |
15 | | - securityLevel: "loose", |
16 | | - fontFamily: "var(--font-monospace)", |
17 | | - darkMode: true, |
18 | | - elk: { |
19 | | - nodePlacementStrategy: "LINEAR_SEGMENTS", |
20 | | - mergeEdges: true, |
21 | | - }, |
22 | | - wrap: true, |
23 | | - markdownAutoWrap: true, |
24 | | - flowchart: { |
25 | | - nodeSpacing: 60, |
26 | | - curve: "linear", |
27 | | - defaultRenderer: "elk", |
28 | | - }, |
29 | | -}); |
| 9 | +// Lazy-loaded mermaid module to reduce startup time |
| 10 | +// Mermaid is 64MB and loads heavy dependencies (cytoscape, elk, langium) |
| 11 | +// Only load when first diagram is actually rendered |
| 12 | +// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- Dynamic import type is intentional for lazy loading |
| 13 | +type MermaidModule = typeof import("mermaid").default; |
| 14 | +let mermaidInstance: MermaidModule | null = null; |
| 15 | +let mermaidLoadPromise: Promise<MermaidModule> | null = null; |
| 16 | + |
| 17 | +async function loadMermaid(): Promise<MermaidModule> { |
| 18 | + // Return cached instance if already loaded |
| 19 | + if (mermaidInstance) return mermaidInstance; |
| 20 | + |
| 21 | + // Return in-flight promise if already loading |
| 22 | + if (mermaidLoadPromise) return mermaidLoadPromise; |
| 23 | + |
| 24 | + // Start loading mermaid |
| 25 | + mermaidLoadPromise = (async () => { |
| 26 | + /* eslint-disable no-restricted-syntax */ |
| 27 | + const mermaidModule = await import("mermaid"); |
| 28 | + /* eslint-enable no-restricted-syntax */ |
| 29 | + |
| 30 | + const mermaid = mermaidModule.default; |
| 31 | + |
| 32 | + // Initialize mermaid after loading |
| 33 | + mermaid.initialize({ |
| 34 | + startOnLoad: false, |
| 35 | + theme: "dark", |
| 36 | + layout: "elk", |
| 37 | + securityLevel: "loose", |
| 38 | + fontFamily: "var(--font-monospace)", |
| 39 | + darkMode: true, |
| 40 | + elk: { |
| 41 | + nodePlacementStrategy: "LINEAR_SEGMENTS", |
| 42 | + mergeEdges: true, |
| 43 | + }, |
| 44 | + wrap: true, |
| 45 | + markdownAutoWrap: true, |
| 46 | + flowchart: { |
| 47 | + nodeSpacing: 60, |
| 48 | + curve: "linear", |
| 49 | + defaultRenderer: "elk", |
| 50 | + }, |
| 51 | + }); |
| 52 | + |
| 53 | + mermaidInstance = mermaid; |
| 54 | + return mermaid; |
| 55 | + })(); |
| 56 | + |
| 57 | + return mermaidLoadPromise; |
| 58 | +} |
30 | 59 |
|
31 | 60 | // Common button styles |
32 | 61 | const getButtonStyle = (disabled = false): CSSProperties => ({ |
@@ -137,6 +166,8 @@ export const Mermaid: React.FC<{ chart: string }> = ({ chart }) => { |
137 | 166 | const renderDiagram = async () => { |
138 | 167 | try { |
139 | 168 | setError(null); |
| 169 | + // Load mermaid on-demand when first diagram is rendered |
| 170 | + const mermaid = await loadMermaid(); |
140 | 171 | const id = `mermaid-${Math.random().toString(36).substr(2, 9)}`; |
141 | 172 | const { svg: renderedSvg } = await mermaid.render(id, chart); |
142 | 173 | setSvg(renderedSvg); |
|
0 commit comments