📐 GraphQL-Formatierer & Verschönerer
Machen Sie Ihre hässlichen GraphQL-Abfragen und -Schemata wieder schön. Formatieren Sie Ihren Code ganz einfach und drucken Sie ihn sofort in Ihrem Browser aus.
Was Das Formatieren Einer Abfrage Wirklich Verändert
Kurz gesagt: Der Formatierer rückt jedes Selection Set auf zwei Leerzeichen pro Verschachtelungsebene ein, setzt jedes Feld in eine eigene Zeile und vereinheitlicht die Abstände um Argumente, Aliase und Direktiven. Was die Abfrage tatsächlich anfordert, bleibt unangetastet.
query{me:user(id:1){name email posts(limit:5){title comments{body author{name}}}}}query {
me: user(id: 1) {
name
email
posts(limit: 5) {
title
comments {
body
author {
name
}
}
}
}
}Fügen Sie diese einzeilige Abfrage oben in das Tool ein und klicken Sie auf Code formatieren, um dasselbe Ergebnis zu sehen. Jede öffnende geschweifte Klammer erhält ihre eigene Einrückungsebene, jedes Feld, das zuvor in einer Zeile zusammengedrängt war, bekommt seine eigene Zeile, und der Alias vor user bleibt exakt an seiner Stelle, nur mit einheitlichem Abstand um den Doppelpunkt. Verschachtelte Selection Sets, also ein Feld innerhalb eines Feldes innerhalb eines weiteren Feldes, erhalten jeweils zwei Leerzeichen mehr Einrückung als ihr übergeordnetes Feld. Deshalb landet comments eine Ebene tiefer als posts und author noch eine Ebene tiefer.
Im Hintergrund läuft dafür der GraphQL-Parser von Prettier, der beim ersten Klick auf Code formatieren von einem öffentlichen CDN geladen wird. Prettier liest Ihren Text in einen Syntaxbaum ein, unterscheidet also zwischen einem Feld, einem Argument, einer Direktive und einem Fragment-Spread, und druckt diesen Baum anschließend anhand fester Regeln wieder aus, statt Muster in Ihrer ursprünglichen Formatierung zu suchen. Deshalb sieht das Ergebnis gleich aus, egal ob Sie eine einzelne 400 Zeichen lange Zeile eingefügt haben oder etwas, das bereits halb eingerückt war und Tabs mit Leerzeichen mischte: Die ursprüngliche Formatierung wird vollständig verworfen und aus der geparsten Struktur neu aufgebaut.
| Was Sie geschrieben haben | Was dabei herauskommt |
|---|---|
| Ein Feld, das direkt neben seinen Geschwistern steht | Eine eigene Zeile, im selben Selection Set, in der Reihenfolge, in der Sie es geschrieben haben |
Ein Argument wie id:1 | id: 1, ein Leerzeichen nach dem Doppelpunkt, keines davor |
Ein Alias wie me:user(...) | me: user(...), weiterhin vor dem Feld, das er umbenennt |
Eine Direktive wie @include(if:$x) | @include(if: $x), direkt nach dem gedruckt, was sie verändert |
Ein verschachteltes { } Selection Set | Eine eigene Einrückungsebene, genau zwei Leerzeichen tiefer als das übergeordnete Feld |
Ebenso wichtig ist, was sich nicht ändert. Der Formatierer ordnet Felder niemals neu an, entfernt niemals ein Feld, das er für überflüssig hält, und benennt niemals etwas um. Stand name bei Ihnen vor email, steht name auch in der Ausgabe vor email. Haben Sie ein Feld zweimal mit unterschiedlichen Aliasen versehen, um es unter zwei verschiedenen Schlüsseln abzurufen, bleiben beide Aliase unangetastet erhalten. Die Neuformatierung ist ein reiner Druckschritt über die geparste Struktur. Sie kann die Bedeutung der Abfrage nicht verändern, weil sie den geparsten Baum nie verändert, sondern nur den Text, mit dem dieser Baum wieder auf die Seite gedruckt wird.
Wie Einrückung Und Zeilenumbruch Entschieden Werden
Kurz gesagt: Jedes verschachtelte Selection Set fügt genau zwei Leerzeichen hinzu, unabhängig davon, wie die Quelle eingerückt war, und eine Argumentliste bricht erst dann auf mehrere Zeilen um, wenn die gesamte Zeile etwa 80 Zeichen überschreiten würde.
query { search(firstArgument: "some long value here", secondArgument: "another long value", thirdArgument: true) { id } }query {
search(
firstArgument: "some long value here"
secondArgument: "another long value"
thirdArgument: true
) {
id
}
}Eine kurze Argumentliste bleibt genau dort, wo sie ist: user(id: 1) wird in einer Zeile gedruckt, weil das gesamte Feld bequem innerhalb der Breitengrenze passt. Der dreiargumentige search-Aufruf oben passt nicht, also setzt der Formatierer jedes Argument in eine eigene Zeile, eine Einrückungsebene tiefer als das Feld selbst, und richtet die schließende Klammer wieder an search aus. Einen Zwischenzustand gibt es nicht. Die Argumente eines Feldes stehen entweder vollständig in einer Zeile oder vollständig gestapelt, niemals ist ein Teil auf der Zeile des Feldes und der Rest darunter.
Die Entscheidung über die Einrückung ignoriert Ihre Quelle vollständig. Eine mit Tabs eingefügte Abfrage, eine mit vier Leerzeichen eingerückte Abfrage und eine als einzelne durchgehende Zeile eingefügte Abfrage erzeugen nach der Formatierung alle das identische Ergebnis, denn der Parser entfernt beim Parsen sämtliche Leerzeichen, und der Printer baut sie anschließend nach seiner eigenen Zwei-Leerzeichen-Regel von Grund auf neu auf. Es gibt nichts zu konfigurieren und nichts, was Sie im Eingabefeld tun könnten, um die Einrückungsbreite zu ändern. Sie ist für jede Konstruktion, die der Formatierer verarbeitet, fest auf zwei Leerzeichen pro Ebene eingestellt.
| Situation | Angewendete Regel |
|---|---|
| Die Quelle verwendet Tabs, vier Leerzeichen oder gar keine Einrückung | Wird ignoriert. Die Ausgabe verwendet immer zwei Leerzeichen pro Verschachtelungsebene |
| Eine Argumentliste passt in die eigene Zeile des Feldes | Bleibt in der Zeile, z. B. posts(limit: 5) |
| Eine Argumentliste würde die Zeile über die Breitengrenze hinaus verlängern | Bricht auf ein Argument pro Zeile um, eingerückt unter dem Feld, ohne abschließendes Komma |
Ein Objektwert wie {name:"bob",active:true} | Leerzeichen innerhalb der Klammern werden ergänzt: { name: "bob", active: true } |
Ein Listenwert wie ["a","b","c"] | Leerzeichen werden nach jedem Komma ergänzt: ["a", "b", "c"] |
| Zwei oder mehr Leerzeilen zwischen Definitionen auf oberster Ebene | Werden auf genau eine Leerzeile zusammengefasst |
Sie müssen vor dem Einfügen niemals von Hand irgendetwas ausrichten. Da der Parser Ihre ursprünglichen Leerzeichen verwirft und die Einrückung aus der erkannten Struktur neu aufbaut, bringt es nichts, vor der Formatierung Zeit damit zu verbringen, Klammern in Ihrer Quelle von Hand auszurichten. Fügen Sie den Code genau so ein, wie er in Ihrem Terminal, im Netzwerk-Tab Ihres Browsers oder in einer Chat-Nachricht steht, und überlassen Sie die Ausrichtung dem Formatierer.
Aliase, Direktiven, Fragmente Und Variablen Werden Neu Formatiert, Nicht Neu Geschrieben
Kurz gesagt: Aliase, Direktiven, Fragment-Spreads, Inline-Fragmente und Operationsvariablen behalten alle ihre exakten Namen und Struktur. Formatierung ändert nur ihre Abstände und Einrückung, niemals, worauf sie verweisen oder wie sie miteinander verdrahtet sind.
fragment PostFields on Post { title publishedAt }
query GetFeed($includeDrafts: Boolean!) { feed: posts(status: "published") { ...PostFields drafts: posts(status: "draft") @include(if: $includeDrafts) { title } } }fragment PostFields on Post {
title
publishedAt
}
query GetFeed($includeDrafts: Boolean!) {
feed: posts(status: "published") {
...PostFields
drafts: posts(status: "draft") @include(if: $includeDrafts) {
title
}
}
}Eine Fragmentdefinition wird genauso gedruckt wie eine Operation: ein Feld pro Zeile innerhalb ihres eigenen Selection Sets, auf oberster Ebene des Dokuments statt in etwas anderes verschachtelt. Wo Sie die Fragmentdefinition relativ zur Abfrage platzieren, die sie verwendet, bleibt exakt so, wie Sie es geschrieben haben, davor oder danach, denn der Formatierer ordnet Definitionen auf oberster Ebene in einer Datei nicht neu an. Ein Fragment-Spread wie ...PostFields wird in seiner eigenen Zeile zwischen den anderen ausgewählten Feldern gedruckt, und ein Inline-Fragment wie ... on User { name } erhält sein eigenes eingerücktes Selection Set, genau wie ein normales verschachteltes Feld.
Auf einer Operation deklarierte Variablen, einschließlich solcher mit Standardwerten wie $limit: Int = 10, bleiben innerhalb der Klammern direkt nach dem Operationsnamen, jeweils durch Komma und Leerzeichen getrennt. Direktiven bleiben an das gebunden, worauf sie geschrieben wurden: Eine Direktive auf einem Feld, wie @include(if: $includeDrafts) oben, wird direkt nach den Argumenten dieses Feldes und vor seinem Selection Set gedruckt, und eine Direktive auf einem Argument bleibt an dieses Argument gebunden. Der Formatierer akzeptiert auch eine anonyme Kurzform ohne query-Schlüsselwort und ohne jeden Namen, eine öffnende { direkt vor dem Selection Set, und rückt sie genauso ein wie eine vollständig benannte Abfrage.
| Konstrukt | Wie es gedruckt wird |
|---|---|
| Feld-Alias | alias: field(...), Abstände wie bei jedem anderen Feld vereinheitlicht |
| Fragment-Spread | ...FragmentName, eigene Zeile innerhalb des Selection Sets |
| Inline-Fragment | ... on TypeName { }, eingerückt wie ein verschachteltes Feld |
| Direktive | Bleibt direkt nach dem Feld oder Argument, das sie verändert |
| Variable mit Standardwert | $name: Type = default, innerhalb der Klammern der Operation |
| Anonyme Kurzform-Abfrage | Kein query-Schlüsselwort nötig, wird wie eine benannte Abfrage eingerückt |
Dies ist Druckformatierung, keine Abfrageauflösung. Der Formatierer bindet niemals die Felder eines Fragments an der Stelle ein, an der es verwendet wird, prüft niemals, ob $includeDrafts überhaupt deklariert wurde, bevor es verwendet wird, und bestätigt niemals, dass @include eine Direktive ist, die Ihr Schema kennt. Das sind Aufgaben für einen GraphQL-Client oder einen schemabewussten Linter, der gegen Ihre echte API läuft, nicht für einen Formatierer, der nur Syntax liest und neu ausdruckt.
Eine Schemadefinition Wird Nach Anderen Regeln Formatiert Als Eine Abfrage
Kurz gesagt: Fügen Sie statt einer Abfrage eine Typdefinition ein, wechselt derselbe Code-formatieren-Button von selbst das Verhalten: Typen, Interfaces, Enums, Unions und Inputs erhalten jeweils ein Feld oder einen Wert pro Zeile, und ein Beschreibungsstring wird immer auf drei eigene Zeilen aufgeteilt, selbst ein einzeiliger.
type User{"""The user's unique id"""id:ID! name:String! email:String posts:[Post!]!}type User {
"""
The user's unique id
"""
id: ID!
name: String!
email: String
posts: [Post!]!
}Der Formatierer fragt nicht, ob Ihr eingefügter Text eine Operation oder ein Schema ist. Er parst, was auch immer Sie ihm geben, und druckt zurück, welche Art von Dokument er darin gefunden hat. Geben Sie ihm eine Abfrage, erhalten Sie eine abfrageförmige Ausgabe. Geben Sie ihm Typ-, Interface-, Enum-, Union- und Input-Definitionen, erhalten Sie eine schemaförmige Ausgabe, bei der jedes Feld eines Typs, jeder Wert eines Enums und jedes Argument einer Interface-Methode in seiner eigenen Zeile steht, genau wie die Felder einer Operation. Nicht-Null- und Listenmarkierungen, ID!, [Post!]! und so weiter, bleiben exakt so erhalten, wie sie geschrieben wurden, denn diese Zeichen sind Teil des Typs selbst und nicht etwas, das der Drucker erzeugt.
Beschreibungsstrings verhalten sich anders als die gewöhnliche Feldformatierung, und das sollten Sie wissen, bevor Sie sich darauf verlassen: Eine dreifach angeführte Beschreibung wird auf drei Zeilen aufgeteilt, ein öffnendes """, der Text in eigener Zeile, ein schließendes """, selbst wenn die gesamte Beschreibung problemlos in eine Zeile passen würde, wie das siebenwortige Beispiel oben zeigt. Das ist kein Fehler, den man umgehen müsste. Es ist einfach so, wie der Schema-Drucker Blockbeschreibungen immer darstellt, und das gilt gleichermaßen für einen Typ, ein Feld, ein Argument, einen Enum-Wert oder ein Input-Feld.
Die Feldreihenfolge in einem Schema wird niemals alphabetisch sortiert. Ein Typ mit id, name, email und posts in dieser Reihenfolge kommt in genau dieser Reihenfolge wieder heraus. Dasselbe gilt für Operationen: Fragen Sie zebra, apple, dann mango ab, werden sie in dieser Reihenfolge zurückgegeben, und ein Feld, das im selben Selection Set zweimal wiederholt wird, wird zweimal gedruckt statt entfernt zu werden. Wenn Sie eine bestimmte Feldreihenfolge möchten, müssen Sie sie so schreiben. Der Formatierer rückt nur ein, was bereits vorhanden ist.
Kommentare Überstehen Die Formatierung. Die Meisten Leerzeilen Auch.
Kurz gesagt: Ein #-Kommentar, ob in einer eigenen Zeile oder einem Feld nachgestellt, wird unverändert durchgereicht. Leerzeilen zwischen Definitionen auf oberster Ebene bleiben ebenfalls erhalten, aber nie mehr als eine hintereinander.
# This is a comment
query {
# inline comment
user(id: 1) {
name # trailing comment
}
}# This is a comment
query {
# inline comment
user(id: 1) {
name # trailing comment
}
}Beachten Sie, dass die Ausgabe oben mit der Eingabe identisch ist, abgesehen von der Neueinrückung, die bei einer unkommentierten Version derselben Abfrage ohnehin angewendet würde. Ein alleinstehender Kommentar in eigener Zeile behält seine Position relativ zu dem Feld oder der Definition, die darauf folgt, und ein nachgestellter Kommentar bleibt am Ende der Zeile, in der er geschrieben wurde. Das ist in der Praxis wichtig, weil Kommentare einer der wenigen Orte sind, an denen Entwickler direkt in einer Abfrage Notizen für Kollegen hinterlassen, etwa um ein Feld zu markieren, das nur für einen alten Client noch gebraucht wird, und ein Formatierer, der sie stillschweigend entfernen würde, würde diesen Kontext klammheimlich löschen.
Leerzeilen werden nachsichtiger behandelt als Kommentare. Eine einzelne Leerzeile zwischen zwei Definitionen auf oberster Ebene, zwei getrennten Operationen im selben Dokument oder einer Operation und einer darunterliegenden Fragmentdefinition, bleibt als visuelle Trennung erhalten. Lassen Sie zwei, drei oder mehr Leerzeilen hintereinander stehen, fasst der Formatierer sie alle auf genau eine zusammen. Er entfernt die Trennung nie vollständig, und er behält niemals zusätzlichen Abstand über eine einzelne Zeile hinaus. Innerhalb eines Selection Sets, zwischen einzelnen Feldern, werden Leerzeilen überhaupt nicht erhalten, denn Felder innerhalb von Klammern werden unabhängig vom Abstand in der Quelle direkt hintereinander gedruckt.
Nachgestellte Leerzeichen und verirrte Leerzeilen im Eingabefeld haben keine Bedeutung. Da der Parser Struktur liest und keinen Rohtext, hat eine zusätzliche Leerzeile ganz oben in Ihrem eingefügten Code oder ein nachgestelltes Leerzeichen am Ende einer Zeile keinerlei Auswirkung auf das Ergebnis. Das einzige Leerzeichen, das der Formatierer als bedeutsam behandelt, ist eine einzelne Leerzeile zwischen zwei Definitionen auf oberster Ebene, alles andere wird beim Parsen verworfen.
Kaputtes GraphQL Wird Nicht Markiert. Es Wird Erraten.
Kurz gesagt: Dies ist ein Formatierer, kein Validator. Enthält Ihr eingefügter Code einen echten Syntaxfehler, sagt Ihnen nichts auf der Seite davon. Das Tool wechselt stillschweigend zu einem groben Suchen-und-Ersetzen-Durchgang, der GraphQL überhaupt nicht versteht.
query { me: user(id: 1) { name email posts(limit: 5) { title query {
me: user(id: 1) {
name email posts(limit: 5) {
title Die Ausgabe oben ist kein Formatierungsfehler in diesem Artikel, sie ist genau das, was das Tool erzeugt, doppelte Leerzeichen vor Klammern inklusive. Hier ist der Grund. Hinter dem Code-formatieren-Button erkennt Prettiers echter GraphQL-Parser durchaus korrekt, dass diese Abfrage kaputt ist. Gibt man ihm denselben unabgeschlossenen Text, liefert er einen präzisen Fehler, Syntax Error: Expected Name, found <EOF>, der genau auf Zeile und Spalte zeigt, an der die Abfrage endet. Aber die Komponente umschließt diesen Aufruf mit einem try/catch-Block, und bei jedem Fehler protokolliert sie ihn in der Entwicklerkonsole des Browsers, wo Sie ihn nie sehen werden, es sei denn, Sie suchen gezielt danach, und gibt stattdessen die Ausgabe einer Fallback-Funktion zurück statt einer Fehlermeldung auf der Seite.
Dieser Fallback besteht aus vier verketteten Textersetzungen: Alle Leerzeichen auf ein einziges reduzieren, nach jeder { einen Zeilenumbruch und zwei Leerzeichen Einrückung einfügen, nach jeder } einen Zeilenumbruch einfügen, nach jedem Komma einen Zeilenumbruch und zwei Leerzeichen Einrückung einfügen. Er hat kein Konzept von Verschachtelungstiefe, weshalb jede öffnende Klammer oben dieselbe flache Zwei-Leerzeichen-Einrückung erhält, egal wie tief sie tatsächlich sitzt, und kein Konzept einer passenden schließenden Klammer, weshalb eine unabgeschlossene Abfrage wie diese einfach ausläuft, ohne dass die Struktur je geschlossen wird. Das doppelte Leerzeichen vor jeder { ist ein Nebeneffekt der Reihenfolge der Ersetzungen: Der Schritt, der Leerzeichen reduziert, lässt ein Leerzeichen vor der Klammer stehen, und der Schritt, der die Klammer ersetzt, fügt ein weiteres hinzu, ohne zu prüfen, was bereits da ist.
Derselbe stille Fallback greift auch dann, wenn die Formatierungsbibliothek selbst nie lädt, nicht nur bei einem Syntaxfehler in Ihrem Text. Der echte Parser ist nicht in die Seite eingebettet, er wird beim ersten Klick auf Code formatieren von einem öffentlichen CDN nachgeladen. Wird diese Anfrage blockiert, ist die Verbindung offline, oder schlägt sie aus einem anderen Netzwerkgrund fehl, behandelt der try/catch-Block das genauso wie einen Parsing-Fehler und liefert dieselbe grobe, unverschachtelte Ausgabe, selbst für eine vollkommen gültige Abfrage. Es gibt keine Anzeige auf der Seite, die unterscheidet, ob Ihr GraphQL einen Syntaxfehler hat, der echte Formatierer nicht laden konnte, oder der echte Formatierer gelaufen ist und dies wirklich das korrekte Ergebnis ist. Ein Ergebnis, das flach und seltsam gesetzt wirkt, mit doppelten Leerzeichen vor Klammern, ist das Erkennungszeichen dafür, dass Sie den Fallback sehen und keinen echten Formatierungsdurchgang.
Die praktische Konsequenz ist, dass sich dieses Tool nicht dazu eignet, zu prüfen, ob GraphQL gültig ist. Brauchen Sie eine echte Validierung, die Bestätigung, dass jedes Feld auf dem Typ existiert, gegen den es abgefragt wird, dass jede verwendete Variable deklariert ist, dass jede Direktive korrekt geschrieben ist, dann muss das von einem GraphQL-Client, einer IDE-Erweiterung mit Anbindung an Ihr echtes Schema oder einem dedizierten Linter kommen. Die Aufgabe dieses Formatierers endet bei der Neueinrückung von Syntax, die er erfolgreich parsen kann.