Back to "Mermaid.js Deep Dive: Comment il fonctionne réellement et comment l'étendre"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Diagrams JavaScript Mermaid SVG

Mermaid.js Deep Dive: Comment il fonctionne réellement et comment l'étendre

Sunday, 09 November 2025

Introduction

NOTE: Il s'agit d'une partie de mes expériences avec l'AI / une façon d'épargner $1000 Code Calude Crédits WebM SK3 J'ai alimenté cet article par un BUNCH de documentsMSC5 ma compréhensionMST6 des questions que j'ai eues pour générer cet articleMst7 C'est fun et remplit une lacune que je n'ai jamais vu remplie ailleursM st10

Ce post repose sur des articles précédents: Si vous n’avez pas encore vérifié Ajouter mermaid.js avec htmx, Changement des thèmes pour Mermaid, et Améliorer les diagrammes de mermaid avec Pan/Zoom et Export. Cette profonde plongée explique les aspects internes de ces mises en œuvres.

Mermaid.js est vraiment brillantM SK1 Rédigez un texte simple , obtenez des diagrammes magnifiques M SK3 Il ne faut plus fumer avec Visio ou dessiner

Mais je voulais savoir comment il fonctionne en fait sous le cap. Comment cela se fait?

graph LR
    A[Text] --> B[Magic?]
    B --> C[Beautiful Diagram]

...devenez un SVG réel?et plus important encoreM SK2 comment vous pouvez l'affecter pour ajouter des fonctionnalités comme le pan @mostlylucid/mermaidM SK2améliorations)?

Après beaucoup de recherches sur Mermaid, le code source de ' et la mise en place d’extensions réelles, , ici,', c’est tout ce que j’ai appris sur la façon dont Mermaid fonctionne à l’interne et comment l’étendre correctement.

Qu'est-ce que Mermaid.js?

Mermaid transforme les définitions de texte en diagrammes. Pensez "Marquage des diagrammes

La vieille façon:

  1. Ouvrir l'outil de diagramme
  2. Créer un diagramme
  3. Exporter en tant que PNG
  4. Insérer dans les documents
  5. Besoin de mettre à jour? Trouver le fichier sourceM SK1 éditer , réMSC3 exporter, remplacer l'imageMST5

The Mermaid way:

  1. Rédiger un diagramme dans le texte
  2. Terminé

La mise à jour? Juste édition du texte. Contrôle de versionM SK2 Il 's just textMSC4 Travaille en marquage-à-têteMska5 OuiM Ska6

Types de diagrammes

Mermaid appuie un nombre ridicule de types de diagrammes:

graph LR
    A[Flowcharts] --> B[Sequence Diagrams]
    B --> C[Class Diagrams]
    C --> D[State Diagrams]
    D --> E[ER Diagrams]
    E --> F[Gantt Charts]
    F --> G[Pie Charts]
    G --> H[Git Graphs]
    H --> I[User Journeys]
    I --> J[And many more...]

Voir les docs de la Mermaid pour la liste complète.

Comment fonctionne la mermaid en fait: Le pipeline

Voici ce qui se passe lorsque la Mermaid donne un diagramme.

graph TD
    A[Text Definition] --> B[Lexer/Tokenizer]
    B --> C[Parser]
    C --> D[AST Built]
    D --> E[Diagram Type Detected]
    E --> F[Type-Specific Renderer]
    F --> G[SVG Generated]
    G --> H[Inserted into DOM]
    H --> I[Your Enhancements Run]

    style A stroke:#059669,stroke-width:3px,color:#10b981
    style D stroke:#2563eb,stroke-width:3px,color:#3b82f6
    style G stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
    style I stroke:#d97706,stroke-width:3px,color:#f59e0b

Let's break down each step.

Étape 1: Définition du texte

Tout débute par le texte. Vous écrivez des diagrammes dans Mermaid 's DSL M SK2domain-language spécifiqueMSC4

// Flowchart
const diagram = `
graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]
    B -->|No| D[Debug time]
`;

Étape 2: Analyse lexique

Le lexer fractionne le texte en tokens. Par exemple, cette ligneM SK2

A[Start] --> B{Decision}

Devient des tokens comme:

[
    { type: 'NODE_ID', value: 'A' },
    { type: 'NODE_TEXT', value: 'Start' },
    { type: 'ARROW', value: '-->' },
    { type: 'NODE_ID', value: 'B' },
    { type: 'NODE_TEXT', value: 'Decision' },
    { type: 'NODE_SHAPE', value: 'diamond' }  // from { }
]

Étape 3: Parsage et génération d'AST

Le parteur consomme des tokens et construit une branche de syntaxe abstraite (AST):

// Simplified AST structure
{
    type: 'flowchart',
    direction: 'TD',
    nodes: [
        { id: 'A', text: 'Start', shape: 'rect' },
        { id: 'B', text: 'Decision', shape: 'diamond' }
    ],
    edges: [
        { from: 'A', to: 'B', type: 'arrow' }
    ]
}

Mermaid utilise différents parseurs pour chaque type de diagramme. Ces fichiers sont souvent générés à partir des fichiers de grammaire en utilisant Jison (comme YaccM SK1Bison pour JavaScript).

Étape 4: Détection du diagramme

Mermaid détecte le type de diagramme de la première ligne:

// Simplified detection logic
if (text.match(/^\s*graph/)) return 'flowchart';
if (text.match(/^\s*sequenceDiagram/)) return 'sequence';
if (text.match(/^\s*classDiagram/)) return 'class';
// ... etc

Étape 5: Type - Rendement spécifique

Chaque type de diagramme a son propre renderer. Le rendereur prend l'AST et génère des éléments SVG.

Pour les cartes de débit, Mermaid utilise le Dagre library for graph layout. For othersM SK1 it uses custom algorithms or libraries like Cytoscape.

// Simplified flowchart renderer
export const draw = function (text, id, version, diagObj) {
    const graph = diagObj.db;  // The AST
    const svg = d3.select(`#${id}`);

    // Render nodes
    graph.getVertices().forEach(vertex => {
        drawNode(svg, vertex);
    });

    // Render edges
    graph.getEdges().forEach(edge => {
        drawEdge(svg, edge);
    });

    // Apply layout algorithm
    dagre.layout(graph);
};

Étape 6: Production de SVG

Le rédacteur produit un marquage SVG:

<svg xmlns="http://www.w3.org/2000/svg">
    <g class="node">
        <rect x="0" y="0" width="100" height="50"/>
        <text x="50" y="25">Start</text>
    </g>
    <g class="edge">
        <path d="M 100 25 L 200 25" stroke="#333"/>
    </g>
</svg>

Étape 7: Insertion de DOM

Mermaid trouve tout .mermaid les éléments et les remplace par SVG: rendu

// From mermaid.ts
export const init = async function (config, nodes) {
    const nodesToProcess = nodes || document.querySelectorAll('.mermaid');

    for (const node of nodesToProcess) {
        const id = `mermaid-${Date.now()}-${Math.random()}`;
        const txt = node.textContent;

        const { svg } = await render(id, txt);
        node.innerHTML = svg;
    }
};

Étape 8: Poste-Procédure (où vous arrivezM SK3

Après que Mermaid insère le SVG, vous pouvez l'enrichir. C'est là où tous mes améliorations s'attachent dans :

  • Fonctionnalité Pan/zoom
  • boutons de contrôle
  • Capacité d'exportation
  • Changement de thème

Plus sur ce sujet ci-dessous.

Extendant Mermaid: Les points d'extension

Maintenant que nous savons comment fonctionne la Mermaid, laissez-nous examiner comment l’étendre.

1. Configuration

L'extension la plus élémentaire est configuration:

import mermaid from 'mermaid';

mermaid.initialize({
    startOnLoad: true,
    theme: 'dark',
    securityLevel: 'loose',
    flowchart: {
        curve: 'basis',
        padding: 15
    }
});

2. Customisation du thème

Je l'ai traité de manière approfondie dans Changement des thèmes pour Mermaid, mais ici 'est la mise en œuvre clé:

Le problème du changement de thème

Mermaid doit être initialisée avec un thème, et vous pouvezM SK1t le changer après. Mais si vous voulez remettre les diagrammes avec un nouveau thème , vous avez besoin de la source originale du diagrammeMSC5qui Mermaid doesn't ne stocke pas dans le DOM.

La solution

Entretenir le contenu original avant de rendre , puis restaurer et remettre -render lors du changement des thèmes:

// From my theme-switcher implementation
const originalData = new Map();

// Save original content before first render
const saveOriginalData = async () => {
    const elements = document.querySelectorAll('.mermaid');
    elements.forEach(element => {
        const id = element.id || `mermaid-${Date.now()}`;
        element.id = id;

        // Store the original diagram source
        if (!originalData.has(id)) {
            originalData.set(id, element.textContent?.trim());
        }
    });
};

// When theme changes, restore and re-render
const loadMermaid = async (theme) => {
    mermaid.initialize({
        startOnLoad: false,
        theme: theme
    });

    const elements = document.querySelectorAll('.mermaid');
    for (const element of elements) {
        const source = originalData.get(element.id);
        if (source) {
            element.innerHTML = '';  // Clear
            element.removeAttribute('data-processed');

            const { svg } = await mermaid.render(
                `mermaid-svg-${element.id}`,
                source
            );
            element.innerHTML = svg;
        }
    }
};

Méthodes de détection multiples du thème (les sites traitent les thèmes différemment):

function detectTheme() {
    // Check various sources
    if (typeof window.__themeState !== 'undefined') {
        return window.__themeState;
    }
    if (localStorage.theme) {
        return localStorage.theme;
    }
    if (document.documentElement.classList.contains('dark')) {
        return 'dark';
    }
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        return 'dark';
    }
    return 'light';
}

Voir le code de l'interrupteur complet du thème pour plus de détails.

3. Post-Render Enhancements

C'est là que se produit la véritable magie. Après les renders de Mermaid, vous pouvez ajouter des fonctionnalités interactivesM SK2

Je l'ai traité de manière approfondie dans Améliorer les diagrammes de mermaid avec Pan/Zoom et Export, Je soulignerai ici les techniques clés de .

Établissement de diagrammes

Créer un contenant d'emballage pour les commandes:

function wrapDiagram(element) {
    if (element.closest('.mermaid-wrapper')) {
        return element.closest('.mermaid-wrapper');
    }

    const wrapper = document.createElement('div');
    wrapper.className = 'mermaid-wrapper';
    wrapper.id = `wrapper-${element.id}`;

    element.parentNode.insertBefore(wrapper, element);
    wrapper.appendChild(element);

    return wrapper;
}

Ajouter Pan/Zoom

Utilisation svg-panM SK1zoom:

import svgPanZoom from 'svg-pan-zoom';

const panZoomInstances = new Map();

function initPanZoom(svgElement, diagramId) {
    // Clean up existing instance
    if (panZoomInstances.has(diagramId)) {
        panZoomInstances.get(diagramId).destroy();
        panZoomInstances.delete(diagramId);
    }

    const instance = svgPanZoom(svgElement, {
        zoomEnabled: true,
        controlIconsEnabled: false,
        fit: true,
        center: true,
        minZoom: 0.1,
        maxZoom: 10
    });

    panZoomInstances.set(diagramId, instance);
    return instance;
}

boutons de contrôle

Créer un panneau de contrôle flottant:

function createControlButtons(container, diagramId) {
    const controlsDiv = document.createElement('div');
    controlsDiv.className = 'mermaid-controls';

    const buttons = [
        { icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
        { icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
        { icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
        { icon: 'bx-reset', title: 'Reset', action: 'reset' },
        { icon: 'bx-move', title: 'Pan', action: 'pan' },
        { icon: 'bx-image', title: 'Export PNG', action: 'exportPng' },
        { icon: 'bx-code-alt', title: 'Export SVG', action: 'exportSvg' }
    ];

    buttons.forEach(btn => {
        const button = document.createElement('button');
        button.className = `mermaid-control-btn bx ${btn.icon}`;
        button.setAttribute('data-action', btn.action);
        button.setAttribute('data-diagram-id', diagramId);
        controlsDiv.appendChild(button);
    });

    container.appendChild(controlsDiv);
}

Délégation de l’événement (Performance!)

Don't attacher les auditeurs à chaque boutonM SK1 Utiliser la délégation des événements:

document.addEventListener('click', (e) => {
    const target = e.target;
    if (!target.classList.contains('mermaid-control-btn')) return;

    const action = target.getAttribute('data-action');
    const diagramId = target.getAttribute('data-diagram-id');
    const panZoom = panZoomInstances.get(diagramId);

    switch (action) {
        case 'zoomIn': panZoom?.zoomIn(); break;
        case 'zoomOut': panZoom?.zoomOut(); break;
        case 'reset': panZoom?.reset(); break;
        // ... etc
    }
});

Fonctionnalité d'exportation

Le défi: Les éléments SVG ont une taille dynamique, pan/transformations de zoomM SK2 et styles héréditaires . Pour exporter correctementMska4 vous devezM Ska5

  1. Créer le SVG
  2. Préserver les dimensions
  3. Remplacer les transformations
  4. Convert à PNG ou SVG

Utilisation html-à-image:

import { toPng, toSvg } from 'html-to-image';

async function exportDiagram(container, format, diagramId) {
    const svgElement = container.querySelector('svg');
    if (!svgElement) return;

    // Clone to avoid modifying original
    const clonedSvg = svgElement.cloneNode(true);

    // Get or calculate viewBox
    let viewBox = clonedSvg.getAttribute('viewBox');
    if (!viewBox) {
        const bbox = svgElement.getBBox();
        viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
        clonedSvg.setAttribute('viewBox', viewBox);
    }

    // Set explicit dimensions
    const [, , width, height] = viewBox.split(' ').map(Number);
    clonedSvg.setAttribute('width', width);
    clonedSvg.setAttribute('height', height);

    // Remove pan-zoom transforms
    clonedSvg.removeAttribute('style');

    // Create off-screen container
    const temp = document.createElement('div');
    temp.style.position = 'absolute';
    temp.style.left = '-9999px';
    temp.appendChild(clonedSvg);
    document.body.appendChild(temp);

    // Export
    const dataUrl = format === 'png'
        ? await toPng(clonedSvg, { pixelRatio: 2 })
        : await toSvg(clonedSvg);

    downloadFile(dataUrl, `diagram-${Date.now()}.${format}`);

    // Cleanup
    document.body.removeChild(temp);
}

Détails critiques:

  • Préserver la boîte de vue - Capture l’ensemble du diagramme , non seulement la partie visible
  • Off-réalisation de l'écran - Éviter d'avoir une incidence sur le diagramme affiché
  • pixelRatio: M SK1 - HighM SK1DPI pour les exportations de PNG crisp

Voir le code d'exportation complet pour plus de détails.

Le paquet d'amélioration complet

J'ai emballé toutes ces améliorations comme suit : @mostlylucid/mermaidM SK2améliorations. Voir Publier les améliorations Mermaid en tant que paquet npm pour plus de détails.

Voici la façon dont il se prête tout ensemble:

graph TB
    A[User Initializes] --> B[init Function]
    B --> C[initMermaid]
    B --> D[enhanceMermaidDiagrams]

    C --> E[Theme Detection]
    C --> F[Event Listeners]
    C --> G[Mermaid Rendering]

    E --> E1[Global State]
    E --> E2[LocalStorage]
    E --> E3[DOM Class]
    E --> E4[OS Preference]

    F --> F1[Custom Events]
    F --> F2[Media Query]

    G --> H[Apply Enhancements]
    D --> H

    H --> I[Wrap Diagrams]
    H --> J[Init Pan/Zoom]
    H --> K[Add Controls]

    I --> L[Interactive Diagram]
    J --> L
    K --> L

    L --> M[User Interactions]
    M --> M1[Zoom In/Out]
    M --> M2[Pan]
    M --> M3[Fullscreen]
    M --> M4[Export PNG/SVG]

    style A stroke:#059669,stroke-width:3px,color:#10b981
    style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
    style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6

Utilisation

npm install @mostlylucid/mermaid-enhancements
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

await init();

Que les diagrammes de la mermaid ont maintenant une valeur de

  • ✅ Pan interactifM SK1zoom
  • ✅ Case lumineuse à écran complet
  • Exportation ✅ PNGM SK1SVG
  • ✅ Changement automatique du thème
  • ✅ Design réactif

Intégration avec HTMX

Comme je l'ai couvert dans Ajouter mermaid.js avec htmx, vous devez renouveler-initialiser Mermaid après les échanges de contenu HTMXM SK2

// On page load
document.addEventListener('DOMContentLoaded', function () {
    mermaid.initialize({ startOnLoad: true });
});

// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
    mermaid.run();
});

Avec le paquet d’améliorations:

import { init, enhanceMermaidDiagrams } from '@mostlylucid/mermaid-enhancements';

// Initial load
await init();

// After HTMX swap
document.body.addEventListener('htmx:afterSwap', async function() {
    await init();  // Re-init Mermaid with current theme
    enhanceMermaidDiagrams();  // Re-apply enhancements
});

Les meilleures pratiques que j’ai apprises

Après avoir construit ces choses et débugé des cas de bord étranges, iciM SK1 ce qui fonctionne:

1. Garder toujours le contenu original

Mermaid ne préserve pas la source originale du diagramme après la traduction. Vous devez l’entreposer vous-mêmeM SK2

const originalData = new Map();

// Before first render
element.setAttribute('data-original-code', element.textContent);
originalData.set(element.id, element.textContent);

// When re-rendering
element.innerHTML = originalData.get(element.id);

2. Nettoyage des instances

Les fuites de mémoire sont réelles. Détruire les instances avant d’en créer de nouvelles

if (panZoomInstances.has(id)) {
    try {
        panZoomInstances.get(id).destroy();
    } catch (e) {
        console.warn('Failed to destroy:', e);
    }
    panZoomInstances.delete(id);
}

3. Utiliser la délégation des événements

Don't attacher les auditeurs aux boutons individuelsM SK1

// ❌ Don't do this
buttons.forEach(btn => {
    btn.addEventListener('click', handler);
});

// ✅ Do this
document.addEventListener('click', (e) => {
    if (e.target.matches('.mermaid-control-btn')) {
        handleClick(e.target);
    }
});

4. Handle Cloudflare Rocket Loader

Loader de fusils retarde l'exécution JavaScript. Attendez les dépendances:

function waitForDependencies(maxAttempts = 50) {
    return new Promise((resolve) => {
        let attempts = 0;

        const check = () => {
            if (window.mermaid && window.htmx && window.Alpine) {
                resolve();
            } else if (attempts >= maxAttempts) {
                resolve();  // Give up
            } else {
                attempts++;
                setTimeout(check, Math.min(50 * Math.pow(1.2, attempts), 500));
            }
        };

        check();
    });
}

Et exclure votre script principal de Rocket Loader:

<script src="main.js" data-cfasync="false"></script>

5. Le moment est tout

Utilisation requestAnimationFrame pour un meilleur calendrier que l'arbitrage arbitraire setTimeout:

// After Mermaid renders
await mermaid.run();

// Wait for paint before enhancing
await new Promise(resolve => {
    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            enhanceMermaidDiagrams();
            resolve();
        });
    });
});

6. Manipulation de SVG de défense

Les SVG peuvent être étranges. Vérifier toujours:

const svgElement = container.querySelector('svg');
if (!svgElement) {
    console.warn('No SVG found');
    return;
}

// Clone before modifying
const cloned = svgElement.cloneNode(true);

// Ensure viewBox exists
let viewBox = cloned.getAttribute('viewBox');
if (!viewBox) {
    const bbox = svgElement.getBBox();
    viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
}

Conseils de débugging

Résultats de la console

Avec l’initialisation appropriée, vous devriez voir

Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams

Examen de la liste de vérification

Après la mise en œuvre des améliorations:

  • Rendre les diagrammes sur charge de page
  • PanM SK1contrôle du travail de la couche
  • Ouverture de l'écran intégral/ferme (XM SK3 cliquer à l'extérieur
  • Exportation de PNG capte le diagramme complet
  • L'exportation de SVG préserve les vecteurs
  • fonctionne après échange de contenu HTMX
  • Changement de thème re-renders correctement
  • Réceptif mobile
  • Styling en mode foncé
  • Accès à la clavier

Questions communes

Graphique non rendue:

  • Vérifier la console du navigateur pour les erreurs
  • Vérifier que la mermaid a été chargée (window.mermaid)
  • Vérifier la syntaxe du diagramme

Pan/zoom ne fonctionne pasM SK1

  • Vérifier l'initialisation de svg-pan-zoom
  • Vérifier si le CSS est en conflit (pointer-events: none)
  • Inspecter la carte d'instance

Exportation capture seulement le coin:

  • Préservation de la boîte à vue manquante
  • La transformation n'est pas supprimée
  • Vérifier la logique de clonage

Thème ne change pas:

  • Données originales non conservées
  • data-processed ne pas réétablir
  • Les auditeurs de l'événement ne sont pas enregistrés

Considérations relatives au rendement

Initialisation lente

Donner des améliorations de charge jusqu’à ce que cela soit nécessaire:

let enhancementsLoaded = false;

async function loadEnhancements() {
    if (enhancementsLoaded) return;

    const { enhanceMermaidDiagrams } = await import('./enhancements.js');
    enhanceMermaidDiagrams();
    enhancementsLoaded = true;
}

// Load on first interaction
document.addEventListener('click', (e) => {
    if (e.target.closest('.mermaid')) {
        loadEnhancements();
    }
}, { once: true });

Observateur de l'intersection

Rendre les diagrammes seulement lorsque visibles:

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            renderDiagram(entry.target);
            observer.unobserve(entry.target);
        }
    });
}, { rootMargin: '100px' });

document.querySelectorAll('.mermaid').forEach(el => {
    observer.observe(el);
});

Debounce Re-renders

Sur redimensionnement ou changement de thème:

let timeout;
window.addEventListener('resize', () => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
        panZoomInstances.forEach(instance => {
            instance.resize();
            instance.fit();
        });
    }, 250);
});

En conclusion

Mermaid.js est fantastique à l’extérieur de la boîte, mais comprendre comment il fonctionne internement vous permet d’élaborer quelques améliorations vraiment cool.

  1. Rendement du pipeline: Texte → Lecteur → Parseur M→ AST R→ Rendreur P→ SVG
  2. Points d'extension: ConfigMSC1 themes, postM SK3render enhancements
  3. Stocker les données originales: La mermaid ne le fait pas pour vous
  4. Nettoyage des ressources: Les fuites de mémoire sont réelles
  5. Délégation des événements: Meilleure performance
  6. Sources de thème multiples: Les sites traitent les thèmes différemment
  7. Questions liées au calendrier: Usage requestAnimationFrame

Toutes les techniques que j’ai exposées ici sont utilisées dans la production sur ce site et emballées en @mostlylucid/mermaidM SK2améliorations. La source complète est disponible à Web plus lumineux/plus lumineux.

Postes connexes

Ressources

Essayez les commandes des diagrammes ci-dessus! M SK1

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.