style9: build-time CSS-in-JS

Avatar of Johan Holmerin
Johan Holmerin am

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

Im April letzten Jahres stellte Facebook sein großes neues Redesign vor. Ein ehrgeiziges Projekt, es war ein Relaunch einer großen Website mit einer riesigen Nutzerzahl. Um dies zu erreichen, nutzten sie mehrere Technologien, die sie selbst entwickelt und als Open Source veröffentlicht haben, wie React, GraphQL, Relay und eine neue CSS-in-JS-Bibliothek namens stylex.

Diese neue Bibliothek ist intern bei Facebook, aber sie haben genug Informationen darüber geteilt, um eine Open-Source-Implementierung namens style9 zu ermöglichen.

Warum eine weitere CIJ-Bibliothek?

Es gibt bereits viele CSS-in-JS (CIJ)-Bibliotheken, daher ist es vielleicht nicht offensichtlich, warum eine weitere benötigt wird. style9 bietet die gleichen Vorteile wie alle anderen CIJ-Lösungen, wie von Christopher Chedeau dargelegt, darunter gescoped Selektoren, Eliminierung von totem Code, deterministische Auflösung und die Möglichkeit, Werte zwischen CSS und JavaScript zu teilen.

Es gibt jedoch einige Dinge, die style9 einzigartig machen.

Minimaler Laufzeit-Code

Obwohl die Stile in JavaScript definiert sind, werden sie vom Compiler in eine normale CSS-Datei extrahiert. Das bedeutet, dass keine Stile in Ihrer endgültigen JavaScript-Datei ausgeliefert werden. Übrig bleiben nur die finalen Klassennamen, die der minimale Laufzeit-Code bedingt anwendet, genau wie Sie es normalerweise tun würden. Dies führt zu kleineren Code-Bundles, einer Reduzierung des Speicherverbrauchs und schnellerem Rendering.

Da die Werte zur Kompilierungszeit extrahiert werden, können dynamische Werte nicht verwendet werden. Diese sind glücklicherweise nicht sehr verbreitet und da sie einzigartig sind, leiden sie nicht darunter, inline definiert zu werden. Häufiger kommt die bedingte Anwendung von Stilen vor, die natürlich unterstützt wird. Ebenso lokale Konstanten und mathematische Ausdrücke, dank path.evaluate von Babel.

Atomare Ausgabe

Aufgrund der Funktionsweise von style9 kann jede Eigenschaftsdeklaration zu ihrer eigenen Klasse mit einer einzigen Eigenschaft werden. Wenn wir beispielsweise opacity: 0 an mehreren Stellen in unserem Code verwenden, existiert sie in der generierten CSS-Datei nur einmal. Der Vorteil hierbei ist, dass die CSS-Datei mit der Anzahl der *einzigartigen Deklarationen* wächst, nicht mit der Gesamtzahl der Deklarationen. Da die meisten Eigenschaften mehrmals verwendet werden, kann dies zu dramatisch kleineren CSS-Dateien führen. Beispielsweise benötigte die alte Facebook-Homepage 413 KB gzip-komprimiertes CSS. Das Redesign benötigt 74 KB für *alle* Seiten. Auch hier führt eine kleinere Dateigröße zu besserer Leistung.

Folie von Building the New Facebook with React and Relay von Frank Yan, bei 13:23, die die logarithmische Skala von atomarem CSS zeigt.

Manche mögen sich darüber beschweren, dass die generierten Klassennamen nicht semantisch sind, dass sie opak sind und die Kaskade ignorieren. Das stimmt. Wir behandeln CSS als Kompilierungsziel. Aber aus gutem Grund. Indem wir bisher als selbstverständlich angesehene Best Practices in Frage stellen, können wir sowohl die Benutzer- als auch die Entwicklererfahrung verbessern.

Zusätzlich bietet style9 viele weitere großartige Funktionen, darunter: typisierte Stile mit TypeScript, Eliminierung ungenutzter Stile, die Möglichkeit, JavaScript-Variablen zu verwenden, und Unterstützung für Media Queries, Pseudo-Selektoren und Keyframes.

Hier ist die Anwendung

Zuerst installieren Sie es wie üblich

npm install style9

style9 verfügt über Plugins für Rollup, Webpack, Gatsby und Next.js, die alle auf einem Babel-Plugin basieren. Anleitungen zur Verwendung finden Sie im Repository. Hier verwenden wir das Webpack-Plugin.

const Style9Plugin = require('style9/webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      // This will transform the style9 calls
      {
        test: /\.(tsx|ts|js|mjs|jsx)$/,
        use: Style9Plugin.loader
      },
      // This is part of the normal Webpack CSS extraction
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    // This will sort and remove duplicate declarations in the final CSS file
    new Style9Plugin(),
    // This is part of the normal Webpack CSS extraction
    new MiniCssExtractPlugin()
  ]
};

Definition von Stilen

Die Syntax zum Erstellen von Stilen ähnelt stark anderen Bibliotheken. Wir beginnen mit dem Aufruf von style9.create mit Objekten von Stilen.

import style9 from 'style9';

const styles = style9.create({
  button: {
    padding: 0,
    color: 'rebeccapurple'
  },
  padding: {
    padding: 12
  },
  icon: {
    width: 24,
    height: 24
  }
});

Da alle Deklarationen zu atomaren Klassen führen, funktionieren Kurzschreibweisen wie flex: 1 und background: blue nicht, da sie mehrere Eigenschaften setzen. Eigenschaften, die erweitert werden können, wie padding, margin, overflow usw., werden automatisch in ihre Langform-Varianten umgewandelt. Wenn Sie TypeScript verwenden, erhalten Sie einen Fehler bei der Verwendung nicht unterstützter Eigenschaften.

Auflösen von Stilen

Um einen Klassennamen zu generieren, können wir jetzt die von style9.create zurückgegebene Funktion aufrufen. Sie akzeptiert als Argumente die Schlüssel der Stile, die wir verwenden möchten.

const className = styles('button');

Die Funktion funktioniert so, dass Stile auf der rechten Seite Vorrang haben und mit den Stilen auf der linken Seite zusammengeführt werden, ähnlich wie bei Object.assign. Das Folgende würde zu einem Element mit einem Padding von 12px und einer rebeccapurple-Textfarbe führen.

const className = styles('button', 'padding');

Wir können Stile bedingt mit einem der folgenden Formate anwenden.

// logical AND
styles('button', hasPadding && 'padding');
// ternary
styles('button', isGreen ? 'green' : 'red');
// object of booleans
styles({
  button: true,
  green: isGreen,
  padding: hasPadding
});

Diese Funktionsaufrufe werden während der Kompilierung entfernt und durch direkte String-Verkettung ersetzt. Die erste Zeile im obigen Code wird durch etwas wie 'c1r9f2e5 ' + hasPadding ? 'cu2kwdz ' : '' ersetzt. Es bleibt kein Laufzeit-Code übrig.

Kombination von Stilen

Wir können ein Style-Objekt erweitern, indem wir es mit einem Eigenschaftsnamen ansprechen und es an style9 übergeben.

const styles = style9.create({ blue: { color: 'blue; } });
const otherStyles = style9.create({ red: { color: 'red; } });

// will be red
const className = style9(styles.blue, otherStyles.red);

Ähnlich wie bei Funktionsaufrufen haben die Stile auf der rechten Seite Vorrang. In diesem Fall kann der Klassenname jedoch nicht statisch aufgelöst werden. Stattdessen werden die Eigenschaftswerte durch Klassen ersetzt und zur Laufzeit zusammengeführt. Die Eigenschaften werden wie zuvor zur CSS-Datei hinzugefügt.

Zusammenfassung

Die Vorteile von CSS-in-JS sind sehr real. Dennoch entstehen Leistungskosten, wenn wir Stile in unseren Code einbetten. Indem wir die Werte zur Build-Zeit extrahieren, können wir das Beste aus beiden Welten haben. Wir profitieren von der Zusammenlegung unserer Stile mit unserem Markup und der Möglichkeit, die vorhandene JavaScript-Infrastruktur zu nutzen, während wir gleichzeitig optimale Stylesheets generieren können.

Wenn style9 für Sie interessant klingt, werfen Sie einen Blick auf das Repository und probieren Sie es aus. Wenn Sie Fragen haben, öffnen Sie gerne ein Issue oder kontaktieren Sie uns.

Danksagungen

Dank an Giuseppe Gurgone für seine Arbeit an style-sheet und dss, Nicolas Gallagher für react-native-web, Satyajit Sahoo und alle bei Callstack für linaria, Christopher Chedeau, Sebastian McKenzie, Frank Yan, Ashley Watkins, Naman Goel und alle anderen, die an stylex bei Facebook gearbeitet haben, dafür, dass sie ihre Erkenntnisse öffentlich zugänglich gemacht haben. Und allen anderen, die ich vielleicht übersehen habe.