Die Kompromisse von CSS-in-JS

Foto von Artem Bali

Kürzlich habe ich einen Überblick über CSS-in-JS auf höherer Ebene geschrieben, in dem hauptsächlich die Probleme angesprochen wurden, die mit diesem Ansatz gelöst werden sollen. Autoren von Bibliotheken investieren selten Zeit in die Beschreibung der Kompromisse ihrer Lösung. Manchmal liegt es daran, dass sie zu voreingenommen sind, und manchmal wissen sie einfach nicht, wie die Benutzer das Tool anwenden. Dies ist also ein Versuch, die Kompromisse zu beschreiben, die ich bisher gesehen habe. Ich denke, es ist wichtig zu erwähnen, dass ich der Autor von JSS bin, daher sollte ich als voreingenommen betrachtet werden.

Sozialer Einfluss

Es gibt eine Schicht von Menschen, die auf der Webplattform arbeiten und kein JavaScript kennen. Diese Leute werden dafür bezahlt, HTML und CSS zu schreiben. CSS-in-JS hat den Workflow der Entwickler enorm beeinflusst. Eine wahrhaft transformative Veränderung kann niemals durchgeführt werden, ohne dass einige Menschen zurückgelassen werden. Ich weiß nicht, ob CSS-in-JS der einzige Weg sein muss, aber die Massenakzeptanz ist ein deutliches Zeichen für Probleme bei der Verwendung von CSS in modernen Anwendungen.

Ein großer Teil des Problems ist unsere Unfähigkeit, die Anwendungsfälle, in denen CSS-in-JS glänzt, genau zu kommunizieren und wie man es richtig für eine Aufgabe verwendet. Viele CSS-in-JS-Enthusiasten haben die Technologie erfolgreich beworben, aber nicht viele Kritiker sprachen konstruktiv über die Kompromisse, ohne billige Abstriche bei den Tools zu machen. Infolgedessen ließen wir viele Kompromisse verborgen und unternahmen keine großen Anstrengungen, um Erklärungen und Problemumgehungen bereitzustellen.

CSS-in-JS ist ein Versuch, die Handhabung komplexer Anwendungsfälle zu vereinfachen. Verschieben Sie es also nicht dorthin, wo es nicht benötigt wird!

Laufzeitkosten

Wenn CSS zur Laufzeit aus JavaScript generiert wird, entsteht im Browser ein inhärenter Overhead. Der Laufzeitaufwand variiert von Bibliothek zu Bibliothek. Dies ist ein guter allgemeiner Benchmark, aber stellen Sie sicher, dass Sie Ihre eigenen Tests durchführen. Die Hauptunterschiede zur Laufzeit hängen von der Notwendigkeit eines vollständigen CSS-Parsings der Vorlagenzeichenfolgen, der Anzahl der Optimierungen, den Implementierungsdetails für dynamische Stile, dem Hashing-Algorithmus und den Kosten für die Framework-Integrationen ab. *

Neben dem möglichen Laufzeitaufwand müssen Sie vier verschiedene Bündelungsstrategien berücksichtigen, da einige CSS-in-JS-Bibliotheken mehrere Strategien unterstützen und es dem Benutzer überlassen ist, diese anzuwenden. *

Strategie 1: Nur Laufzeitgenerierung

Die Laufzeit-CSS-Generierung ist eine Technik, die eine CSS-Zeichenfolge in JavaScript generiert und diese Zeichenfolge dann mithilfe eines Style-Tags in das Dokument einfügt. Diese Technik erzeugt ein Stylesheet, KEINE Inline-Styles.

Der Nachteil der Laufzeitgenerierung ist die Unfähigkeit, gestalteten Inhalt zu einem frühen Zeitpunkt bereitzustellen, wenn das Dokument geladen wird. Dieser Ansatz eignet sich normalerweise für Anwendungen ohne Inhalt, die sofort nützlich sein können. Normalerweise erfordern solche Anwendungen Benutzerinteraktionen, bevor sie einem Benutzer wirklich nützlich werden können. Oft arbeiten solche Anwendungen mit Inhalten, die so dynamisch sind, dass sie veraltet sind, sobald Sie sie laden. Daher müssen Sie frühzeitig eine Update-Pipeline einrichten, z. B. Twitter. Wenn ein Benutzer angemeldet ist, muss für SEO kein HTML bereitgestellt werden.

Wenn für die Interaktion JavaScript erforderlich ist, muss das Bundle geladen werden, bevor die App fertig ist. Beispielsweise können Sie den Inhalt eines Standardkanals anzeigen, wenn Sie Slack in das Dokument laden. Es ist jedoch wahrscheinlich, dass der Benutzer den Kanal direkt danach ändern möchte. Also, wenn Sie die ersten Inhalte geladen haben, nur um sie sofort wegzuwerfen.

Die wahrgenommene Leistung solcher Anwendungen kann mit Platzhaltern und anderen Tricks verbessert werden, damit sich die Anwendung augenblicklicher anfühlt als sie tatsächlich ist. Solche Anwendungen sind in der Regel ohnehin datenintensiv, sodass sie nicht so schnell nützlich sind wie ein Artikel.

Strategie 2: Laufzeitgenerierung mit kritischem CSS

Kritisches CSS ist die minimale Menge an CSS, die erforderlich ist, um die Seite im ursprünglichen Zustand zu formatieren. Es wird mit einem Stil-Tag im Kopf des Dokuments gerendert. Diese Technik wird häufig mit und ohne CSS-in-JS verwendet. In beiden Fällen werden Sie wahrscheinlich die CSS-Regeln doppelt laden, einmal als Teil des kritischen CSS und einmal als Teil des JavaScript- oder CSS-Bundles. Die Größe von Critical CSS kann abhängig von der Menge des Inhalts recht groß sein. Normalerweise wird das Dokument nicht zwischengespeichert.

Ohne Critical CSS muss eine statische, inhaltsintensive Einzelseitenanwendung mit CSS-in-JS zur Laufzeit Platzhalter anstelle von Inhalten anzeigen. Dies ist schlecht, da es für einen Benutzer früher nützlich gewesen sein könnte, um die Zugänglichkeit auf Low-End-Geräten und für Verbindungen mit geringer Bandbreite zu verbessern.

Mit kritischem CSS kann die CSS-Generierung zur Laufzeit zu einem späteren Zeitpunkt erfolgen, ohne dass die Benutzeroberfläche in der Anfangsphase blockiert wird. Beachten Sie jedoch, dass sich die CSS-Generierung mit JavaScript auf mobilen Low-End-Geräten, die mindestens 5 Jahre alt sind, negativ auf die Leistung auswirken kann. Dies hängt stark von der Menge des generierten CSS und der verwendeten Bibliothek ab und kann daher nicht verallgemeinert werden.

Der Kompromiss dieser Strategie sind die Kosten für die kritische CSS-Extraktion und die Kosten für die Laufzeit-CSS-Generierung.

Strategie 3: Nur Extraktion während der Erstellung

Diese Strategie ist die Standardstrategie im Web ohne CSS-in-JS. In einigen CSS-in-JS-Bibliotheken können Sie statisches CSS zum Zeitpunkt der Erstellung extrahieren. * In diesem Fall ist kein Laufzeitaufwand erforderlich. CSS wird auf der Seite mithilfe eines Link-Tags gerendert. Die Kosten für die CSS-Generierung werden einmal im Voraus bezahlt.

Hier gibt es zwei wichtige Kompromisse:

  1. Einige der von CSS-in-JS angebotenen dynamischen APIs können zur Laufzeit nicht verwendet werden, da Sie keinen Zugriff auf den Status haben. Häufig können Sie noch keine benutzerdefinierten CSS-Eigenschaften verwenden, da diese nicht in allen Browsern unterstützt werden und zum Zeitpunkt der Erstellung von Natur aus nicht mehrfach ausgefüllt werden können. In diesem Fall müssen Sie Problemumgehungen für dynamisches Theming und zustandsbasiertes Styling durchführen. *
  2. Ohne kritisches CSS und mit einem leeren Cache blockieren Sie die erste Farbe, bis Ihr CSS-Bundle geladen wird. Ein Linkelement im Kopf des Dokuments blockiert das Rendern von HTML.
  3. Nicht deterministische Spezifität mit seitenbasierter Bündelaufteilung in Einseitenanwendungen. *

Strategie 4: Extraktion in der Build-Zeit mit kritischem CSS

Diese Strategie gilt auch nicht nur für CSS-in-JS. Die vollständige statische Extraktion mit kritischem CSS bietet die beste Leistung, wenn Sie mit einer statischeren Anwendung arbeiten. Dieser Ansatz weist immer noch die oben genannten Nachteile eines statischen CSS auf, mit der Ausnahme, dass das blockierende Link-Tag an den unteren Rand des Dokuments verschoben werden kann.

Es gibt 4 Hauptstrategien für das CSS-Rendering. Nur 2 von ihnen sind CSS-in-JS-spezifisch und keine von ihnen gilt für alle Bibliotheken.

Zugänglichkeit

CSS-in-JS kann bei falscher Verwendung die Barrierefreiheit beeinträchtigen. Dies ist der Fall, wenn eine weitgehend statische Content-Site ohne Critical CSS-Extraktion implementiert wird, sodass HTML nicht gezeichnet werden kann, bevor das JavaScript-Bundle geladen und ausgewertet wurde. Dies kann auch passieren, wenn eine große CSS-Datei mit einem blockierenden Link-Tag im Kopf des Dokuments gerendert wird. Dies ist das derzeit am häufigsten auftretende Problem bei der herkömmlichen Einbettung und nicht CSS-in-JS-spezifisch.

Entwickler müssen die Verantwortung für die Barrierefreiheit übernehmen. Es gibt immer noch eine stark irreführende Vorstellung, dass eine instabile Internetverbindung ein Problem wirtschaftlich schwacher Länder ist. Wir neigen dazu zu vergessen, dass wir jeden Tag Verbindungsprobleme haben, wenn wir ein U-Bahn-System oder ein großes Gebäude betreten. Eine stabile kabellose Mobilfunkverbindung ist ein Mythos. Es ist nicht einmal einfach, eine stabile WiFi-Verbindung zu haben. Beispielsweise kann ein 2,4-GHz-WI-FI-Netzwerk durch einen Mikrowellenherd gestört werden!

Die Kosten für kritisches CSS mit serverseitigem Rendering

Um eine kritische CSS-Extraktion für CSS-in-JS zu erhalten, benötigen wir SSR. SSR ist ein Prozess zum Generieren des endgültigen HTML-Codes für einen bestimmten Status einer Anwendung auf dem Server. Tatsächlich kann es ein ziemlich komplexer und teurer Prozess sein. Für jede HTTP-Anforderung ist eine bestimmte Anzahl von CPU-Zyklen auf dem Server erforderlich.

CSS-in-JS nutzt normalerweise die Tatsache, dass es in die HTML-Rendering-Pipeline eingebunden ist. * Es weiß, welches HTML gerendert wurde und welches CSS es benötigt, damit es die absolut minimale Menge davon erzeugen kann. Kritisches CSS fügt dem HTML-Rendering auf dem Server zusätzlichen Overhead hinzu, da dieses CSS auch in eine endgültige CSS-Zeichenfolge kompiliert werden muss. In einigen Szenarien ist es jedoch schwierig oder sogar unmöglich, einen Cache auf dem Server zu erstellen.

Black Box rendern

Sie müssen wissen, wie eine von Ihnen verwendete CSS-in-JS-Bibliothek Ihr CSS wiedergibt. Beispielsweise sind sich die Benutzer häufig nicht bewusst, wie Gestaltete Komponenten und Emotionen dynamische Stile implementieren. Dynamische Stile sind eine Syntax, die die Verwendung von JavaScript-Funktionen innerhalb Ihrer Stildeklaration ermöglicht. Diese Funktionen akzeptieren Requisiten und geben einen CSS-Block zurück.

Um die Quellordnungsspezifität konsistent zu halten, generieren beide oben genannten Bibliotheken eine neue CSS-Regel, wenn sie eine dynamische Deklaration enthält und die Komponente mit neuen Requisiten aktualisiert wird. Um zu demonstrieren, was ich meine, habe ich diese Sandbox erstellt. In JSS haben wir uns für einen anderen Kompromiss entschieden, mit dem wir die dynamischen Eigenschaften aktualisieren können, ohne neue CSS-Regeln zu generieren. *

Steile Lernkurve

Für Leute, die mit CSS vertraut sind, aber noch keine Erfahrung mit JavaScript haben, ist der anfängliche Arbeitsaufwand, um sich mit CSS-in-JS vertraut zu machen, möglicherweise recht groß.

Sie müssen kein professioneller JavaScript-Entwickler sein, um CSS-in-JS zu schreiben, bis zu dem Punkt, an dem komplexe Logik beteiligt ist. Wir können die Komplexität des Stils nicht verallgemeinern, da dies wirklich vom Anwendungsfall abhängt. In Fällen, in denen CSS-in-JS komplex wird, ist die Implementierung mit Vanilla-CSS wahrscheinlich noch komplexer.

Für das grundlegende CSS-in-JS-Styling muss man wissen, wie man Variablen deklariert, Vorlagenzeichenfolgen verwendet und JavaScript-Werte interpoliert. Wenn die Objektnotation verwendet wird, muss man wissen, wie man mit JavaScript-Objekten und der bibliotheksspezifischen objektbasierten Syntax arbeitet. Wenn es um dynamisches Styling geht, muss man wissen, wie man JavaScript-Funktionen und -Bedingungen verwendet.

Insgesamt gibt es eine Lernkurve, die wir nicht leugnen können. Diese Lernkurve ist jedoch normalerweise nicht viel größer als das Lernen von Sass. Tatsächlich habe ich diesen Egghead-Kurs erstellt, um dies zu demonstrieren.

Keine Interoperabilität

Die meisten CSS-in-JS-Bibliotheken sind nicht interoperabel. Dies bedeutet, dass mit einer Bibliothek geschriebene Stile nicht mit einer anderen Bibliothek gerendert werden können. Praktisch bedeutet dies, dass Sie nicht einfach Ihre gesamte Anwendung von einer Implementierung auf eine andere umstellen können. Dies bedeutet auch, dass Sie Ihre Benutzeroberfläche in NPM nur dann auf einfache Weise freigeben können, wenn Sie die CSS-in-JS-Bibliothek Ihrer Wahl in das Paket des Consumers aufnehmen, es sei denn, Sie verfügen über eine statische Extraktion in der Erstellungszeit für Ihr CSS.

Wir haben begonnen, an dem ISTF-Format zu arbeiten, mit dem dieses Problem behoben werden soll. Leider hatten wir noch keine Zeit, es in den produktionsbereiten Zustand zu versetzen. *

Ich denke, die Freigabe von wiederverwendbaren Framework-unabhängigen UI-Komponenten im öffentlichen Bereich ist immer noch ein im Allgemeinen schwer zu lösendes Problem.

Sicherheits Risikos

Mit CSS-in-JS ist es möglich, Sicherheitslücken zu schließen. Wie bei allen clientseitigen Anwendungen müssen Sie vor dem Rendern immer die Benutzereingaben umgehen.

In diesem Artikel erhalten Sie weitere Informationen und einige Beispiele für die Verschleierung.

Nicht lesbare Klassennamen

Einige Leute halten es immer noch für wichtig, dass wir aussagekräftige, lesbare Klassennamen im Web haben. Derzeit bieten viele CSS-in-JS-Bibliotheken aussagekräftige Klassennamen basierend auf dem Deklarationsnamen oder Komponentennamen im Entwicklungsmodus. In einigen von ihnen können Sie sogar die Funktion zum Generieren von Klassennamen anpassen.

Im Produktionsmodus generieren die meisten von ihnen kürzere Namen für eine kleinere Nutzlast. Dies ist ein Kompromiss, den der Benutzer der Bibliothek bei Bedarf eingehen und anpassen muss.

Fazit

Es gibt Kompromisse und ich habe wahrscheinlich nicht alle erwähnt. Die meisten davon gelten jedoch nicht für alle CSS-in-JS. Sie hängen davon ab, welche Bibliothek Sie verwenden und wie Sie sie verwenden.

* Um diesen Satz zu erklären, wird ein Artikel benötigt. Lassen Sie mich auf Twitter (@ oleg008) wissen, über welches Sie mehr lesen möchten.