Einführung in Sass-Module

Avatar of Miriam Suzanne
Miriam Suzanne am

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

Sass hat gerade ein wichtiges neues Feature veröffentlicht, das Ihnen aus anderen Sprachen bekannt vorkommen mag: ein Modulsystem. Dies ist ein großer Schritt nach vorne für @import, eines der meistgenutzten Sass-Features. Während die aktuelle @import-Regel es Ihnen ermöglicht, Drittanbieter-Pakete einzubinden und Ihren Sass-Code in überschaubare „Partials“ zu unterteilen, hat sie einige Einschränkungen.

  • @import ist auch eine CSS-Funktion, und die Unterschiede können verwirrend sein.
  • Wenn Sie dieselbe Datei mehrmals @importieren, kann dies die Kompilierung verlangsamen, zu Konflikten bei Überschreibungen führen und doppelte Ausgaben erzeugen.
  • Alles befindet sich im globalen Namensraum, einschließlich Drittanbieter-Paketen – daher könnte meine color()-Funktion Ihre bestehende color()-Funktion überschreiben oder umgekehrt.
  • Wenn Sie eine Funktion wie color() verwenden, ist es unmöglich zu wissen, woher sie genau stammt. Aus welchem @import kommt sie?

Sass-Paketautoren (wie ich) haben versucht, Probleme mit dem Namensraum zu umgehen, indem sie unsere Variablen und Funktionen manuell mit Präfixen versehen haben – aber Sass-Module sind eine viel leistungsfähigere Lösung. Kurz gesagt, @import wird durch die expliziteren Regeln @use und @forward ersetzt. In den nächsten Jahren wird Sass @import veraltet sein und dann entfernt werden. Sie können weiterhin CSS-Imports verwenden, aber diese werden nicht von Sass kompiliert. Keine Sorge, es gibt ein Migrationswerkzeug, das Ihnen bei der Aktualisierung hilft!

Dateien mit @use importieren

@use 'buttons';

Das neue @use ist ähnlich wie @import, hat aber einige bemerkenswerte Unterschiede.

  • Die Datei wird nur einmal importiert, egal wie oft Sie sie in einem Projekt @usen.
  • Variablen, Mixins und Funktionen (was Sass als „Mitglieder“ bezeichnet), die mit einem Unterstrich (_) oder einem Bindestrich (-) beginnen, gelten als privat und werden nicht importiert.
  • Mitglieder aus der verwendeten Datei (in diesem Fall buttons.scss) werden nur lokal verfügbar gemacht, aber nicht an zukünftige Importe weitergegeben.
  • Ähnlich verhält es sich mit @extends: Sie gelten nur *entlang der Kette*; sie erweitern Selektoren in importierten Dateien, aber nicht Dateien, die diese importieren.
  • Alle importierten Mitglieder sind standardmäßig *namensgeschützt*.

Wenn wir eine Datei @usen, generiert Sass automatisch einen Namensraum, der auf dem Dateinamen basiert.

@use 'buttons'; // creates a `buttons` namespace
@use 'forms'; // creates a `forms` namespace

Wir haben jetzt Zugriff auf Mitglieder aus sowohl buttons.scss als auch forms.scss – aber dieser Zugriff wird nicht zwischen den Imports übertragen: forms.scss hat weiterhin keinen Zugriff auf die in buttons.scss definierten Variablen. Da die importierten Features namnesgeschützt sind, müssen wir eine neue, durch Punkte getrennte Syntax verwenden, um auf sie zuzugreifen.

// variables: <namespace>.$variable
$btn-color: buttons.$color;
$form-border: forms.$input-border;

// functions: <namespace>.function()
$btn-background: buttons.background();
$form-border: forms.border();

// mixins: @include <namespace>.mixin()
@include buttons.submit();
@include forms.input();

Wir können den Standard-Namensraum ändern oder entfernen, indem wir as <name> zum Import hinzufügen.

@use 'buttons' as *; // the star removes any namespace
@use 'forms' as f;

$btn-color: $color; // buttons.$color without a namespace
$form-border: f.$input-border; // forms.$input-border with a custom namespace

Die Verwendung von as * fügt dem Wurzel-Namensraum ein Modul hinzu, sodass kein Präfix erforderlich ist, aber diese Mitglieder bleiben lokal auf das aktuelle Dokument beschränkt.

Integrierte Sass-Module importieren

Interne Sass-Funktionen wurden ebenfalls in das Modulsystem integriert, sodass wir die vollständige Kontrolle über den globalen Namensraum haben. Es gibt mehrere integrierte Module – math, color, string, list, map, selector und meta – die in einer Datei explizit importiert werden müssen, bevor sie verwendet werden.

@use 'sass:math';
$half: math.percentage(1/2);

Sass-Module können auch in den globalen Namensraum importiert werden.

@use 'sass:math' as *;
$half: percentage(1/2);

Interne Funktionen, die bereits Präfixe hatten, wie map-get oder str-index, können ohne Verdopplung dieses Präfixes verwendet werden.

@use 'sass:map';
@use 'sass:string';
$map-get: map.get(('key': 'value'), 'key');
$str-index: string.index('string', 'i');

Eine vollständige Liste der integrierten Module, Funktionen und Namensänderungen finden Sie in der Sass-Modulspezifikation.

Neue und geänderte Kernfunktionen

Als Nebeneffekt bedeutet dies, dass Sass sicher neue interne Mixins und Funktionen hinzufügen kann, ohne Namenskonflikte zu verursachen. Das aufregendste Beispiel in dieser Version ist ein sass:meta-Mixin namens load-css(). Dieses funktioniert ähnlich wie @use, gibt aber nur generierte CSS-Ausgabe zurück und kann dynamisch überall in unserem Code verwendet werden.

@use 'sass:meta';
$theme-name: 'dark';

[data-theme='#{$theme-name}'] {
  @include meta.load-css($theme-name);
}

Das erste Argument ist eine Modul-URL (wie bei @use), kann aber dynamisch durch Variablen geändert werden und sogar Interpolation beinhalten, wie theme-#{$name}. Das zweite (optionale) Argument akzeptiert eine Map mit Konfigurationswerten.

// Configure the $base-color variable in 'theme/dark' before loading
@include meta.load-css(
  'theme/dark', 
  $with: ('base-color': rebeccapurple)
);

Das Argument $with akzeptiert Konfigurationsschlüssel und -werte für jede Variable im geladenen Modul, wenn diese beide

  • Eine globale Variable, die nicht mit _ oder - beginnt (jetzt zur Kennzeichnung der Privatsphäre verwendet)
  • Als !default-Wert markiert, zur Konfiguration
// theme/_dark.scss
$base-color: black !default; // available for configuration
$_private: true !default; // not available because private
$config: false; // not available because not marked as a !default

Beachten Sie, dass der Schlüssel 'base-color' die Variable $base-color setzen wird.

Es gibt zwei weitere neue sass:meta-Funktionen: module-variables() und module-functions(). Jede gibt eine Map mit Mitgliedernamen und -werten aus einem bereits importierten Modul zurück. Diese akzeptieren ein einzelnes Argument, das dem Modul-Namensraum entspricht.

@use 'forms';

$form-vars: module-variables('forms');
// (
//   button-color: blue,
//   input-border: thin,
// )

$form-functions: module-functions('forms');
// (
//   background: get-function('background'),
//   border: get-function('border'),
// )

Mehrere andere sass:meta-Funktionen – global-variable-exists(), function-exists(), mixin-exists() und get-function() – erhalten zusätzliche $module-Argumente, die es uns ermöglichen, jeden Namensraum explizit zu inspizieren.

Farben anpassen und skalieren

Das Modul sass:color hat auch einige interessante Besonderheiten, während wir versuchen, uns von einigen Altlasten zu lösen. Viele der alten Kurzformen wie lighten() oder adjust-hue() sind vorerst veraltet und werden durch explizite color.adjust() und color.scale() Funktionen ersetzt.

// previously lighten(red, 20%)
$light-red: color.adjust(red, $lightness: 20%);

// previously adjust-hue(red, 180deg)
$complement: color.adjust(red, $hue: 180deg);

Einige dieser alten Funktionen (wie adjust-hue) sind redundant und unnötig. Andere – wie lighten, darken, saturate und so weiter – müssen mit besserer interner Logik neu erstellt werden. Die ursprünglichen Funktionen basierten auf adjust(), das lineare Mathematik verwendet: 20% zur aktuellen Helligkeit von Rot in unserem obigen Beispiel hinzufügen. In den meisten Fällen möchten wir die Helligkeit prozentual relativ zum aktuellen Wert scale()n.

// 20% of the distance to white, rather than current-lightness + 20
$light-red: color.scale(red, $lightness: 20%);

Sobald diese Kurzform-Funktionen vollständig veraltet und entfernt sind, werden sie schließlich in sass:color mit neuem Verhalten erscheinen, das auf color.scale() anstatt auf color.adjust() basiert. Dies geschieht in Etappen, um abrupte abwärtskompatible Änderungen zu vermeiden. In der Zwischenzeit empfehle ich, Ihren Code manuell zu überprüfen, um zu sehen, wo color.scale() für Sie besser funktionieren könnte.

Importierte Bibliotheken konfigurieren

Drittanbieter- oder wiederverwendbare Bibliotheken werden oft mit globalen Standardkonfigurationsvariablen geliefert, die Sie überschreiben können. Früher haben wir das mit Variablen vor einem Import getan.

// _buttons.scss
$color: blue !default;

// old.scss
$color: red;
@import 'buttons';

Da verwendete Module keinen Zugriff mehr auf lokale Variablen haben, benötigen wir eine neue Möglichkeit, diese Standardwerte festzulegen. Das können wir tun, indem wir eine Konfigurationsmap zu @use hinzufügen.

@use 'buttons' with (
  $color: red,
  $style: 'flat',
);

Dies ähnelt dem $with-Argument in load-css(), aber anstatt Variablennamen als Schlüssel zu verwenden, verwenden wir die Variable selbst, beginnend mit $.

Ich liebe, wie explizit dies die Konfiguration macht, aber es gibt eine Regel, die mich mehrmals gestolpert hat: Ein Modul kann nur einmal konfiguriert werden, beim ersten Mal, wenn es verwendet wird. Die Reihenfolge der Imports war schon immer wichtig für Sass, selbst mit @import, aber diese Probleme sind leise fehlgeschlagen. Jetzt erhalten wir einen expliziten Fehler, was sowohl gut als auch manchmal überraschend ist. Stellen Sie sicher, dass Sie Bibliotheken zuerst in jeder „Einstiegsdatei“ (dem zentralen Dokument, das alle Partials importiert) @usen und konfigurieren, damit diese Konfigurationen vor anderen @uses der Bibliotheken kompiliert werden.

Es ist (derzeit) unmöglich, Konfigurationen zu „verketteten“, während sie bearbeitbar bleiben, aber Sie können ein konfiguriertes Modul zusammen mit Erweiterungen wrappen und dieses als neues Modul weitergeben.

Dateien mit @forward weiterleiten

Wir müssen eine Datei nicht immer verwenden und auf ihre Mitglieder zugreifen. Manchmal möchten wir sie einfach an zukünftige Importe weiterleiten. Nehmen wir an, wir haben mehrere formularbezogene Partials und möchten sie alle zusammen als einen einzigen Namensraum importieren. Das können wir mit @forward tun.

// forms/_index.scss
@forward 'input';
@forward 'textarea';
@forward 'select';
@forward 'buttons';

Mitglieder der weitergeleiteten Dateien sind im aktuellen Dokument nicht verfügbar und es wird kein Namensraum erstellt, aber diese Variablen, Funktionen und Mixins werden verfügbar sein, wenn eine andere Datei die gesamte Sammlung @usen oder @forwarden möchte. Wenn die weitergeleiteten Partials tatsächliches CSS enthalten, wird dies ebenfalls weitergegeben, ohne dass eine Ausgabe generiert wird, bis das Paket verwendet wird. Zu diesem Zeitpunkt wird alles als ein einziges Modul mit einem einzigen Namensraum behandelt.

// styles.scss
@use 'forms'; // imports all of the forwarded members in the `forms` namespace

Hinweis: Wenn Sie Sass auffordern, ein Verzeichnis zu importieren, sucht es nach einer Datei namens index oder _index).

Standardmäßig werden alle öffentlichen Mitglieder mit einem Modul weitergeleitet. Wir können jedoch selektiver sein, indem wir show- oder hide-Klauseln hinzufügen und bestimmte Mitglieder zum Ein- oder Ausschließen benennen.

// forward only the 'input' border() mixin, and $border-color variable
@forward 'input' show border, $border-color;

// forward all 'buttons' members *except* the gradient() function
@forward 'buttons' hide gradient;

Hinweis: Wenn Funktionen und Mixins denselben Namen haben, werden sie zusammen angezeigt und ausgeblendet.

Um die Quelle zu verdeutlichen oder Namenskonflikte zwischen weitergeleiteten Modulen zu vermeiden, können wir as verwenden, um Mitglieder eines Partials beim Weiterleiten mit einem Präfix zu versehen.

// forms/_index.scss
// @forward "<url>" as <prefix>-*;
// assume both modules include a background() mixin
@forward 'input' as input-*;
@forward 'buttons' as btn-*;

// style.scss
@use 'forms';
@include forms.input-background();
@include forms.btn-background();

Und wenn wir müssen, können wir immer dasselbe Modul @usen und @forwarden, indem wir beide Regeln hinzufügen.

@forward 'forms';
@use 'forms';

Das ist besonders nützlich, wenn Sie eine Bibliothek mit Konfigurationen oder zusätzlichen Tools wrappen möchten, bevor Sie sie an Ihre anderen Dateien weitergeben. Es kann sogar helfen, Importpfade zu vereinfachen.

// _tools.scss
// only use the library once, with configuration
@use 'accoutrement/sass/tools' with (
  $font-path: '../fonts/',
);
// forward the configured library with this partial
@forward 'accoutrement/sass/tools';

// add any extensions here...


// _anywhere-else.scss
// import the wrapped-and-extended library, already configured
@use 'tools';

Sowohl @use als auch @forward müssen am Stammverzeichnis des Dokuments (nicht verschachtelt) und am Anfang der Datei deklariert werden. Nur @charset und einfache Variablendefinitionen können vor den Importbefehlen stehen.

Umstieg auf Module

Um die neue Syntax zu testen, habe ich eine neue Open-Source-Sass-Bibliothek (Cascading Color Systems) und eine neue Website für meine Band erstellt – beides noch im Bau. Ich wollte Module sowohl als Bibliotheksautor als auch als Website-Autor verstehen. Beginnen wir mit der „Endbenutzer“-Erfahrung beim Schreiben von Website-Stilen mit der Modulsyntax…

Styles pflegen und schreiben

Die Verwendung von Modulen auf der Website war eine Freude. Die neue Syntax fördert eine Code-Architektur, die ich bereits verwende. Alle meine globalen Konfigurations- und Tool-Imports befinden sich in einem einzigen Verzeichnis (ich nenne es config) mit einer Indexdatei, die alles weiterleitet, was ich brauche.

// config/_index.scss
@forward 'tools';
@forward 'fonts';
@forward 'scale';
@forward 'colors';

Während ich andere Aspekte der Website ausbaue, kann ich diese Tools und Konfigurationen importieren, wo immer ich sie brauche.

// layout/_banner.scss
@use '../config';

.page-title {
  @include config.font-family('header');
}

Dies funktioniert sogar mit meinen bestehenden Sass-Bibliotheken wie Accoutrement und Herman, die immer noch die alte @import-Syntax verwenden. Da die Regel @import nicht über Nacht überall ersetzt wird, hat Sass eine Übergangsfrist eingebaut. Module sind jetzt verfügbar, aber @import wird erst in ein bis zwei Jahren veraltet sein – und erst ein Jahr danach aus der Sprache entfernt werden. In der Zwischenzeit werden die beiden Systeme in beide Richtungen zusammenarbeiten.

  • Wenn wir eine Datei @importieren, die die neue @use/@forward-Syntax enthält, werden nur die öffentlichen Mitglieder ohne Namensraum importiert.
  • Wenn wir eine Datei @usen oder @forwarden, die eine ältere @import-Syntax enthält, erhalten wir Zugriff auf alle verschachtelten Imports als einen einzigen Namensraum.

Das bedeutet, dass Sie die neue Modulsyntax sofort nutzen können, ohne auf eine neue Version Ihrer Lieblingsbibliotheken warten zu müssen: und ich kann mir etwas Zeit nehmen, um all meine Bibliotheken zu aktualisieren!

Migrationswerkzeug

Die Aktualisierung sollte nicht lange dauern, wenn wir das von Jennifer Thakar entwickelte Migrationswerkzeug verwenden. Es kann mit Node, Chocolatey oder Homebrew installiert werden.

npm install -g sass-migrator
choco install sass-migrator
brew install sass/sass/migrator

Dies ist kein Einmal-Tool zur Migration zu Modulen. Da Sass wieder in aktiver Entwicklung ist (siehe unten), wird das Migrationswerkzeug auch regelmäßige Updates erhalten, um bei der Migration neuer Features zu helfen. Es ist ratsam, dies global zu installieren und für zukünftige Verwendungen aufzubewahren.

Der Migrator kann über die Kommandozeile ausgeführt werden und wird hoffentlich auch in Drittanbieter-Anwendungen wie CodeKit und Scout integriert. Zeigen Sie darauf eine einzelne Sass-Datei, wie style.scss, und geben Sie an, welche Migration(en) angewendet werden sollen. Derzeit gibt es nur eine Migration namens module.

# sass-migrator <migration> <entrypoint.scss...>
sass-migrator module style.scss

Standardmäßig aktualisiert der Migrator nur eine einzelne Datei, aber in den meisten Fällen möchten wir die Hauptdatei *und alle ihre Abhängigkeiten* aktualisieren: alle Partials, die importiert, weitergeleitet oder verwendet werden. Das können wir tun, indem wir jede Datei einzeln nennen oder das Flag --migrate-deps hinzufügen.

sass-migrator --migrate-deps module style.scss

Für einen Testlauf können wir --dry-run --verbose (oder kurz -nv) hinzufügen und die Ergebnisse sehen, ohne Dateien zu ändern. Es gibt eine Reihe weiterer Optionen, mit denen wir die Migration anpassen können – sogar eine speziell für Bibliothekautoren, um alte manuelle Namensräume zu entfernen – aber ich werde nicht alle hier behandeln. Das Migrationswerkzeug ist vollständig dokumentiert auf der Sass-Website.

Veröffentlichte Bibliotheken aktualisieren

Auf der Bibliotheksseite stieß ich auf einige Probleme, insbesondere bei dem Versuch, benutzerdefinierte Konfigurationen über mehrere Dateien hinweg verfügbar zu machen und die fehlenden verketteten Konfigurationen zu umgehen. Die Fehler bei der Reihenfolge können schwer zu debuggen sein, aber die Ergebnisse sind die Mühe wert, und ich glaube, wir werden bald weitere Patches sehen. Ich muss noch das Migrationswerkzeug für komplexe Pakete ausprobieren und möglicherweise einen Folgebeitrag für Bibliotheksautoren schreiben.

Das Wichtigste, was man derzeit wissen muss, ist, dass Sass uns während der Übergangszeit abdeckt. Nicht nur, dass Imports und Module zusammenarbeiten können, sondern wir können auch „nur-Import“-Dateien erstellen, um eine bessere Erfahrung für Legacy-Benutzer zu bieten, die unsere Bibliotheken immer noch @importieren. In den meisten Fällen wird dies eine alternative Version der Hauptpaketdatei sein, und Sie möchten sie nebeneinander haben: <name>.scss für Modulbenutzer und <name>.import.scss für Legacy-Benutzer. Jedes Mal, wenn ein Benutzer @import <name> aufruft, wird die `.import`-Version der Datei geladen.

// load _forms.scss
@use 'forms';

// load _forms.input.scss
@import 'forms';

Dies ist besonders nützlich, um Präfixe für Nicht-Modul-Benutzer hinzuzufügen.

// _forms.import.scss
// Forward the main module, while adding a prefix
@forward "forms" as forms-*;

Sass aktualisieren

Sie erinnern sich vielleicht, dass Sass vor einigen Jahren einen Feature-Freeze hatte, damit verschiedene Implementierungen (LibSass, Node Sass, Dart Sass) alle aufholen konnten, und schließlich die ursprüngliche Ruby-Implementierung eingestellt wurde. Dieser Freeze endete letztes Jahr mit mehreren neuen Features und aktiven Diskussionen und Entwicklungen auf GitHub – aber ohne viel Aufhebens. Wenn Sie diese Releases verpasst haben, können Sie sich auf dem Sass Blog auf den neuesten Stand bringen.

Dart Sass ist jetzt die kanonische Implementierung und wird im Allgemeinen die erste sein, die neue Features implementiert. Wenn Sie das Neueste möchten, empfehle ich, umzusteigen. Sie können Dart Sass mit Node, Chocolatey oder Homebrew installieren. Es funktioniert auch hervorragend mit bestehenden gulp-sass-Build-Schritten.

Ähnlich wie bei CSS (seit CSS3) gibt es keine einheitliche Versionsnummer mehr für neue Releases. Alle Sass-Implementierungen arbeiten nach derselben Spezifikation, aber jede hat einen einzigartigen Release-Zeitplan und eine Nummerierung, die mit Unterstützungsinformationen in der wunderschönen neuen Dokumentation, entworfen von Jina, widergespiegelt wird.

Sass-Module sind ab dem **1. Oktober 2019** in **Dart Sass 1.23.0** verfügbar.