Zum Hauptinhalt springen

Prettier 1.5: GraphQL, CSS-in-JS & JSON

· 15 Min. Lesezeit
Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Dieses Release bringt Unterstützung für die Formatierung von GraphQL, CSS-in-JS (inklusive styled-components) und JSON in Prettier.

Dies ist das Release, auf das ich so lange gewartet habe: eines mit nur minimalen Änderungen an JavaScript!

In den letzten sechs Monaten haben wir kontinuierlich verschiedene Aspekte der JavaScript-Formatierung angepasst, in der Hoffnung, eines Tages einen stabilen Zustand zu erreichen. Kein automatisiertes Tool wird perfekten Code für alle möglichen Grenzfälle ausgeben. Unser Ziel ist es, einen Punkt zu finden, an dem wir gemeldeten Code, der merkwürdig formatiert erscheint, nicht verbessern können, ohne andernfalls anderen Code schlechter aussehen zu lassen, schwer verständliches Verhalten einzuführen oder unverhältnismäßige Komplexität in die Codebasis zu bringen.

Wir sind noch nicht zu 100% dort, aber näher als je zuvor!

Da der Unterstützungsbedarf für JavaScript zurückgeht, bietet sich die Gelegenheit, andere Sprachen zu unterstützen, an denen Frontend-Entwickler arbeiten und die sie formatiert haben möchten. Im letzten Release haben wir TypeScript und CSS eingeführt und bringen in diesem Release eine Reihe von Fixes dafür mit. Wir fügen außerdem Unterstützung für neue Sprachen hinzu: GraphQL-Queries, CSS-in-JS und JSON sind nun in Prettier verfügbar!

Blog-Post: Adding a new layout strategy to Prettier von @karl

Prettier ist nicht nur ein nützliches Tool, sondern auch eine faszinierende Technologie. @karl hat viel Zeit darauf verwendet, die JSX-Unterstützung zu verbessern und dabei ein neues Primitive für Prettier implementiert: fill. Sein äußerst interessanter Blog-Post Adding a new layout strategy to Prettier ist sehr empfehlenswert, wenn Sie daran interessiert sind, wie Prettier hinter den Kulissen funktioniert.

GraphQL

Dank @stubailo, @jnwng, @tgriesser und @azz unterstützt Prettier nun das Formatieren von GraphQL-Queries!

Es funktioniert für .graphql-Dateien und innerhalb von JavaScript-Templates, die mit graphql, graphql.experimental oder gql beginnen, um mit Relay und Apollo zu arbeiten.

ReactDOM.render(
<QueryRenderer
query={graphql`
query appQuery {
viewer {
...TodoApp_viewer
}
}
`}
// ...
/>,
mountNode
);

Beachten Sie, dass nur die Open-Source-Syntax von GraphQL unterstützt wird, es also nicht mit Relay Classic funktioniert, sondern nur mit Relay Modern.

CSS-in-JS

Wenn Sie styled-components oder styled-jsx verwenden, wird Prettier nun das CSS in Ihren Template-Ausdrücken neu formatieren. Vielen Dank an @pomber für die großartige Arbeit!

const EqualDivider = styled.div`
margin: 0.5rem;
padding: 1rem;
background: papayawhip;
> * {
flex: 1;
&:not(:first-child) {
${props => (props.vertical ? "margin-top" : "margin-left")}: 1rem;
}
}
`;

JSON

Die Umsetzung war relativ unkompliziert, aber dennoch sehr nützlich. Danke an @josephfrazier für die Umsetzung :)

{
"name": "prettier",
"version": "1.5.0",
"description": "Prettier is an opinionated JavaScript formatter",
"bin": {
"prettier": "./bin/prettier.js"
}
}

CSS

Ich bin wirklich begeistert, denn wir haben nur wenige Tage in die initiale CSS-Unterstützung investiert, die überraschend gut funktioniert hat. Dieses Release bringt einige wichtige Verbesserungen für CSS, aber nichts, das große Änderungen erfordert hätte.

CSS: Jeder Selektor wird nun in einer eigenen Zeile ausgegeben (#2047) von @yuchi

Die größte Unbekannte bei der Formatierung von CSS war der Umgang mit mehreren Selektoren. Unser ursprünglicher Ansatz folgte der 80-Zeichen-Regel, bei der wir nur umbrachen, wenn die Zeilenlänge überschritten wurde. Viele Nutzer berichteten jedoch von einer alternativen Strategie: Immer nach einem , umbrechen. Es zeigt sich, dass viele populäre Codebasen diesen Ansatz verwenden – er vermittelt ein besseres Strukturverständnis, wenn Selektoren untereinander angeordnet sind.

// Before
.clusterPlannerDialog input[type="text"], .clusterPlannerDialog .uiTypeahead {
color: #333;
}

// After
.clusterPlannerDialog input[type="text"],
.clusterPlannerDialog .uiTypeahead {
color: #333;
}

CSS: Hex-Farben in Kleinbuchstaben (#2203) von @azz

Das Konzept der Codeformatierung hat fließende Grenzen. Kernelement sind Leerzeichen, aber Aspekte wie einfache vs. doppelte Anführungszeichen oder Semikolons werden üblicherweise miteinbezogen. Bei Prettier für JavaScript normalisieren wir auch Strings (entfernen überflüssige \) und Zahlen. Für CSS müssen wir ähnlich interpretieren, wo die Grenzen liegen. Bei Farben entschieden wir uns, alle Buchstaben klein zu schreiben – Konvertierungen von rgb() zu Hex oder 6-stellige zu 3-stelligen Hex-Werten liegen außerhalb des Scopes.

// Before
.foo {
color: #AAA;
-o-color: #fabcd3;
-ms-color: #AABBCC;
}

// After
.foo {
color: #aa;
-o-color: #fabcd3;
-ms-color: #aabbcc;
}

CSS: Verwenden von fill für CSS-Werte (#2224)

Das neue fill-Primitiv erwies sich als äußerst nützlich für CSS. Bei langen Werten setzen wir statt eines Umbruchs und eines \n vor jedem Element nur dann einen \n, wenn die Zeilenlänge überschritten wird. Das führt zu optisch ansprechenderem Code.

// Before
foo {
border-left:
1px
solid
mix($warningBackgroundColors, $warningBorderColors, 50%);
}

// After
foo {
border-left: 1px solid
mix($warningBackgroundColors, $warningBorderColors, 50%);
}

CSS: Lange Media-Queries umbrechen lassen (#2219)

Ein weiterer kleiner Schritt bei der Unterstützung einer neuen Sprache: Wir ermöglichen nun Umbrüche bei langen @media-Regeln.

// Before
@media all and (-webkit-min-device-pixel-ratio: 1.5), all and (-o-min-device-pixel-ratio: 3/2), all and (min--moz-device-pixel-ratio: 1.5), all and (min-device-pixel-ratio: 1.5) {
}

// After
@media all and (-webkit-min-device-pixel-ratio: 1.5),
all and (-o-min-device-pixel-ratio: 3/2),
all and (min--moz-device-pixel-ratio: 1.5),
all and (min-device-pixel-ratio: 1.5) {
}

CSS: Drucke @else in derselben Zeile wie } (#2088) von @azz

Less und SCSS entwickeln sich zu echten Programmiersprachen :) Schritt für Schritt formatieren wir ihre Konstrukte analog zu JavaScript. Diesmal betrifft es die Platzierung von else.

// Before
@if $media == phonePortrait {
$k: .15625;
}
@else if $media == tabletPortrait {
$k: .065106;
}

// After
@if $media == phonePortrait {
$k: .15625;
} @else if $media == tabletPortrait {
$k: .065106;
}

CSS: Implementierung von prettier-ignore (#2089) von @azz

Obwohl Prettier gesamte Codebasen formatieren soll, gibt es Fälle, in denen wir es "besser wissen" und eine Ausnahme benötigen. Hier kommt der prettier-ignore-Kommentar ins Spiel. Für CSS funktionierte er bisher nicht – das war ein Versehen, nun ist es behoben :)

// Before
foo {
/* prettier-ignore */
thing: foo;
-ms-thing: foo;
}

// After
foo {
/* prettier-ignore */
thing: foo;
-ms-thing: foo;
}

CSS: Korrektur bei CSS-Modules composes mit langer Zeilenlänge (#2190) von @tdeekens

Viele "Bundler" verwenden grobe Regex- statt vollständiger Parser, um Abhängigkeiten zu extrahieren – das ist der Grund, warum wir lange require()-Aufrufe nicht umbrechen. Das betrifft auch CSS-Modules: Bei Zeilenumbrüchen im composes-Feld werden diese nicht mehr erkannt. Daher brechen wir dort nicht mehr um, selbst bei Überschreitung von 80 Zeichen.

// Before
.reference {
composes:
selector
from
"a/long/file/path/exceeding/the/maximum/length/forcing/a/line-wrap/file.css";
}

// After
.reference {
composes: selector from "a/long/file/path/exceeding/the/maximum/length/forcing/a/line-wrap/file.css";
}

CSS: Bevorzuge SCSS bei @import mit Komma (#2225)

Wir entschieden uns für einen einzigen "Parser" für CSS, SCSS und Less (nutzt intern postcss-less/postcss-scss). Ein Regex bestimmt den initialen Parser, bei Syntaxfehlern folgt ein Fallback. Leider überspringt der erste (falsche) Parser bei manchen Features Elemente ohne Fehler. Daher verbessern wir den Regex für zuverlässigere Früherkennung.

Glücklicherweise funktioniert dieser Workaround in der Praxis gut. Sollten wir auf deutlich mehr Randfälle stoßen, werden wir vermutlich die richtige Lösung(tm) implementieren und sie in zwei separate Parser aufteilen.

// Before
@import "text-shadow";

// After
@import "rounded-corners", "text-shadow";

TypeScript

Die TypeScript-Unterstützung ist jetzt stabil, alle Änderungen in diesem Release betreffen kleine Randfälle.

TypeScript: Pfeilfunktionstyp-Parameter in derselben Zeile wie Parameter ausgeben (#2101) von @azz

Der Kernalgorithmus von Prettier erweitert eine Gruppe, wenn nicht alle Elemente in eine Zeile passen. Das funktioniert in der Praxis für den Großteil von JavaScript hervorragend, aber bei zwei nebeneinanderliegenden Gruppen – hier: <Generics>(Arguments) – stößt er an Grenzen. Wir müssen Gruppen sorgfältig so definieren, dass Argumente zuerst erweitert werden, da dies den allgemeinen Erwartungen entspricht.

// Before
export const forwardS = R.curry(<
V,
T
>(prop: string, reducer: ReducerFunction<V, T>, value: V, state: {[name: string]: T}) =>
R.assoc(prop, reducer(value, state[prop]), state)
);

// After
export const forwardS = R.curry(
<V, T>(
prop: string,
reducer: ReducerFunction<V, T>,
value: V,
state: { [name: string]: T }
) => R.assoc(prop, reducer(value, state[prop]), state)
);

TypeScript: Klammern bei Non-Null-Assertions mit yield/await beibehalten (#2149) von @azz

Ob zum Guten oder Schlechten, wir haben uns entschieden, das Hinzufügen von Klammern manuell zu steuern. Wenn ein neuer Operator eingeführt wird, müssen wir sicherstellen, dass wir korrekte Klammern hinzufügen, wenn er mit anderen Operatoren kombiniert wird. In diesem Fall haben wir das await innerhalb von TypeScripts ! vergessen.

// Before
const bar = await foo(false)!;

// After
const bar = (await foo(false))!;

TypeScript: in Import-Anweisungen erhalten, wenn im Original vorhanden (#2150) von @azz

Wir nutzen typescript-eslint-parser, der TypeScript-AST in estree-AST übersetzt. Gelegentlich fehlt dort die Behandlung spezieller Fälle – hier fehlte die Erkennung leerer {}, die für TypeScript relevant sind. Das Team reagierte schnell und behob dies, nachdem wir einen Workaround in Prettier implementiert hatten.

// Before
import from "@types/googlemaps";

// After
import {} from "@types/googlemaps";

TypeScript: Interfaces immer mehrzeilig ausgeben (#2161) von @azz

Der Code für interface teilt sich die Logik mit dem für objects, der eine Regel enthält, sie erweitert zu lassen, wenn ein \n darin ist. Für Interfaces ist dieses Verhalten jedoch unerwünscht. Sie sollen – ähnlich wie Klassen – stets erweitert werden, auch bei Unterschreitung der 80-Zeichen-Grenze.

// Before
interface FooBar { [id: string]: number; }

// After
interface FooBar {
[id: string]: number;
}

TypeScript: Überflüssiges Semikolon in TypeScript-Deklarationen entfernen (#2167) von @azz

no-semi und semi werden oft angefragt, aber das Prettier-Team ging weiter und implementierte two-semi! Spaß beiseite – es handelte sich um einen Bug, der nun behoben ist ;)

// Before
declare module "classnames" {
export default function classnames(
...inputs: (string | number | false | object | undefined)[]
): string;;
}

// After
declare module "classnames" {
export default function classnames(
...inputs: (string | number | false | object | undefined)[]
): string;
}

TypeScript: Funktionsparameter in Signatur-Definitionen gruppieren (#2169) von @azz

Kommentare vor Methoden lösten unerwartete Zeilenumbrüche aus, da ihre Länge fälschlich einbezogen wurde. Die Lösung war einfach: Die Ausgabe in eine group wrappen.

// Before
interface TimerConstructor {
// Line-splitting comment
new (
interval: number,
callback: (handler: Timer) => void
): Timer;
}

interface TimerConstructor {
// Line-splitting comment
new (interval: number, callback: (handler: Timer) => void): Timer;
}

TypeScript: tsep-Parser aktualisieren (#2183) von @azz

Dieser Bug war besonders ärgerlich: Bei jedem Formatieren wurde ein weiteres _ zum Objektschlüssel hinzugefügt!

// Before
obj = {
__: 42
___: 42
};

// After
obj = {
_: 42
__: 42
};

TypeScript: Zeilenumbruch bei mehrfacher Interface-Vererbung (#2085) von @azz

Im Gegensatz zu JavaScript ermöglicht TypeScript, mehrere Klassen gleichzeitig zu erweitern. Es stellt sich heraus, dass diese Funktion genutzt wird, und Prettier erledigt die Formatierung nun besser.

// Before
export interface ThirdVeryLongAndBoringInterfaceName extends AVeryLongAndBoringInterfaceName, AnotherVeryLongAndBoringInterfaceName, AThirdVeryLongAndBoringInterfaceName {}

// After
export interface ThirdVeryLongAndBoringInterfaceName
extends AVeryLongAndBoringInterfaceName,
AnotherVeryLongAndBoringInterfaceName,
AThirdVeryLongAndBoringInterfaceName {}

TypeScript: Verarbeitung von ObjectPattern statt ObjectExpression innerhalb von BinaryExpression (#2238) von @azz

Dieser Punkt ist weniger relevant – es handelt sich um einen Randfall, der bei der Konvertierung von TypeScript zu estree nicht korrekt behandelt wurde.

// Before
call(c => { bla: 1 }) || [];

// After
call(c => ({ bla: 1 })) || [];

Leerzeilen nach Direktiven erhalten (#2070)

Durch die TypeScript-Unterstützung wird Prettier nun in vielen Angular-Codebasen verwendet, was Randfälle aufdeckt, die zuvor nicht richtig behandelt wurden. In diesem Fall wurden Leerzeilen nach Direktiven innerhalb von Funktionen nicht beibehalten.

// Before
export default class {
constructor($log, $uibModal) {
"ngInject";
Object.assign(this, { $log, $uibModal });

// After
export default class {
constructor($log, $uibModal) {
"ngInject";

Object.assign(this, { $log, $uibModal });

JavaScript

Dieses Release enthält nur wenige JavaScript-Änderungen, was hervorragend ist. Wir nähern uns dem Ziel eines stabilen Pretty Printers. Ein 100% perfektes automatisches Formatierungswerkzeug wird es nie geben. Unser Ziel ist, dass es für jeden gemeldeten Fehler keine offensichtliche Verbesserungsmöglichkeit gibt, ohne andere Codeabschnitte zu verschlechtern.

Ermögliche Neuverkettung von JSX-Zeilen (#1831) von @karl

Prettier strebt konsistente Formatierung an: Bei gleichem AST soll immer gleich formatiert werden. Bei JSX und Objekten mussten wir bisher Kompromisse eingehen und den Originalcode lesen. Mit dieser Änderung verlassen wir uns nicht mehr auf die Originaleingabe bei JSX mit Textinhalt. Dadurch können wir Text innerhalb von JSX neu umbrechen.

// Before
const Abc = () => {
return (
<div>
Please state your
{" "}
<b>name</b>
{" "}
and
{" "}
<b>occupation</b>
{" "}
for the board of directors.
</div>
);
};

// After
const Abc = () => {
return (
<div>
Please state your <b>name</b> and <b>occupation</b> for the board of
directors.
</div>
);
}

Umbruch bei nicht-literalen berechneten Member-Ausdrücken (#2087) von @azz

Die Formatierung von Member-Ketten ist Prettiers komplexester Teil. Wir finden ständig kleine Optimierungen für ein besseres Ergebnis.

// Before
nock(/test/)
.matchHeader("Accept", "application/json")[httpMethodNock(method)]("/foo")
.reply(200, {
foo: "bar",
});

// After
nock(/test/)
.matchHeader("Accept", "application/json")
[httpMethodNock(method)]("/foo")
.reply(200, {
foo: "bar",
});

Erste Variable in Einzelvar-Deklaration einrücken (#2095) von @azz

Bisher unterstützten wir mehrfache Variablendeklarationen kaum, da einzelne Deklarationen üblicher sind. Bei Einzeldelarationen wollen wir keine Einrückung, aber bei Folgedeklarationen sieht es ohne Einrückung seltsam aus.

// Before
var templateTagsMapping = {
'%{itemIndex}': 'index',
'%{itemContentMetaTextViews}': 'views'
},
separator = '<span class="item__content__meta__separator">•</span>';

// After
var templateTagsMapping = {
'%{itemIndex}': 'index',
'%{itemContentMetaTextViews}': 'views'
},
separator = '<span class="item__content__meta__separator">•</span>';

Umbruch bei gleichzeitigen Default- und Named-Imports ermöglichen (#2096) von @azz

Korrektur einer bedauerlichen Regression aus Version 1.4: Wir hatten Imports mit nur einem Element inline dargestellt, aber die Definition von "einem Element" erlaubte fälschlich einen einzelnen Typ plus ein Element.

// Before
import transformRouterContext, { type TransformedContextRouter } from '../../helpers/transformRouterContext';

// After
import transformRouterContext, {
type TransformedContextRouter
} from '../../helpers/transformRouterContext';

Option allowImportExportEverywhere aktivieren (#2207) von @zimme

Prettier soll praktischen Code formatieren, daher aktivieren wir lockere/experimentelle Modi in allen Parsern. Babylon erlaubt Imports in Funktionen (nicht standardkonform), was wir ohne großen Aufwand unterstützen.

// Before
ParseError

// After
function f() {
import x from 'x';
}

Inline-Templates für new-Aufrufe unterstützen (#2222)

Wir erweitern ständig Features für Funktionsaufrufe und müssen diese auf neue Aufrufe übertragen, die einen anderen AST-Knotentyp haben, obwohl wir sie in der Praxis gleich behandeln möchten. Diese Änderung vereinheitlicht die Behandlung beider Aufruftypen, indem sie den gleichen Aufrufort durchlaufen, und sollte hoffentlich verhindern, dass weitere Unstimmigkeiten auftreten.

// Before
new Error(
formatErrorMessage`
This is a really bad error.
Which has more than one line.
`
);

// After
new Error(formatErrorMessage`
This is a really bad error.
Which has more than one line.
`);

Keine Einrückung von + in Objektwerten (#2227)

Als wir dieselbe Heuristik für Zuweisungen (a = b) wie für Objekte ({a: b}) verwendeten, vergaßen wir die Einrückung zu korrigieren. Jetzt ist das behoben.

// Before
var abc = {
thing:
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf" +
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf" +
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf",
}

// After
var abc = {
thing:
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf" +
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf" +
"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf",
}

Umgang mit Bedingungen innerhalb ternärer Ausdrücke (#2228)

Prettier hatte bereits einen Sonderfall für bedingte Ausdrücke, aber dieser galt nicht, wenn die Bedingung der linke Teil eines ternären Ausdrucks war. Jetzt ist das der Fall.

// Before
room = room.map((row, rowIndex) =>
row.map(
(col, colIndex) =>
rowIndex === 0 ||
colIndex === 0 ||
rowIndex === height ||
colIndex === width
? 1
: 0
)
);

// After
room = room.map((row, rowIndex) =>
row.map(
(col, colIndex) =>
rowIndex === 0 ||
colIndex === 0 ||
rowIndex === height ||
colIndex === width
? 1
: 0
)
);

Caching für den Druckvorgang hinzugefügt (#2259)

Mit Release 1.0 behoben wir einen Druckfehler, der exponentielles Verhalten verursachte. Wir konnten die größten Probleme entschärfen, sodass normaler Code nicht mehr Zeitüberschreitungen verursachte, aber nicht vollständig beheben. Durch Caching an entscheidender Stelle sollte das Problem nun gelöst sein.

Dadurch sollte das Drucken des IR von Prettier mit Prettier im Debug-Modus keine Zeitüberschreitungen mehr verursachen.

// Before
...times out...

// After
someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
return someObject.someFunction().then(function () {
anotherFunction();
});
});
});
});
});
});
});
});
});

Korrektur der Varianzposition (#2261)

Bei der Refaktorierung des Modifikator-Drucks für TypeScript-Unterstützung wurde versehentlich die Varianz (+) vor static platziert - ungültig in Flow. Nun korrigiert.

// Before
class Route {
+static param: T;
}

// After
class Route {
static +param: T;
}

Verschiedenes

Diverse Korrekturen für Bereichs- und Cursor-Tracking (#2266, #2248, #2250, #2136) von @CiGit und @josephfrazier

Beide Features wurden im letzten Release eingeführt, wobei sich in der Praxis diverse Probleme zeigten. Viele wurden behoben - bei weiteren Beobachtungen bitte melden!