Überbrückung der Lücke zwischen CSS und JavaScript: CSS-in-JS

Avatar of Matija Marohnić
Matija Marohnić am

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

In diesem Artikel tauchen wir in das Konzept von CSS-in-JS ein. Wenn Sie mit diesem Konzept bereits vertraut sind, werden Sie vielleicht trotzdem einen Spaziergang durch die Philosophie dieses Ansatzes genießen, und Sie werden vielleicht sogar noch mehr an dem nächsten Artikel interessiert sein.

Webentwicklung ist sehr interdisziplinär. Wir sind es gewohnt, eng mit mehreren Sprachen zusammenzuarbeiten. Und da die Entwicklung von Webanwendungen immer üblicher und nuancierter wird, suchen wir oft nach kreativen Wegen, um die Lücken zwischen diesen Sprachen zu überbrücken, um unsere Entwicklungsumgebungen und Arbeitsabläufe einfacher und effizienter zu gestalten.

Die häufigsten Beispiele sind typischerweise bei der Verwendung von Templating-Sprachen. Beispielsweise kann eine Sprache verwendet werden, um den Code einer umfangreicheren Sprache (oft HTML) zu generieren. Dies ist einer der Schlüsselaspekte von Front-End-Frameworks – wie sieht die Manipulation von HTML aus? Der jüngste Twist in diesem Bereich war JSX, weil es keine echte Templating-Sprache ist; es ist eine Syntaxerweiterung für JavaScript, und es macht die Arbeit mit HTML wirklich prägnant.

Webanwendungen durchlaufen viele Zustandsübergänge, und es ist oft schwierig, Inhalte allein zu verwalten. Deshalb fällt CSS manchmal durchs Raster – obwohl die Verwaltung von Stilen über verschiedene Zustände und Medienabfragen genauso wichtig und ebenso herausfordernd ist. In dieser zweiteiligen Serie möchte ich CSS ins Rampenlicht rücken und die Überbrückung der Lücke zwischen ihm und JavaScript untersuchen. Während dieser Serie gehe ich davon aus, dass Sie einen Modul-Bundler wie webpack verwenden. Daher werde ich in meinen Beispielen React verwenden, aber dieselben oder ähnliche Prinzipien sind auf andere JavaScript-Frameworks, einschließlich Vue, anwendbar.

Die CSS-Landschaft entwickelt sich in viele Richtungen, weil es viele Herausforderungen zu lösen gibt und es keinen „richtigen“ Weg gibt. Ich habe viel Mühe darauf verwendet, verschiedene Ansätze auszuprobieren, hauptsächlich in persönlichen Projekten, daher ist die Absicht hinter dieser Serie nur, zu **informieren**, nicht vorzuschreiben.

Herausforderungen von CSS

Bevor wir uns mit dem Code befassen, ist es erwähnenswert, die bemerkenswertesten Herausforderungen beim Stylen von Webanwendungen zu erläutern. Diejenigen, über die ich in dieser Serie sprechen werde, sind Scoping, bedingte und dynamische Stile sowie Wiederverwendbarkeit.

Scoping

Scoping ist eine bekannte CSS-Herausforderung, es ist die Idee, Stile zu schreiben, die nicht aus der Komponente herauslecken und somit unbeabsichtigte Nebenwirkungen vermeiden. Wir möchten dies idealerweise erreichen, ohne die Authoring-Erfahrung zu beeinträchtigen.

Bedingte und dynamische Stile

Während der Zustand in Front-End-Anwendungen immer fortschrittlicher wurde, war CSS immer noch statisch. Wir konnten nur Stile *bedingt* anwenden – wenn ein Button primär war, würden wir wahrscheinlich die Klasse „primary“ anwenden und seine Stile in einer separaten CSS-Datei definieren, um anzuwenden, wie er auf dem Bildschirm aussehen würde. Eine Handvoll vordefinierter Button-Variationen war überschaubar, aber was ist, wenn wir eine Vielzahl von Buttons haben möchten, wie z. B. spezielle für Twitter, Facebook, Pinterest und wer weiß was noch? Was wir wirklich tun wollen, ist einfach eine Farbe zu übergeben und Zustände mit CSS wie Hover, Fokus, deaktiviert usw. zu definieren. Dies nennt man *dynamisches* Styling, da wir nicht mehr zwischen vordefinierten Stilen wechseln – wir wissen nicht, was als Nächstes kommt. Inline-Stile könnten einem in den Sinn kommen, um dieses Problem zu lösen, aber sie unterstützen keine Pseudoklassen, Attributselektoren, Media Queries oder ähnliches.

Wiederverwendbarkeit

Die Wiederverwendung von Regelsätzen, Media Queries usw. ist ein Thema, das ich in letzter Zeit selten erwähnt sehe, da es von Präprozessoren wie Sass und Less gelöst wurde. Aber ich möchte es in dieser Serie immer noch aufgreifen.

Ich werde in beiden Teilen dieser Serie einige Techniken zur Bewältigung dieser Herausforderungen zusammen mit ihren Einschränkungen auflisten. Keine Technik ist den anderen überlegen und sie schließen sich nicht einmal gegenseitig aus; Sie können eine auswählen oder sie kombinieren, je nachdem, was Sie entscheiden, die Qualität Ihres Projekts verbessert.

Setup

Wir werden verschiedene Styling-Techniken anhand einer Beispielkomponente namens Photo demonstrieren. Wir werden ein responsives Bild rendern, das möglicherweise abgerundete Ecken hat und alternativen Text als Bildunterschrift anzeigt. Es wird wie folgt verwendet:

<Photo publicId="balloons" alt="Hot air balloons!" rounded />

Bevor wir die eigentliche Komponente erstellen, werden wir das Attribut srcSet abstrahieren, um den Beispielcode kurz zu halten. Erstellen wir also eine Datei utils.js mit zwei Hilfsprogrammen zur Generierung von Bildern unterschiedlicher Breite unter Verwendung von Cloudinary

import { Cloudinary } from 'cloudinary-core'

const cl = Cloudinary.new({ cloud_name: 'demo', secure: true })

export const getSrc = ({ publicId, width }) =>
  cl.url(publicId, { crop: 'scale', width })

export const getSrcSet = ({ publicId, widths }) => widths
  .map(width => `${getSrc({ publicId, width })} ${width}w`)
  .join(', ')

Wir richten unsere Cloudinary-Instanz so ein, dass sie den Namen der Demo-Cloud von Cloudinary sowie ihre url-Methode verwendet, um URLs für die Bild-publicId gemäß den angegebenen Optionen zu generieren. Wir sind nur daran interessiert, die Breite in dieser Komponente zu ändern.

Wir werden diese Hilfsprogramme für die Attribute src bzw. srcset verwenden

getSrc({ publicId: 'balloons', width: 200 })
// => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons'

getSrcSet({ publicId: 'balloons', widths: [200, 400] })
// => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons 200w,
      https://res.cloudinary.com/demo/image/upload/c_scale,w_400/balloons 400w'

Wenn Sie mit den Attributen srcset und sizes nicht vertraut sind, empfehle ich Ihnen, zuerst etwas über responsive Bilder zu lesen. Auf diese Weise wird es Ihnen leichter fallen, den Beispielen zu folgen.

CSS-in-JS

CSS-in-JS ist ein Styling-Ansatz, der das CSS-Modell auf die Komponentenebene abstrahiert, anstatt auf die Dokumentenebene. Die Idee ist, dass CSS auf eine bestimmte Komponente – und nur diese Komponente – so gescoped werden kann, dass diese spezifischen Stile nicht mit anderen Komponenten geteilt oder auf sie übertragen werden und weiter nur dann aufgerufen werden, wenn sie benötigt werden. CSS-in-JS-Bibliotheken erstellen Stile zur Laufzeit, indem sie <style>-Tags in den <head> einfügen.

Eine der ersten Bibliotheken, die dieses Konzept nutzte, ist JSS. Hier ist ein Beispiel, das seine Syntax verwendet

import React from 'react'
import injectSheet from 'react-jss'
import { getSrc, getSrcSet } from './utils'

const styles = {
  photo: {
    width: 200,
    '@media (min-width: 30rem)': {
      width: 400,
    },
    borderRadius: props => (props.rounded ? '1rem' : 0),
  },
}

const Photo = ({ classes, publicId, alt }) => (
  <figure>
    <img
      className={classes.photo}
      src={getSrc({ publicId, width: 200 })}
      srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}
      sizes="(min-width: 30rem) 400px, 200px"
    />
    <figcaption>{alt}</figcaption>
  </figure>
)
Photo.defaultProps = {
  rounded: false,
}

export default injectSheet(styles)(Photo)

Auf den ersten Blick sieht das styles-Objekt wie CSS in Objektnotation mit zusätzlichen Funktionen aus, z. B. dem Übergeben einer Funktion, um den Wert basierend auf Props festzulegen. Die generierten Klassen sind eindeutig, sodass Sie sich nie Gedanken über Kollisionen mit anderen Stilen machen müssen. Mit anderen Worten, Sie erhalten Scoping kostenlos! So funktionieren die meisten CSS-in-JS-Bibliotheken – natürlich mit einigen Abweichungen bei Funktionen und Syntax, die wir im Laufe der Zeit behandeln werden.

Anhand der Attribute können Sie sehen, dass die Breite unseres gerenderten Bildes bei 200px beginnt, und wenn die Viewport-Breite mindestens 30rem beträgt, erhöht sich die Breite auf 400px. Wir haben zusätzliche 800 Quellen generiert, um auch höhere Bildschirmdichten abzudecken.

  • 1x-Bildschirme verwenden 200 und 400
  • 2x-Bildschirme verwenden 400 und 800

styled-components ist eine weitere CSS-in-JS-Bibliothek, aber mit einer viel vertrauteren Syntax, die clevere Tagged Template Literals anstelle von Objekten verwendet, um eher wie CSS auszusehen.

import React from 'react'
import styled, { css } from 'styled-components'
import { getSrc, getSrcSet } from './utils'

const mediaQuery = '(min-width: 30rem)'

const roundedStyle = css`
  border-radius: 1rem;
`

const Image = styled.img`
  width: 200px;
  @media ${mediaQuery} {
    width: 400px;
  }
  ${props => props.rounded && roundedStyle};
`
  
const Photo = ({ publicId, alt, rounded }) => (
  <figure>
    <Image
      src={getSrc({ publicId, width: 200 })}
      srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })}
      sizes={`${mediaQuery} 400px, 200px`}
      rounded={rounded}
    />
    <figcaption>{alt}</figcaption>
  </figure>
)
Photo.defaultProps = {
  rounded: false,
}

export default Photo

Wir erstellen oft semantisch neutrale Elemente wie <div> und <span> nur zu Styling-Zwecken. Diese Bibliothek und viele andere erlauben uns, diese in einem einzigen Schritt zu erstellen und zu stylen.

Mein liebster Vorteil dieser Syntax ist, dass sie wie normales CSS ist, abzüglich der Interpolationen. Das bedeutet, dass wir unseren CSS-Code leichter migrieren können und unsere vorhandene Muskelgedächtnis nutzen können, anstatt uns mit dem Schreiben von CSS in Objektnotation vertraut machen zu müssen.

Beachten Sie, dass wir fast alles in unsere Stile interpolieren können. Dieses spezielle Beispiel zeigt, wie wir die Media-Query in der Variable speichern und an mehreren Stellen wiederverwenden können. Responsive Bilder sind dafür ein hervorragendes Anwendungsbeispiel, da das sizes-Attribut im Grunde CSS enthält, sodass wir JavaScript verwenden können, um den Code DRYer zu machen.

Nehmen wir an, wir haben beschlossen, die Bildunterschrift visuell zu verstecken, sie aber dennoch für Screenreader zugänglich zu machen. Ich weiß, dass eine bessere Möglichkeit, dies zu erreichen, die Verwendung eines alt-Attributs wäre, aber lassen Sie uns zu diesem Beispiel einen anderen Weg gehen. Wir können eine Bibliothek von Style-Mixins namens polished verwenden – sie funktioniert hervorragend mit CSS-in-JS-Bibliotheken und ist daher großartig für unser Beispiel. Diese Bibliothek enthält einen Mixin namens hideVisually, der genau das tut, was wir wollen, und wir können ihn verwenden, indem wir seinen Rückgabewert interpolieren.

import { hideVisually } from 'polished'

const Caption = styled.figcaption`
  ${hideVisually()};
`

<Caption>{alt}</Caption>

Auch wenn hideVisually ein Objekt ausgibt, weiß die styled-components-Bibliothek, wie sie es als Stile interpoliert.

CSS-in-JS-Bibliotheken verfügen über viele fortgeschrittene Funktionen wie Theming, Vendor-Prefixing und sogar das Inlining von kritischem CSS, was es einfach macht, auf das Schreiben von CSS-Dateien ganz zu verzichten. An diesem Punkt können Sie sehen, warum CSS-in-JS zu einem verlockenden Konzept wird.

Nachteile und Einschränkungen

Der offensichtliche Nachteil von CSS-in-JS ist, dass es eine Laufzeit einführt: Die Stile müssen über JavaScript geladen, analysiert und ausgeführt werden. Autoren von CSS-in-JS-Bibliotheken fügen alle möglichen intelligenten Optimierungen hinzu, wie z. B. Babel-Plugins, aber einige Laufzeitkosten werden trotzdem anfallen.

Es ist auch wichtig zu beachten, dass diese Bibliotheken nicht von PostCSS analysiert werden, da PostCSS nicht für die Laufzeit entwickelt wurde. Viele verwenden stattdessen stylis, da es viel schneller ist. Das bedeutet, dass wir leider keine PostCSS-Plugins verwenden können.

Der letzte Nachteil, den ich erwähnen werde, sind die Tools. CSS-in-JS entwickelt sich sehr schnell und Texteditor-Erweiterungen, Linter, Code-Formatierer usw. müssen mit neuen Funktionen Schritt halten, um auf dem neuesten Stand zu bleiben. Zum Beispiel verwenden Leute die VS Code-Erweiterung styled-components für ähnliche CSS-in-JS-Bibliotheken wie emotion, obwohl sie nicht alle dieselben Funktionen haben. Ich habe sogar API-Entscheidungen von vorgeschlagenen Funktionen gesehen, die von dem Ziel beeinflusst wurden, die Syntaxhervorhebung beizubehalten!

Die Zukunft

Es gibt zwei neue CSS-in-JS-Bibliotheken, Linaria und astroturf, die ohne Laufzeit auskommen, indem sie CSS in Dateien extrahieren. Ihre APIs sind ähnlich wie styled-components, aber sie unterscheiden sich in Funktionen und Zielen.

Das Ziel von Linaria ist es, die API von CSS-in-JS-Bibliotheken wie styled-components nachzuahmen, indem integrierte Funktionen wie Scoping, Nesting und Vendor-Prefixing vorhanden sind. Umgekehrt basiert astroturf auf CSS Modules, hat begrenzte Interpolationsmöglichkeiten und ermutigt zur Verwendung eines CSS-Ökosystems, anstatt auf JavaScript zurückzugreifen.

Ich habe Gatsby-Plugins für beide Bibliotheken erstellt, wenn Sie damit spielen möchten.

Zwei Dinge, die Sie bei der Verwendung dieser Bibliotheken beachten sollten

  1. tatsächliche CSS-Dateien zu haben bedeutet, dass wir sie mit vertrauten Werkzeugen wie PostCSS verarbeiten können.
  2. Linaria verwendet im Hintergrund benutzerdefinierte Eigenschaften (auch CSS-Variablen genannt). Berücksichtigen Sie unbedingt deren Browserunterstützung, bevor Sie diese Bibliothek verwenden.

Fazit

CSS-in-JS sind All-in-One-Styling-Lösungen zur Überbrückung der Lücke zwischen CSS und JavaScript. Sie sind einfach zu bedienen und enthalten nützliche integrierte Optimierungen – aber all das hat seinen Preis. Insbesondere durch die Verwendung von CSS-in-JS verlassen wir im Wesentlichen das CSS-Ökosystem und überlassen JavaScript die Lösung unserer Probleme.

Zero-Runtime-Lösungen mildern einige der Nachteile, indem sie die CSS-Werkzeuge zurückbringen, was die CSS-in-JS-Diskussion auf ein viel interessanteres Niveau hebt. Was sind die tatsächlichen Grenzen von Präprozessing-Tools im Vergleich zu CSS-in-JS? Dies wird im nächsten Teil dieser Serie behandelt.

Artikelserie

  1. CSS-in-JS (Dieser Beitrag)
  2. CSS Modules, PostCSS und die Zukunft von CSS