Zum Inhalt springen
Auf dieser Seite

ngrx-graph: Statische Analyse und Graph-Visualisierung für NgRx-Anwendungen

ngrx-graph: Statische Analyse und Graph-Visualisierung für NgRx-AnwendungenKI-generiertes Bild

Wenn Sie jemals eine Action durch eine umfangreiche NgRx-Codebasis verfolgt haben und sich fragten, welcher Effect darauf hört, welcher Reducer sie verarbeitet und ob ein anderer Effect im Hintergrund eine weitere Action in der Kette auslöst, kennen Sie das Problem. Das manuelle Durchsuchen des Codes funktioniert so lange, bis es an seine Grenzen stößt. Ein einziger verpasster dispatch()-Aufruf, der tief in einem mergeMap vergraben ist, und Ihr mentales Modell bricht zusammen.

ngrx-graph ist ein CLI-Tool, das dieses Chaos in Bilder verwandelt. Es scannt Ihr Angular-NgRx-Projekt, erstellt eine strukturierte JSON-Repräsentation aller Komponenten, Actions, Effects und Reducer und generiert DOT- sowie SVG-Graphen, die Action-Abläufe auf einen Blick sichtbar machen.

Das Problem: NgRx-Actions von Hand verfolgen

NgRx ist mächtig. Es bietet einen vorhersehbaren State-Container mit einer klaren Trennung von Zuständigkeiten: Actions beschreiben, was passiert ist, Effects behandeln Seiteneffekte, Reducer entscheiden, wie sich der State ändert. In der Theorie ist der Datenfluss sauber.

In der Praxis sammeln sich in großen NgRx-Anwendungen Dutzende (manchmal Hunderte) von Actions an. Effects verknüpfen Actions miteinander. Komponenten lösen Actions aus, die Effects anstoßen, welche wiederum weitere Actions auslösen. Reducer hören auf bestimmte Action-Typen. Und irgendwo mittendrin wird eine Action über einen Barrel-Export als Alias weitergeführt und unter einem anderen Namen erneut exportiert.

So sieht eine typische Debugging-Sitzung aus:

  1. Sie finden ein dispatch(SomeAction) in einer Komponente.
  2. Sie suchen danach, wo SomeAction verarbeitet wird: vielleicht in einem Effect, vielleicht in einem Reducer.
  3. Der Effect führt etwas aus und löst AnotherAction aus.
  4. Sie wiederholen den Prozess für AnotherAction.
  5. Drei Ebenen tief verlieren Sie den Überblick.

Das ist kein theoretisches Problem. In produktiven NgRx-Codebasen erstrecken sich Action-Ketten routinemäßig über 3 bis 5 Indirektionsebenen. Effects kombinieren Effects. Verschachtelte Actions (Actions, die durch das Auslösen anderer Actions entstehen) fügen eine weitere Ebene hinzu. Der mentale Aufwand ist hoch und führt zu Fehlern: verpasste Dispatches, veralteter State und doppelter Code.

Was Sie brauchen, ist eine Karte. Keine Diagrammzeichnung, die irgendwann einmal angefertigt und dann vergessen wurde, sondern eine generierte Karte, die jedes Mal direkt aus dem tatsächlichen Quellcode erstellt wird.

Ohne ngrx-graph: 15 Minuten Verfolgen von ofType(), Barrel-Exports und dispatch()-Aufrufen über 8 geöffnete Tabs in VS Code.

Mit ngrx-graph: Führen Sie npx ngrx-graph "LoadUsers" --svg aus und prüfen Sie den vollständigen Ausführungspfad in 2 Sekunden.

High-Level-Architektur

ngrx-graph ist ein Node.js-CLI-Tool, das mit oclif gebaut wurde. Die Pipeline läuft in drei Phasen ab:

  1. Scanning: Die TypeScript-Compiler-API analysiert die Quelldateien und extrahiert über AST-Traversierung Action-Definitionen, Effect-Ketten, Reducer-Handler und Component-Dispatches.
  2. Zwischen-JSON: Alle Scan-Ergebnisse werden in ngrx-graph.json serialisiert, eine kanonische Repräsentation, die das Scanning von der Visualisierung entkoppelt.
  3. Graph-Generierung: Das JSON wird in die Graphviz-DOT-Generierung eingespeist, optional mit SVG-Rendering über ein natives dot-Binary oder ein WASM-Fallback.
Quelldateien --> AST-Scanner --> ngrx-graph.json --> DOT/SVG-Renderer
     |               |                 |                   |
  *.ts-Dateien  p-limit Worker   kanonisches Format   Graphviz oder viz.js

Jede Phase ist unabhängig nutzbar. Sie können nach der JSON-Erstellung anhalten, um die Scanner-Ausgabe zu prüfen. Sie können die DOT-Dateien mit jedem Graphviz-kompatiblen Werkzeug wiederverwenden. Die Architektur trennt die Zuständigkeiten klar, sodass Verbesserungen am Scanning keine Auswirkungen auf das Rendering haben und umgekehrt.

Installation und Schnellstart

Global installieren oder direkt mit npx ausführen:

npm install -g ngrx-graph
# oder
npx ngrx-graph ...

Typische Arbeitsabläufe

# Nur JSON: Prüfen, was der Scanner findet
ngrx-graph -d ./src --out ./out
# schreibt: ./out/ngrx-graph.json

# Fokussierter Graph für eine bestimmte Action
ngrx-graph "LoadUsers" -d ./src --out ./out --svg
# schreibt: ./out/LoadUsers.dot + ./out/LoadUsers.svg

# Vollständiger Projekt-Graph
ngrx-graph -a -d ./src --out ./out --dot --svg
# schreibt: ./out/all.dot + ./out/all.svg

# Erneuten Scan erzwingen (JSON-Cache umgehen)
ngrx-graph -d ./src --out ./out -f

Vollständige Flag-Referenz

FlagKurzBeschreibung
--dir-dZu scannendes Verzeichnis (Standard: cwd)
--out-oAusgabeverzeichnis für ngrx-graph.json
--json-jNur scannen und JSON schreiben, kein DOT/SVG
--dotDOT-Dateien generieren (pro Action + aggregiert)
--svg-sSVG-Dateien aus DOT generieren
--vizviz.js (WASM) für SVG bevorzugen, wenn dot nicht da ist
--all-aNur das aggregierte all.dot generieren
--concurrency-cParallele Datei-Parsing-Worker (Standard: CPU-Anzahl - 2)
--force-fErneuten Scan erzwingen, gecachtes JSON ignorieren
--verbose-vAusführliches Logging aktivieren

Tipps für große Projekte

Bei Codebasen mit Tausenden von Dateien machen einige Verfahren den Unterschied zwischen einem Scan von 2 Sekunden und einem von 20 Sekunden aus:

  • Mit JSON beginnen: Führen Sie zuerst ngrx-graph -d ./src --json aus, um zu sehen, was der Scanner findet. Prüfen Sie ngrx-graph.json, bevor Sie Graphen generieren.
  • Standardmäßig cachen: Nachfolgende Ausführungen nutzen das vorhandene JSON erneut. Übergeben Sie --force nur nach größeren Refactorings.
  • Nebenläufigkeit anpassen: Auf CI-Maschinen mit begrenzter CPU-Leistung reduzieren Sie die Nebenläufigkeit mit --concurrency 2, um Ressourcenkonflikte zu vermeiden.
  • --viz ohne installiertes Graphviz nutzen: Das --viz-Flag nutzt viz.js (einen WASM/JS-Renderer) anstelle des nativen dot-Binarys. Es greift elegant auf viz.js zurück.
  • Graphen fokussieren: Das --all-Flag erzeugt den gesamten Projektgraphen. Zum Debuggen einer bestimmten Action übergeben Sie immer den Action-Namen, um einen fokussierten Teilgraphen zu erhalten. Vollständige Graphen mit über 500 Knoten sind zwar technisch korrekt, aber in der Praxis unlesbar.

Unter der Haube: Lösung komplexer AST- und Graph-Probleme

Der Aufbau eines zuverlässigen NgRx-Action-Graphen aus einer statischen Quelltextanalyse erforderte die Lösung von drei anspruchsvollen technischen Problemen. Jedes davon besiegte naive Lösungsansätze und verlangte nach einer durchdachten algorithmischen Lösung.

Herausforderung 1: Statische Analyse von RxJS-Ketten in Effects

Effects stellen den schwierigsten Teil des Graphen dar. Ein typischer Effect sieht folgendermaßen aus:

loadUsers$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadUsers),
    switchMap(() =>
      this.userService.getAll().pipe(
        map(users => loadUsersSuccess({ users }))
      )
    )
  )
);

Der Scanner muss zwei Dinge ermitteln: welche Actions über ofType() eingehen und welche Actions über dispatch() oder ein return innerhalb von Operator-Callbacks ausgegeben werden. Die Schwierigkeit liegt darin, dass RxJS-Operatoren tief verschachtelte Callback-Funktionen sind. Die innerhalb von map() oder mergeMap() ausgelöste Action ist kein Geschwisterknoten des ofType()-Aufrufs, sondern befindet sich mehrere Ebenen tiefer in der Funktionsverschachtelung.

Die Lösung ist ein rekursiver AST-Durchlauf über CallExpression-Knoten. Ausgehend von jedem createEffect()-Rumpf identifiziert der Scanner pipe()-Aufrufe, durchläuft jedes Operator-Argument und gleicht ofType-Aufrufe ab, um die Eingangs-Actions zu extrahieren:

// Konzeptueller AST-Knoten-Durchlauf für Effect-Eingaben
if (ts.isCallExpression(node) && node.expression.getText() === "ofType") {
  const inputAction = node.arguments[0]?.getText();
  // Eingangs-Action registrieren...
}

Wenn der Durchlauf auf map, mergeMap, switchMap, concatMap oder exhaustMap stößt, steigt er in den Rumpf des Callbacks ab. Innerhalb des Callbacks sucht er nach dispatch(SomeAction)-Aufrufen sowie return SomeAction-Ausdrücken und zeichnet diese jeweils als Ausgangs-Action auf.

Dieser Ansatz verarbeitet beliebig verschachtelte Operatorketten: pipe(switchMap(() => pipe(map(X), tap(() => dispatch(Y))))) wird korrekt aufgelöst, da der Durchlauf unabhängig von der Verschachtelungstiefe arbeitet. Er verfolgt die Kette strukturell, nicht textuell.

Der Scanner deckt auch das verbreitete Muster ab, bei dem ein Effect eine Action bedingt auslöst. Jeder dispatch()-Aufruf innerhalb des Operator-Rumpfes wird unabhängig von seiner Position in if/else-Zweigen als mögliche Ausgabe erfasst. Dies ist bewusst konservativ gestaltet: Es ist besser, eine Action anzuzeigen, die möglicherweise ausgelöst wird, als eine zu verpassen.

Herausforderung 2: Auflösung von Barrel-File-Aliasen

Angular-Projekte organisieren Actions häufig in eigenen Dateien und exportieren sie über Barrel-Files (index.ts) erneut. Ein Problem entsteht, wenn beim Re-Export Aliase verwendet werden:

// actions/user.actions.ts
export const loadUsers = createAction('[Users] Load');
export const loadUsersSuccess = createAction('[Users] Load Success');
// index.ts
export { loadUsers, loadUsersSuccess as usersLoaded } from "./actions/user.actions";
// user.effects.ts
import { usersLoaded } from "../";

Der Effect bezieht sich auf usersLoaded, der kanonische Name der Action lautet jedoch loadUsersSuccess. Ein naiver Scanner behandelt diese als zwei unzusammenhängende Actions. Der Graph zerfällt in getrennte Teilgraphen und liefert ein unvollständiges Bild.

ngrx-graph löst dies über eine kanonische Symboltabelle im Arbeitsspeicher, die während der Scanning-Phase aufgebaut wird:

  1. Deklarations-Scan: Der erste Durchgang identifiziert alle createAction()-Aufrufe und zeichnet deren kanonische Namen sowie die Speicherorte der Quelldateien auf.
  2. Re-Export-Scan: Der zweite Durchgang liest alle index.ts-Dateien aus und analysiert Muster wie export { X as Y } und export { X }. Für jeden Re-Export wird der Alias (Y) auf den kanonischen Namen (X) zurückgeführt.
  3. Auflösung: Wenn ein Scanner auf einen importierten Action-Namen stößt, prüft er die Symboltabelle. Handelt es sich bei dem Namen um einen Alias, wird er auf den kanonischen Namen aufgelöst, bevor er im Graphen erfasst wird.

Dieser Drei-Wege-Ansatz verarbeitet mehrstufige Re-Exports (A exportiert aus B, das aus C exportiert) und gemischte Alias-Muster. Die Symboltabelle dient als zentrale Referenz für die Action-Identität über die gesamte Codebasis hinweg.

Herausforderung 3: Rauschen im Graphen und Erreichbarkeitsfilterung

Ein projektweiter NgRx-Graph mit Hunderten von Actions bietet wenig Nutzen. Die hohe Anzahl an Knoten macht DOT-Layouts unleserlich, und die meisten Knoten sind für die konkrete Untersuchung irrelevant. Die technische Aufgabe besteht darin, einen aussagekräftigen Teilgraphen aus einem großen gerichteten Graphen zu extrahieren.

[ Unverbundene Action ]   [ Angular-Komponente ]
           |                        |
           x               ( Ziel-Action )    <-- BFS-Startpunkt
                              /        \
                     [ Effect 1 ]    [ Reducer ]
                          |
                  [ Ausgangs-Action ]

ngrx-graph modelliert den Datenfluss als gerichteten Graphen G=(V,E), wobei die Knoten Komponenten, Actions, Effects und Reducer darstellen. Die Kanten repräsentieren Dispatch-, Handling- und Verarbeitungsbeziehungen. Fordert der Benutzer einen fokussierten Graphen für die Action A an, führt das Tool eine bidirektionale Breitensuche (Breadth-First Search, BFS) aus:

Vorwärtsdurchlauf: Ausgehend von A werden die Kanten verfolgt, um alle Actions zu ermitteln, die durch Effects ausgelöst werden, die wiederum auf A hören. Anschließend werden alle Actions ermittelt, die durch Effects ausgelöst werden, die auf diese Actions hören, und so weiter. Dies erfasst die gesamte nachgelagerte Kette.

Rückwärtsdurchlauf: Ausgehend von A werden die Kanten in umgekehrter Richtung verfolgt, um festzustellen, welche Komponenten A auslösen und welche Effects A erzeugen. Dies erfasst den vorgelagerten Kontext.

Die Vereinigung beider Durchläufe ergibt den kleinstmöglichen Teilgraphen, der A sowie alle von dort aus erreichbaren oder dorthin führenden Elemente enthält. Nicht erreichbare Actions, Effects und Reducer werden ausgeschlossen. Das Ergebnis ist ein fokussierter, lesbarer Graph, der exakt den für die untersuchte Action relevanten Ablauf zeigt.

Die BFS-Implementierung nutzt eine Adjazenzlisten-Repräsentation mit einer Traversierungskomplexität von O(V+E). Bei Projekten mit mehr als 500 Actions und 200 Effects enthält der fokussierte Graph in der Regel nur 10 bis 30 Knoten, was ihn sofort erfassbar macht.

Technischer Stack und Architekturentscheidungen

Jede Bibliothek in ngrx-graph wurde im Hinblick auf konkrete Anforderungen ausgewählt:

KomponenteWahlGrundIn Betracht gezogene Alternative
AST-ParsingTypeScript Compiler API (ts.createSourceFile)Exakte AST-Treue für TS/TSX. Verarbeitet Generics, Dekoratoren und Typanmerkungen, die Babel entfernt oder umwandelt. Keine Konfigurationsabweichung zwischen Parser und Projekt-tsconfig.Babel: schneller, verliert jedoch Typinformationen. Regex: bricht bei verschachtelten Callbacks und Alias-Importen ab.
Begrenzte Nebenläufigkeitp-limitVerhindert eine Auslastung von CPU und Arbeitsspeicher bei Codebasen mit Tausenden von Dateien. max(1, CPU_COUNT - 2) Worker halten das System während des Scans reaktionsfähig.Unbehandeltes Promise.all: unbegrenzte Parallelität führt bei Projekten mit über 10.000 Dateien zu Out-of-Memory-Fehlern. Sequenziell: zu langsam für Unternehmens-Codebasen.
CLI-FrameworkoclifProfessionelle CLI-Struktur: Flag-Parsing, Hilfe-Generierung, Plugin-System, Test-Utilities. Erprobt bei Salesforce.Commander/yargs: leichter, bietet aber keine integrierte Hilfe-Formatierung und Test-Infrastruktur.
Graph-Rendering (primär)Natives Graphviz dot-BinarySchnellster Rendering-Pfad. Erzeugt hochwertige Layouts. In den meisten CI-Umgebungen verfügbar.entfällt: Dies ist die Referenzimplementierung.
Graph-Rendering (Fallback)@hpcc-js/wasm (viz.js)WASM-Renderer ohne externe Abhängigkeiten. Wenn dot nicht installiert ist (häufig in minimalen Docker-Images oder CI-Containern), nutzt --viz die In-Browser-Graphviz-Portierung. Keine Installation erforderlich.Puppeteer/Headless Chrome: schwerfällig, langsam, anfällig in CI-Umgebungen.
AusgabeformatGraphviz DOTStandardformat, das von jedem Graph-Tool unterstützt wird. DOT-Dateien sind textbasiert, diffbar und kombinierbar. SVG erfordert nur einen zusätzlichen Rendering-Schritt.Benutzerdefiniertes JSON-Graph-Format: würde einen eigenen Renderer erfordern und die Interoperabilität einschränken.

Die zweistufige Rendering-Strategie (zuerst natives dot, dann WASM-Fallback) stellt sicher, dass ngrx-graph überall funktioniert, ohne dass Benutzer dazu gezwungen sind, Systempakete zu installieren. In CI-Umgebungen ermöglicht --viz ein Rendering ohne externe Abhängigkeiten. Auf Entwicklerrechnern arbeitet das native Binary schneller.

Praxisbeispiele und Sonderfälle

Das Repository enthält vier Beispielprojekte, die zunehmend komplexere Szenarien demonstrieren.

Fall 1: Einfacher Action-Ablauf

Ein einfaches Setup: Eine Komponente löst action1 aus, ein Effect hört darauf und löst action2 sowie action3 aus, ein Reducer verarbeitet action3.

npx ngrx-graph action1

Der generierte Graph zeigt den vollständigen Ablauf: FirstComponent -> action1 -> effect1$ -> action2, action3 -> firstReducer. Sie sehen genau, welche Komponente die Kette gestartet hat und wo sie endet. In Unternehmens-Codebasen bildet dies die Grundlage, um zu überprüfen, ob der Action-Ablauf eines Features durchgehend vollständig ist.

Die Fokussierung auf action3 zeigt stattdessen einen kleineren Ausschnitt: nur Knoten, die diese spezifische Action beeinflussen oder von ihr beeinflusst werden.

Fall 2: Verschachtelte Actions

Actions können andere Actions in ihrer Payload transportieren. Fall 2 demonstriert nestedAction1 (das action1 und action2 als Payload-Actions enthält) sowie nestedAction2. Der Graph hebt verschachtelte Actions durch eine hellblaue Füllung hervor, wodurch das Kompositionsmuster optisch direkt erkennbar wird.

Dies ist in großen NgRx-Projekten von Bedeutung, da die Action-Komposition ein gängiges Muster zur Orchestrierung mehrstufiger Workflows ist. Ohne visuelle Hervorhebung werden verschachtelte Actions bei Code-Reviews leicht übersehen.

Fall 3: Erreichbarkeitsfilterung in unverbundenen Graphen

Wenn Actions unverbunden sind (action2 existiert, ist aber von action1 aus über keine Effect-Kette erreichbar), schließt der fokussierte Graph diese korrekt aus. Ohne Erreichbarkeitsfilterung wären fokussierte Graphen zu unübersichtlich. Dies ist entscheidend für große Codebasen, in denen Hunderte von Actions nebeneinander existieren, aber nur ein Teil davon an einem bestimmten Ablauf beteiligt ist.

Der BFS-Algorithmus stellt sicher, dass das Ergebnis den kleinstmöglichen Teilgraphen darstellt. Keine isolierten Knoten, keine irrelevanten Kanten. Der Unterschied zwischen einem fokussierten und einem aggregierten Graphen in einem Projekt mit 300 Actions entspricht dem Unterschied zwischen einer gut lesbaren Karte und einer unleserlichen Ansammlung von Linien.

Fall 4: Aliase und Re-Exports

Reale Projekte exportieren Actions häufig über Barrel-Files erneut:

// index.ts
export { actionB, actionA as exportedActionA } from "./case4.actions";

Effects importieren den Alias:

import { exportedActionA } from "./index";

ngrx-graph löst diese Aliase wieder auf die ursprünglichen Action-Namen auf, sodass der Graph den tatsächlichen Ablauf anzeigt, anstatt an den Grenzen von Re-Exports abzubrechen. In Monorepo-Architekturen mit gemeinsam genutzten Action-Bibliotheken verhindert die Alias-Auflösung, dass der Graph in unverbundene Teilgraphen zerfällt.

Sonderfall: Bedingte Dispatches

Effects lösen Actions manchmal an Bedingungen geknüpft aus:

loadUsers$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadUsers),
    switchMap(() =>
      this.userService.getAll().pipe(
        map(users => users.length > 0
          ? loadUsersSuccess({ users })
          : loadUsersEmpty()
        )
      )
    )
  )
);

Sowohl loadUsersSuccess als auch loadUsersEmpty werden als Ausgaben aufgeführt. Der Scanner erfasst jeden möglichen Pfad, sodass keine Action im Graphen übergangen wird.

Sonderfall: Mehrstufige Re-Exports

In Monorepos wandern Actions unter Umständen durch mehrere Barrel-Files:

core/actions.ts  -->  core/index.ts  -->  feature/index.ts  -->  feature.effects.ts

Die kanonische Symboltabelle löst Alias-Ketten in beliebiger Tiefe auf. Der Graph zeigt unabhängig davon, wie viele Re-Export-Ebenen zwischen der Deklaration und der Verwendung liegen, stets den ursprünglichen Action-Namen an.

Warum ein CLI-Tool besser abschneidet als Browser-Visualisierer

Es gibt verschiedene Werkzeuge zur Visualisierung von NgRx-States, diese decken jedoch unterschiedliche Anforderungen ab:

  • ngrx-visualizer (Google): Ein webbasiertes Werkzeug zur interaktiven Untersuchung. Erfordert den manuellen Aufbau des Graphen im Browser. Funktioniert gut für kleine Projekte, ist aber für CI/CD-Pipelines oder große Codebasen weniger geeignet.
  • ngrx-graph: Ein CLI-fokussiertes Werkzeug, das für die Automatisierung entwickelt wurde. Es analysiert Quellcode über AST, verarbeitet Aliase sowie verschachtelte Actions, unterstützt fokussierte Graphen mit Erreichbarkeitsfilterung und gibt Standard-DOT/SVG-Dateien zur Einbindung in Dokumentations-Pipelines aus.

Das entscheidende Unterscheidungsmerkmal sind fokussierte Action-Graphen. Die meisten Visualisierungswerkzeuge zeigen den gesamten State-Graphen auf einmal. In einem großen Projekt führt das zu einem unleserlichen Knotenpunkt-Netzwerk. ngrx-graph verfolgt eine einzelne Action durch den gesamten Ablauf und zeigt nur die erreichbaren Knoten an, worin der eigentliche Nutzen beim Debuggen liegt.

Wo sich ngrx-graph im Einsatz auszahlt

Code-Review: Generieren Sie einen fokussierten Graphen für eine neue Action, um zu überprüfen, ob der gesamte Ablauf vollständig ist. Ein fehlender Reducer-Handler oder eine fehlende Effect-Subscription wird visuell sofort sichtbar.

Einarbeitung: Neue Teammitglieder betrachten einen projektweiten Graphen, um die State-Management-Architektur zu verstehen, ohne jede Datei lesen zu müssen. Er dient als dynamisches Architekturdiagramm.

Debuggen von Produktionsfehlern: Wenn ein Fehler auf einen veralteten State zurückzuführen ist, generieren Sie einen Graphen für die betroffene Action-Kette. Sie sehen schnell, ob bei einem Reducer ein Handler fehlt oder ob ein Effect eine unerwartete Action auslöst.

Refactoring: Bevor Sie eine Action umbenennen oder entfernen, generieren Sie deren fokussierten Graphen, um alle Abhängigkeiten einzusehen. Das schützt vor unerwarteten Fehlern.

Ausprobieren

npx ngrx-graph -d ./src --out ./out --svg

Ein Befehl. Ein Graph. Ihr NgRx-Store hört auf, eine Blackbox zu sein.

Links:

Beiträge sind willkommen. Öffnen Sie ein Issue, wenn etwas nicht funktioniert, oder einen Pull Request, wenn Sie einen Fehler beheben möchten.