Mermaid.js Deep Dive: Comment il fonctionne réellement et comment l'étendre
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:
- Ouvrir l'outil de diagramme
- Créer un diagramme
- Exporter en tant que PNG
- Insérer dans les documents
- Besoin de mettre à jour? Trouver le fichier sourceM SK1 éditer , réMSC3 exporter, remplacer l'imageMST5
The Mermaid way:
- Rédiger un diagramme dans le texte
- 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
- Créer le SVG
- Préserver les dimensions
- Remplacer les transformations
- 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-processedne 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.
- Rendement du pipeline: Texte → Lecteur → Parseur M→ AST R→ Rendreur P→ SVG
- Points d'extension: ConfigMSC1 themes, postM SK3render enhancements
- Stocker les données originales: La mermaid ne le fait pas pour vous
- Nettoyage des ressources: Les fuites de mémoire sont réelles
- Délégation des événements: Meilleure performance
- Sources de thème multiples: Les sites traitent les thèmes différemment
- 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
- Ajouter mermaid.js avec htmx
- Changement des thèmes pour Mermaid
- Améliorer les diagrammes de mermaid avec Pan/Zoom et Export
- Publier les améliorations Mermaid en tant que paquet npm
Ressources
- Mermaid.js Documentation
- Mermaid GitHub
- svg-panM SK1zoom
- html-à-image
- My Mermaid Enhancements Package
Essayez les commandes des diagrammes ci-dessus! M SK1