Die vielen Möglichkeiten, CSS in JavaScript-Anwendungen einzubinden

Avatar of Dominic Magnifico
Dominic Magnifico am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Willkommen zu einem unglaublich kontroversen Thema im Bereich der Front-End-Entwicklung! Ich bin sicher, dass die Mehrheit von Ihnen, die dies lesen, ihre fairen Anteile an #heißemDrama bezüglich der Frage erlebt hat, wie CSS in einer JavaScript-Anwendung gehandhabt werden sollte.

Ich möchte diesen Beitrag mit einer Klarstellung beginnen: Es gibt keine feste Regel, die eine Methode zur Handhabung von CSS in einer React-, Vue- oder Angular-Anwendung als überlegen festlegt. Jedes Projekt ist anders, und jede Methode hat ihre Vorzüge! Das mag vage erscheinen, aber was ich weiß, ist, dass die Entwicklungsgemeinschaft, in der wir existieren, voller Menschen ist, die ständig nach neuen Informationen suchen und das Web auf sinnvolle Weise voranbringen wollen.

Lassen Sie uns abgesehen von vorgefassten Meinungen zu diesem Thema in die faszinierende Welt der CSS-Architektur eintauchen!

Zählen wir die Wege

Ein einfacher Google-Suchlauf nach „Wie füge ich CSS zu [Framework einfügen] hinzu“ liefert eine Flut starker Überzeugungen und Meinungen darüber, wie Stile einem Projekt zugewiesen werden sollten. Um etwas Klarheit in das Rauschen zu bringen, betrachten wir einige der häufiger genutzten Methoden auf hoher Ebene und ihren Zweck.

Option 1: Ein verdammt altes Stylesheet

Wir beginnen mit einem vertrauten Ansatz: einem verdammt alten Stylesheet. Wir können problemlos ein externes Stylesheet in unserer Anwendung mit <link> einbinden und das war's.

<link rel="stylesheet" href="styles.css">

Wir können normales CSS schreiben, an das wir gewöhnt sind, und weiterleben. Daran ist absolut nichts auszusetzen, aber je größer und komplexer eine Anwendung wird, desto schwieriger wird es, ein einziges Stylesheet zu pflegen. Das Parsen von Tausenden von CSS-Zeilen, die für das Styling Ihrer gesamten Anwendung verantwortlich sind, wird für jeden Entwickler, der an dem Projekt arbeitet, zur Qual. Der Cascade ist auch eine schöne Sache, aber er wird auch schwer zu handhaben, da einige Stile, die Sie – oder andere Entwickler im Projekt – schreiben, zu Regressionen in anderen Teilen der Anwendung führen. Wir haben diese Probleme schon früher erlebt, und Dinge wie Sass (und in jüngerer Zeit PostCSS) wurden eingeführt, um uns bei der Bewältigung dieser Probleme zu helfen.

Wir könnten diesen Weg weiterverfolgen und die großartige Leistung von PostCSS nutzen, um sehr modulare CSS-Partials zu schreiben, die über @import-Regeln miteinander verknüpft werden. Dies erfordert etwas Arbeit in der Webpack-Konfiguration, um richtig eingerichtet zu werden, aber etwas, das Sie auf jeden Fall bewältigen können!

Unabhängig davon, welchen Compiler Sie am Ende des Tages verwenden (oder nicht verwenden), werden Sie eine einzige CSS-Datei ausliefern, die alle Stile für Ihre Anwendung über ein <link>-Tag im Header enthält. Abhängig von der Komplexität der Anwendung kann diese Datei ziemlich aufgebläht werden, schwer asynchron zu laden und blockierend für den Rest Ihrer Anwendung sein. (Sicher, render-blocking ist nicht *immer* schlecht, aber wir verallgemeinern hier ein wenig und vermeiden render-blocking Skripte und Stile, wo immer wir können.)

Das soll nicht heißen, dass diese Methode keine Vorteile hat. Für eine kleine Anwendung oder eine Anwendung, die von einem Team mit weniger Fokus auf das Frontend entwickelt wurde, kann ein einzelnes Stylesheet eine gute Wahl sein. Es bietet eine klare Trennung zwischen Geschäftslogik und Anwendungsstilen, und da es nicht von unserer Anwendung generiert wird, haben wir die volle Kontrolle darüber, dass genau das, was wir schreiben, auch ausgegeben wird. Darüber hinaus ist eine einzelne CSS-Datei für den Browser recht einfach zu cachen, sodass wiederkehrende Benutzer die Datei bei ihrem nächsten Besuch nicht erneut herunterladen müssen.

Aber nehmen wir an, wir suchen nach einer robusteren CSS-Architektur, die es uns ermöglicht, die Leistung von Tools zu nutzen. Etwas, das uns hilft, eine Anwendung zu verwalten, die einen etwas nuancierteren Ansatz erfordert. Hier kommen CSS-Module ins Spiel.

Option 2: CSS-Module

Ein ziemlich großes Problem bei einem einzelnen Stylesheet ist das Risiko von Regressionen. Das Schreiben von CSS, das einen ziemlich unspezifischen Selektor verwendet, könnte eine andere Komponente in einem völlig anderen Bereich Ihrer Anwendung verändern. Hier ist ein Ansatz namens "Scoped Styles" hilfreich.

Scoped Styles ermöglichen es uns, Klassennamen programmgesteuert zu generieren, die spezifisch für eine Komponente sind. Somit werden diese Stile auf diese spezifische Komponente beschränkt, wodurch sichergestellt wird, dass ihre Klassennamen eindeutig sind. Dies führt zu automatisch generierten Klassennamen wie header__2lexd. Der Teil am Ende ist ein gehashter Selektor, der eindeutig ist. Selbst wenn Sie eine weitere Komponente namens Header hätten, könnten Sie ihr eine Header-Klasse zuweisen, und unsere Scoped Styles würden ein neues Hash-Suffix generieren, wie z. B.: header__15qy_.

CSS-Module bieten je nach Implementierung Möglichkeiten, den generierten Klassennamen zu steuern, aber das überlasse ich der Dokumentation von CSS-Modules.

Nach allem ist es immer noch eine einzelne CSS-Datei, die über ein <link>-Tag im Header an den Browser geliefert wird. Dies birgt die gleichen potenziellen Nachteile (Render-Blocking, aufgeblähte Dateigröße usw.) und einige der Vorteile (Caching, meistens), die wir oben besprochen haben. Aber diese Methode hat aufgrund ihres Zwecks, Stile zu isolieren, eine weitere Einschränkung: die Entfernung des globalen Geltungsbereichs – zumindest anfangs.

Stellen Sie sich vor, Sie möchten eine globale Klasse namens .screen-reader-text verwenden, die auf jede Komponente in Ihrer Anwendung angewendet werden kann. Wenn Sie CSS-Module verwenden, müssten Sie zum :global Pseudoselektor greifen, der das darin enthaltene CSS explizit als etwas definiert, das global von anderen Komponenten in der App zugänglich ist. Solange Sie das Stylesheet importieren, das Ihren :global-Deklarationsblock enthält, in das Stylesheet Ihrer Komponente, haben Sie Zugriff auf diesen globalen Selektor. Kein großer Nachteil, aber etwas, an das man sich gewöhnen muss.

Hier ist ein Beispiel für den :global Pseudoselektor in Aktion

// typography.css
:global {
  .aligncenter {
    text-align: center;
  }
  .alignright {
    text-align: right;
  }
  .alignleft {
    text-align: left;
  }
}

Sie könnten Gefahr laufen, eine ganze Menge globaler Selektoren für Typografie, Formulare und allgemeine Elemente, die die meisten Websites haben, in einen einzigen :global Selektor zu packen. Glücklicherweise können Sie dank der Magie von Dingen wie PostCSS Nested oder Sass Partials direkt in den Selektor importieren, um die Dinge etwas sauberer zu gestalten.

// main.scss
:global {
  @import "typography";
  @import "forms";
}

Auf diese Weise können Sie Ihre Partials ohne den :global Selektor schreiben und sie direkt in Ihr Haupt-Stylesheet importieren.

Eine weitere Sache, an die man sich gewöhnen muss, ist, wie Klassennamen innerhalb von DOM-Knoten referenziert werden. Ich überlasse die individuellen Dokumentationen für Vue, React und Angular für sich selbst sprechen. Ich werde Ihnen auch ein kleines Beispiel hinterlassen, wie diese Klassennamen in einer React-Komponente verwendet aussehen.

// ./css/Button.css

.btn {
  background-color: blanchedalmond;
  font-size: 1.4rem;
  padding: 1rem 2rem;
  text-transform: uppercase;
  transition: background-color ease 300ms, border-color ease 300ms;

  &:hover {
    background-color: #000;
    color: #fff;
  }
}

// ./Button.js

import styles from "./css/Button.css";

const Button = () => (
  <button className={styles.btn}>
    Click me!
  </button>
);

export default Button;

Die CSS-Module-Methode hat wieder einige großartige Anwendungsfälle. Für Anwendungen, die isolierte Stile nutzen möchten, während sie die Leistungsvorteile eines statischen, aber kompilierten Stylesheets beibehalten, sind CSS-Module möglicherweise die richtige Wahl für Sie!

Es ist hier auch erwähnenswert, dass CSS-Module mit Ihrer bevorzugten Art von CSS-Präprozessoren kombiniert werden können. Sass, Less, PostCSS usw. können alle in den Build-Prozess integriert werden, indem CSS-Module verwendet werden.

Aber nehmen wir an, Ihre Anwendung könnte davon profitieren, wenn sie in Ihren JavaScript integriert wird. Vielleicht wäre es auch von Vorteil, Zugang zu den verschiedenen Zuständen Ihrer Komponenten zu erhalten und basierend auf dem sich ändernden Zustand zu reagieren. Nehmen wir an, Sie möchten auch Critical CSS einfach in Ihre Anwendung integrieren können! Hier kommt CSS-in-JS ins Spiel.

Option 3: CSS-in-JS

CSS-in-JS ist ein ziemlich breites Thema. Es gibt mehrere Pakete, die darauf abzielen, das Schreiben von CSS-in-JS so schmerzfrei wie möglich zu gestalten. Frameworks wie JSS, Emotion und Styled Components sind nur einige der vielen Pakete, die dieses Thema umfassen.

Als grobe Erklärung für die meisten dieser Frameworks funktioniert CSS-in-JS weitgehend gleich. Sie schreiben CSS, das mit Ihrer einzelnen Komponente verknüpft ist, und Ihr Build-Prozess kompiliert die Anwendung. Wenn dies geschieht, geben die meisten CSS-in-JS-Frameworks das zugehörige CSS von *nur den Komponenten aus, die zu einem bestimmten Zeitpunkt auf der Seite gerendert werden*. CSS-in-JS-Frameworks tun dies, indem sie dieses CSS in einem <style>-Tag im <head> Ihrer Anwendung ausgeben. Dies bietet Ihnen standardmäßig eine Critical-CSS-Lade strategie! Zusätzlich sind die Stile, ähnlich wie bei CSS-Modulen, isoliert und die Klassennamen sind gehasht.

Während Sie durch Ihre Anwendung navigieren, werden die unmontierten Komponenten ihre Stile aus dem <head> entfernt, und Ihre neu hinzukommenden Komponenten, die montiert werden, erhalten ihre Stile an ihrer Stelle eingefügt. Dies bietet Leistungsvorteile für Ihre Anwendung. Es eliminiert eine HTTP-Anfrage, ist nicht render-blockierend und stellt sicher, dass Ihre Benutzer nur das herunterladen, was sie zu einem bestimmten Zeitpunkt zum Anzeigen der Seite benötigen.

Eine weitere interessante Möglichkeit, die CSS-in-JS bietet, ist die Fähigkeit, verschiedene Komponentenzustände und Funktionen zu referenzieren, um unterschiedliche CSS-Codes zu rendern. Dies kann so einfach sein wie die Nachbildung eines Klassenwechsels basierend auf einer Zustandsänderung oder so komplex wie Theming.

Da CSS-in-JS ein ziemlich #hotdrama-Thema ist, habe ich festgestellt, dass es viele verschiedene Wege gibt, wie Leute versuchen, damit umzugehen. Ich teile die Gefühle vieler anderer Leute, die CSS hoch schätzen, besonders wenn es darum geht, JavaScript zum Schreiben von CSS zu nutzen. Meine anfänglichen Reaktionen auf diesen Ansatz waren ziemlich negativ. Ich mochte die Idee nicht, die beiden zu kreuzkontaminieren. Aber ich wollte aufgeschlossen bleiben. Schauen wir uns einige der Funktionen an, die wir als frontend-orientierte Entwickler *brauchen* würden, um diesen Ansatz überhaupt in Betracht zu ziehen.

  • Wenn wir CSS-in-JS schreiben, müssen wir echtes CSS schreiben. Mehrere Pakete bieten Möglichkeiten, Template-Literal-CSS zu schreiben, verlangen aber, dass Sie Ihre Eigenschaften in CamelCase schreiben – d. h. padding-left wird zu paddingLeft. Das ist nichts, was ich persönlich opfern möchte.
  • Einige CSS-in-JS-Lösungen verlangen, dass Sie Ihre Stile inline auf dem Element schreiben, das Sie stylen möchten. Die Syntax dafür, besonders in komplexen Komponenten, wird sehr hektisch und ist wiederum nichts, was ich opfern möchte.
  • Die Verwendung von CSS-in-JS muss mir leistungsstarke Tools bieten, die mit CSS-Modulen oder einem verdammt alten Stylesheet sonst super schwierig zu erreichen wären.
  • Wir müssen fortschrittliches CSS wie Nesting und Variablen nutzen können. Wir müssen auch Dinge wie Autoprefixer und andere Add-ons integrieren können, um die Entwicklererfahrung zu verbessern.

Das ist viel verlangt von einem Framework, aber für diejenigen von uns, die die meiste Zeit ihres Lebens damit verbracht haben, Lösungen rund um eine Sprache zu studieren und zu implementieren, die wir lieben, müssen wir sicherstellen, dass wir diese gleiche Sprache so gut wie möglich weiter schreiben können.

Hier ist ein kurzer Einblick, wie eine React-Komponente aussehen könnte, die Styled Components verwendet

// ./Button.js
import styled from 'styled-components';

const StyledButton= styled.button`
  background-color: blanchedalmond;
  font-size: 1.4rem;
  padding: 1rem 2rem;
  text-transform: uppercase;
  transition: background-color ease 300ms, border-color ease 300ms;

  &:hover {
    background-color: #000;
    color: #fff;
  }
`;

const Button = () => (
  <StyledButton>
    Click Me!
  </StyledButton>
);

export default Button;

Wir müssen auch die potenziellen Nachteile einer CSS-in-JS-Lösung ansprechen – und definitiv nicht als Versuch, mehr Drama zu erzeugen. Bei einer solchen Methode ist es unglaublich einfach, in eine Falle zu tappen, die zu einer aufgeblähten JavaScript-Datei mit potenziell Hunderten von CSS-Zeilen führt – und das alles, bevor der Entwickler überhaupt die Methoden der Komponente oder ihre HTML-Struktur sieht. Wir können dies jedoch als Gelegenheit nutzen, genau zu untersuchen, wie und warum wir Komponenten auf die von uns gewählte Weise erstellen. Wenn wir etwas tiefer darüber nachdenken, können wir es zu unserem Vorteil nutzen und schlankeren Code mit wiederverwendbareren Komponenten schreiben.

Darüber hinaus verwischt diese Methode eindeutig die Grenze zwischen Geschäftslogik und Anwendungsstilen. Mit einer gut dokumentierten und gut durchdachten Architektur können andere Entwickler im Projekt jedoch leichter in diese Idee eingeführt werden, ohne sich überfordert zu fühlen.

TL;DR

Es gibt mehrere Möglichkeiten, das Biest CSS-Architektur in jedem Projekt zu handhaben und dies bei Verwendung jedes beliebigen Frameworks zu tun. Die Tatsache, dass wir als Entwickler *so viele Möglichkeiten* haben, ist sowohl aufregend als auch überwältigend. Das übergreifende Thema, das meiner Meinung nach in kurzen Social-Media-Gesprächen verloren geht, ist jedoch, dass jede Lösung ihre eigenen Vor- und Nachteile hat. Es geht darum, wie wir sorgfältig und durchdacht ein System implementieren, das unsere zukünftigen Ichs und/oder andere Entwickler, die den Code anfassen, dazu bringt, uns dafür zu danken, dass wir uns die Zeit genommen haben, diese Struktur zu etablieren.