Preise

Architektur im Überblick

Quelldateien

Diese Seite wurde aus den folgenden Quelldateien erstellt:

Mermaid ist eine JavaScript-basierte Diagrammbibliothek, die Text-zu-Diagramm-Konvertierung durch eine modulare, erweiterbare Architektur ermöglicht. Das System transformiert deklarative Textdefinitionen in SVG-Visualisierungen durch eine mehrstufige Pipeline: Text-Parsing, AST-Generierung, semantische Analyse, Layout-Berechnung und SVG-Rendering.

Systemarchitektur Überblick

Die Architektur von Mermaid folgt einem Plugin-basierten Design, bei dem jeder Diagrammtyp als eigenständiges Modul registriert wird. Das System besteht aus vier primären Subsystemen: der Diagram-Orchestrierung, dem Parser-System basierend auf Langium, der Rendering-Pipeline mit austauschbaren Layout-Algorithmen und der Konfigurationsverwaltung.

正在加载图表渲染器...

Architektur-Highlights:

Diagramm-Lebenszyklus und Initialisierung

Der Lebenszyklus eines Mermaid-Diagramms beginnt mit der statischen Factory-Methode Diagram.fromText(), die den gesamten Initialisierungsprozess orchestriert. Diese Methode implementiert ein asynchrones Ladepattern, das bei Bedarf fehlende Diagrammtypen nachlädt.

Kernkomponenten der Diagram-Instanziierung

Die Diagram-Klasse in packages/mermaid/src/Diagram.ts:16-55 kapselt vier essenzielle Komponenten:

KomponenteTypVerantwortlichkeit
typestringIdentifiziert den Diagrammtyp (z.B. 'flowchart', 'sequence')
textstringDer ursprüngliche Diagramm-Text nach Entity-Kodierung
dbDiagramDefinition['db']Semantischer Speicher für extrahierte Daten
parserDiagramDefinition['parser']Parser-Instanz (JISON oder Langium-basiert)
rendererDiagramDefinition['renderer']SVG-Generierungslogik

Der Initialisierungsablauf folgt einer strikten Sequenz:

  1. Typ-Erkennung: detectType() analysiert den Text und bestimmt den Diagrammtyp (packages/mermaid/src/Diagram.ts:19)
  2. Entity-Kodierung: Sonderzeichen werden für sichere Verarbeitung kodiert (packages/mermaid/src/Diagram.ts:20)
  3. Verfügbarkeitsprüfung: Das System prüft, ob der Diagrammtyp bereits registriert ist (packages/mermaid/src/Diagram.ts:22-32)
  4. Lazy Loading: Bei Bedarf wird der Diagram-Loader asynchron aufgerufen
  5. Parser-Initialisierung: Für JISON-Parser wird die Datenbank als yy-Objekt injiziert (packages/mermaid/src/Diagram.ts:34-37)
  6. Datenbank-Reset: db.clear() bereinigt vorherige Parse-Zustände
  7. Parse-Ausführung: Der Text wird geparst und die Datenbank befüllt

Fehlerbehandlung und Randbedingungen

Die Basis-Diagrammregistrierung in packages/mermaid/src/diagram-api/diagram-orchestration.ts:38-77 zeigt zwei spezielle Fehlerbehandlungsmuster:

Error-Diagram: Ein Fallback-Diagramm für Fehlerzustände, das über einen Detector registriert wird, der auf den Text "error" prüft (packages/mermaid/src/diagram-api/diagram-orchestration.ts:45-47).

YAML-Frontmatter-Schutz: Ein spezielles Diagramm für ----Präfixe, das bei fehlerhafter YAML-Einleitung eine informative Fehlermeldung wirft (packages/mermaid/src/diagram-api/diagram-orchestration.ts:64-70).

typescript
1// Beispiel: YAML-Frontmatter-Fehlerbehandlung
2parser: {
3  parse: () => {
4    throw new Error(
5      'Diagrams beginning with --- are not valid. ' +
6        'If you were trying to use a YAML front-matter, please ensure that ' +
7        "you've correctly opened and closed the YAML front-matter..."
8    );
9  },
10}

Parser-Architektur und AST-Generierung

Mermaid verwendet ein hybrides Parser-System: Legacy-Diagrammtypen nutzen JISON-Parser, während neuere Diagrammtypen auf Langium basieren – einem modernen Framework für domänenspezifische Sprachen in TypeScript.

Langium-basierte Parser-Struktur

Die zentrale Parser-Koordination erfolgt in packages/parser/src/parse.ts:1-119. Das System verwaltet einen Cache von Parser-Instanzen und verwendet dynamische Imports für Lazy Loading:

typescript
1const parsers: Record<string, LangiumParser> = {};
2const initializers = {
3  info: async () => {
4    const { createInfoServices } = await import('./language/info/index.js');
5    const parser = createInfoServices().Info.parser.LangiumParser;
6    parsers.info = parser;
7  },
8  // ... weitere Initialisierer
9};

Unterstützte Diagrammtypen im Langium-Parser (packages/parser/src/parse.ts:17-64):

DiagrammtypService-FactoryAST-Typ
infocreateInfoServicesInfo
packetcreatePacketServicesPacket
piecreatePieServicesPie
architecturecreateArchitectureServicesArchitecture
gitGraphcreateGitGraphServicesGitGraph
radarcreateRadarServicesRadar
treemapcreateTreemapServicesTreemap
treeViewcreateTreeViewServicesTreeView
wardleycreateWardleyServicesWardley

Fehlerbehandlung beim Parsing

Die MermaidParseError-Klasse in packages/parser/src/parse.ts:95-119 differenziert zwischen zwei Fehlertypen:

Lexer-Fehler: Tokenisierungsfehler mit Zeilen-/Spalteninformationen:

typescript
1const lexerErrors: string = result.lexerErrors
2  .map((err) => {
3    const line = err.line !== undefined && !isNaN(err.line) ? err.line : '?';
4    const column = err.column !== undefined && !isNaN(err.column) ? err.column : '?';
5    return `Lexer error on line ${line}, column ${column}: ${err.message}`;
6  })
7  .join('\n');

Parser-Fehler: Syntaxfehler nach erfolgreicher Tokenisierung:

typescript
1const parserErrors: string = result.parserErrors
2  .map((err) => {
3    const line = err.token.startLine !== undefined && !isNaN(err.token.startLine)
4      ? err.token.startLine : '?';
5    // ...
6    return `Parse error on line ${line}, column ${column}: ${err.message}`;
7  })
8  .join('\n');

AST-Typsystem

Die exportierten AST-Typen in packages/parser/src/language/index.ts:1-55 definieren die strukturierte Repräsentation jedes Diagrammtyps. Zusätzlich werden Typ-Prädikate exportiert (z.B. isInfo, isPacket, isPie), die typsichere Narrowing ermöglichen:

typescript
1export {
2  Info, Packet, Pie, Architecture, GitGraph, Radar, Treemap, Wardley,
3  TreeView, TreeNode, Branch, Commit, Merge,
4  isInfo, isPacket, isPie, isArchitecture, isGitGraph, isTreemap, isWardley
5} from './generated/ast.js';

Registrierung und Erkennung von Diagrammtypen

Das Diagram-API bildet das Herzstück der Erweiterbarkeit von Mermaid. Es ermöglicht die dynamische Registrierung neuer Diagrammtypen zur Laufzeit.

Zentrale Registry-Implementierung

Die Registry in packages/mermaid/src/diagram-api/diagramAPI.ts:26-83 verwaltet zwei Maps:

typescript
1const diagrams: Record<string, DiagramDefinition> = {};
2export type Detectors = Record<string, DiagramDetector>;

Die registerDiagram-Funktion (packages/mermaid/src/diagram-api/diagramAPI.ts:44-70) führt mehrere Operationen aus:

  1. Existenzprüfung: Warnt bei Überschreibung existierender Diagramme
  2. Registrierung: Speichert die Diagram-Definition
  3. Detector-Registrierung: Verknüpft optionale Typerkennungs-Funktionen
  4. Style-Injection: Fügt diagrammspezifische CSS-Styles hinzu
  5. Utility-Injection: Injiziert Hilfsfunktionen in das Diagramm
typescript
1diagram.injectUtils?.(
2  log, setLogLevel, getConfig, sanitizeText,
3  setupGraphViewbox, getCommonDb(),
4  () => { /* parseDirective removed - legacy support */ }
5);

Detector-Pattern zur Typerkennung

Das Detector-System in packages/mermaid/src/diagram-api/detectType.ts:66-82 ermöglicht die automatische Erkennung von Diagrammtypen basierend auf Textmustern:

typescript
1export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
2  if (detectors[key]) {
3    log.warn(`Detector with key ${key} already exists. Overwriting.`);
4  }
5  detectors[key] = { detector, loader };
6  log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
7};

Lazy-Loading-Integration: Die registerLazyLoadedDiagrams-Funktion (packages/mermaid/src/diagram-api/detectType.ts:66-70) ermöglicht das Registrieren externer Diagramme mit asynchronen Loadern:

typescript
1export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
2  for (const { id, detector, loader } of diagrams) {
3    addDetector(id, detector, loader);
4  }
5};

Diagram-Definition-Schnittstelle

Eine vollständige DiagramDefinition umfasst (packages/mermaid/src/diagram-api/diagramAPI.ts:44-48):

KomponentePflichtBeschreibung
dbJaDatenbank für semantische Daten
parserJaParser-Instanz mit parse()-Methode
rendererJaRenderer mit draw()-Methode
stylesNeinCSS-Styles für das Diagramm
initNeinInitialisierungsfunktion
injectUtilsNeinCallback für Utility-Injection

Rendering-Pipeline und Layout-Algorithmen

Die Rendering-Pipeline transformiert die geparsten Diagrammdaten in SVG-Elemente. Ein Schlüsseldesign ist die Abstraktion der Layout-Algorithmen, die verschiedene Strategien unterstützt.

Layout-Loader-Registry

Das System in packages/mermaid/src/rendering-util/render.ts:31-91 verwaltet Layout-Algorithmen über eine Registry:

typescript
1const layoutAlgorithms: Record<string, LayoutLoaderDefinition> = {};
2
3export const registerLayoutLoaders = (loaders: LayoutLoaderDefinition[]) => {
4  for (const loader of loaders) {
5    layoutAlgorithms[loader.name] = loader;
6  }
7};

Standard-Layout-Loader (packages/mermaid/src/rendering-util/render.ts:39-56):

AlgorithmusBedingungVerwendung
dagreImmerHierarchische Layouts (Flowcharts, Sequenzdiagramme)
cose-bilkentincludeLargeFeaturesKomplexe Graphen mit Clustering

Render-Funktion und DOM-Integration

Die zentrale render-Funktion (packages/mermaid/src/rendering-util/render.ts:58-77) führt folgende Schritte aus:

  1. Algorithmus-Validierung: Prüft, ob der angeforderte Algorithmus registriert ist
  2. ID-Prefixing: Stellt Eindeutigkeit von DOM-IDs bei mehreren Diagrammen sicher
  3. Loader-Aufruf: Lädt den Layout-Algorithmus asynchron
  4. Delegation: Übergibt die Kontrolle an den algorithmusspezifischen Renderer
typescript
1if (data4Layout.diagramId) {
2  for (const node of data4Layout.nodes) {
3    const originalDomId = node.domId || node.id;
4    node.domId = `${data4Layout.diagramId}-${originalDomId}`;
5  }
6}

Fallback-Mechanismus

Die getRegisteredLayoutAlgorithm-Funktion (packages/mermaid/src/rendering-util/render.ts:82-91) implementiert einen robusten Fallback:

typescript
1export const getRegisteredLayoutAlgorithm = (algorithm = '', { fallback = 'dagre' } = {}) => {
2  if (algorithm in layoutAlgorithms) {
3    return algorithm;
4  }
5  if (fallback in layoutAlgorithms) {
6    log.warn(`Layout algorithm ${algorithm} is not registered. Using ${fallback} as fallback.`);
7    return fallback;
8  }
9  throw new Error(`Both layout algorithms ${algorithm} and ${fallback} are not registered.`);
10};

Dagre-Wrapper-Implementierung

Der Dagre-Wrapper in packages/mermaid/src/dagre-wrapper/index.js:1-126 implementiert das konkrete hierarchische Layout. Die recursiveRender-Funktion zeigt den verschachtelten Rendering-Ansatz:

javascript
1const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
2  const elem = _elem.insert('g').attr('class', 'root');
3  const clusters = elem.insert('g').attr('class', 'clusters');
4  const edgePaths = elem.insert('g').attr('class', 'edgePaths');
5  const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
6  const nodes = elem.insert('g').attr('class', 'nodes');
7  
8  // Asynchrone Node-Insertion
9  await Promise.all(
10    graph.nodes().map(async function (v) {
11      const node = graph.node(v);
12      // ... Node-Rendering-Logik
13    })
14  );
15  
16  // Layout-Berechnung
17  dagreLayout(graph);
18};

Typsystem und Konfiguration

Mermaid verwendet TypeScript für typsichere Entwicklung und bietet umfangreiche Konfigurationsoptionen für verschiedene Diagrammtypen.

Kern-Typdefinitionen

Die Typen in packages/mermaid/src/types.ts:57-102 definieren essentielle Datenstrukturen:

TextDimensionConfig (packages/mermaid/src/types.ts:58-62):

typescript
1export interface TextDimensionConfig {
2  fontSize?: number;
3  fontWeight?: number;
4  fontFamily?: string;
5}

EdgeData (packages/mermaid/src/types.ts:70-86) – Repräsentiert Kanten in Graphdiagrammen:

typescript
1export interface EdgeData {
2  arrowheadStyle?: string;
3  labelpos?: string;
4  labelType?: string;
5  label?: string;
6  classes: string;
7  pattern: string;
8  id: string;
9  arrowhead: string;
10  startLabelRight: string;
11  endLabelLeft: string;
12  arrowTypeStart: string;
13  arrowTypeEnd: string;
14  style: string;
15  labelStyle: string;
16  curve: any;
17}

ParseOptions und ParseResult (packages/mermaid/src/types.ts:90-102):

typescript
1export interface ParseOptions {
2  suppressErrors?: boolean; // Bei true: return false statt throw
3}
4
5export interface ParseResult {
6  diagramType: string; // z.B. 'flowchart', 'sequence'
7  // ... weitere Felder
8}

Diagramm-spezifische Konfiguration

Die Konfigurationstypen in packages/mermaid/src/config.type.ts:674-1124 bieten feingranulare Kontrolle:

TimelineDiagramConfig (packages/mermaid/src/config.type.ts:674-713):

OptionTypBeschreibung
diagramMarginXnumberHorizontaler Rand (positiv)
diagramMarginYnumberVertikaler Rand (positiv)
leftMarginnumberAbstand zwischen Akteuren
widthnumberBreite der Akteur-Boxen
heightnumberHöhe der Akteur-Boxen
paddingnumberInnenabstand
boxMarginnumberRand um Loop-Boxen
messageMarginnumberAbstand zwischen Nachrichten

ArchitectureDiagramConfig (packages/mermaid/src/config.type.ts:1085-1096):

typescript
1export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
2  padding?: number;
3  iconSize?: number;
4  fontSize?: number;
5  randomize?: boolean; // Deterministisch vs. zufällige Initialpositionen
6}

MindmapDiagramConfig (packages/mermaid/src/config.type.ts:1103-1110):

typescript
1export interface MindmapDiagramConfig extends BaseDiagramConfig {
2  padding?: number;
3  maxNodeWidth?: number;
4  layoutAlgorithm?: string; // Algorithmus für Node-Positionierung
5}

Datenfluss und Aufrufsequenz

Der folgende Sequenzdiagramm zeigt den vollständigen Datenfluss von der Texteingabe bis zur SVG-Ausgabe:

正在加载图表渲染器...

Datenfluss-Highlights:

  • Asynchrones Laden: Der Loader-Pfad bei nicht registrierten Diagrammen ist vollständig asynchron (packages/mermaid/src/Diagram.ts:24-31)
  • Datenbank-Population: Der Parser befüllt die Datenbank während des Parse-Vorgangs, was nachfolgenden Rendering-Zugriff ermöglicht
  • Zweiphasiges Rendering: Trennung zwischen Layout-Berechnung und SVG-Generierung für bessere Testbarkeit

Modulabhängigkeiten und Abstraktionsschichten

正在加载图表渲染器...

Abhängigkeitsprinzipien:

Kern-Designentscheidungen

1. Hybrides Parser-System (JISON + Langium)

Entscheidung: Mermaid verwendet sowohl JISON-Parser (Legacy) als auch Langium-Parser (neuere Diagrammtypen).

Begründung: Langium bietet typsichere AST-Generierung, bessere IDE-Unterstützung und einfachere Grammatik-Wartung. Die Koexistenz ermöglicht schrittweise Migration ohne Breaking Changes.

Nachteil: Zwei Parser-Infrastrukturen erhöhen die Bundle-Größe und Wartungskomplexität.

2. Lazy Loading für Diagrammtypen

Entscheidung: Diagrammtypen werden erst bei Bedarf geladen (packages/mermaid/src/Diagram.ts:24-31).

Begründung: Reduziert die initiale Bundle-Größe signifikant, da die meisten Anwendungen nur eine Teilmenge der Diagrammtypen verwenden.

Implementierung: Über getDiagramLoader() und asynchrone Dynamic Imports.

3. Plugin-basierte Diagram-Registry

Entscheidung: Zentrale Registry mit registerDiagram()-API (packages/mermaid/src/diagram-api/diagramAPI.ts:44-70).

Begründung: Ermöglicht Drittanbieter-Erweiterungen ohne Kernmodifikation. Die injectUtils-Methode stellt konsistente Hilfsfunktionen für alle Diagramme bereit.

Beispiel: Das Mindmap-Diagramm wird als externes Paket @mermaid-js/mermaid-mindmap verteilt.

4. Abstrahierte Layout-Algorithmen

Entscheidung: Layout-Algorithmen sind über ein Loader-System entkoppelt (packages/mermaid/src/rendering-util/render.ts:32-56).

Begründung: Ermöglicht die Auswahl optimaler Algorithmen pro Diagrammtyp und vereinfacht das Hinzufügen neuer Layout-Strategien.

Fallback-Mechanismus: getRegisteredLayoutAlgorithm() mit konfigurierbarem Fallback (packages/mermaid/src/rendering-util/render.ts:82-91).

5. DOM-ID-Namensraum-Isolation

Entscheidung: Automatisches Prefixing von DOM-IDs mit der Diagram-ID (packages/mermaid/src/rendering-util/render.ts:65-70).

Begründung: Verhindert ID-Kollisionen bei mehreren Diagrammen auf einer Seite.

Implementierung: node.domId = ${data4Layout.diagramId}-${originalDomId}

6. Utility-Injection-Muster

Entscheidung: Diagram-Definitionen erhalten Utilities über injectUtils()-Callback (packages/mermaid/src/diagram-api/diagramAPI.ts:58-69).

Begründung: Entkoppelt Diagram-Implementierungen von Kern-Utilities und ermöglicht konsistente Logging-, Sanitization- und Konfigurationszugriffe.

Injizierte Utilities: log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox, getCommonDb()

Technologie-Stack Übersicht

TechnologieVerwendungszweckAuswahlbegründungAlternative
LangiumParser-Generierung für neue DiagrammtypenTypsicherheit, IDE-Support, TypeScript-NativitätANTLR, Chevrotain
JISONLegacy-Parser (Flowchart, Sequence)Bestandscode-KompatibilitätMigration zu Langium
Dagre-D3Hierarchisches LayoutBewährtes Layout für gerichtete GraphenELK, Graphviz
D3.jsSVG-Manipulation und -RenderingMächtige Selektions-API, TransitionenSnap.svg, Two.js
TypeScriptTypsystem und KompilierungTypsicherheit, bessere IDE-IntegrationJavaScript mit JSDoc
Dynamische ImportsLazy Loading von DiagrammenReduzierte initiale Bundle-GrößeStatische Bündelung
CSS-in-JSDiagramm-spezifische StylesDynamische Style-InjectionExterne CSS-Dateien
Theme-SystemVisuelle Konsistenz5 eingebaute Themes, anpassbarHardcodierte Farben

Erweiterungspunkte und Integration

Externe Diagram-Registrierung

Die Integration externer Diagramme erfolgt über registerExternalDiagrams, wie in tests/webpack/src/index.js:13 demonstriert:

javascript
1const mermaid = require('mermaid');
2import mindmap from '@mermaid-js/mermaid-mindmap';
3
4const load = async () => {
5  await mermaid.registerExternalDiagrams([mindmap]);
6  await render('info');
7  // ...
8};

Theme-Anpassung

Das Theme-System in packages/mermaid/src/themes/index.js:1-23 exportiert fünf vordefinierte Themes:

javascript
1export default {
2  base: { getThemeVariables: baseThemeVariables },
3  dark: { getThemeVariables: darkThemeVariables },
4  default: { getThemeVariables: defaultThemeVariables },
5  forest: { getThemeVariables: forestThemeVariables },
6  neutral: { getThemeVariables: neutralThemeVariables },
7};

Layout-Algorithmus-Erweiterung

Neue Layout-Algorithmen können über registerLayoutLoaders() hinzugefügt werden:

typescript
1registerLayoutLoaders([
2  {
3    name: 'custom-layout',
4    loader: async () => await import('./custom-layout/index.js'),
5  },
6]);

Bekannte Einschränkungen

  1. Race-Condition-Handling: Die addDiagrams()-Funktion verwendet ein hasLoadedDiagrams-Flag zur Vermeidung von Race-Conditions (packages/mermaid/src/diagram-api/diagram-orchestration.ts:39-44), was bei parallelen Initialisierungen zu Problemen führen kann.

  2. JISON-Parser-Limitierung: Die parser.yy = db-Zuweisung (packages/mermaid/src/Diagram.ts:36) ist spezifisch für JISON-Parser und wird für Langium-Parser nicht benötigt.

  3. Große Feature-Abhängigkeit: Der cose-bilkent-Layout-Algorithmus ist von der includeLargeFeatures-Konfiguration abhängig (packages/mermaid/src/rendering-util/render.ts:45-52), was in Lightweight-Builds nicht verfügbar ist.

  4. YAML-Frontmatter-Erkennung: Die Erkennung basiert auf Textmustern (packages/mermaid/src/diagram-api/diagram-orchestration.ts:74-76) und kann bei ungewöhnlichen Formatierungen fehlschlagen.