/ Tech Insights

SCSS Code Conventions

Wie in vielen Bereichen der Entwicklung sind Regeln oder Absprachen auch in der Frontend-Entwicklung nöig, sobald mehr als ein Entwickler beteiligt ist.

Deshalb soll der folgende Artikel eine Art Beipackzettel zum von uns genutzen CSS-Präprozessor Sass in der SCSS-Notation und der Struktur der der zugehörigen Dateien sein.

Schon Spidermans Onkel Ben Parker wusste:

With Great Power Comes Great Responsibility

Auch Tools bringen viele mächtige Möglichkeiten mit sich, die man mit Bedacht nutzen sollte.

Die folgenden Punkte haben sich durch Zusammenarbeit in mehreren Projekten im Frontend-Team herausentwickelt und stellen keine dogmatischen Gesetze dar, sondern sind eine Sammlung von Best-Practices, die sich für uns als brauchbar erwiesen haben.

Architektur

In diesem Abschnitt geht es zunächst um die Strukturierung der Ordner und SCSS-Dateien.

Konzept

Wir folgen grob dem ITCSS Konzept von Harry Roberts.
Dieses Konzept verfolgt eine Ordnung der Dateien von generisch oben, nach immer spezifischer und expliziter unten.

Konkret für uns:

  1. globale Settings / Variablen
  2. dann Utilities: Sass-Mixins und Helper-Funktionen
  3. dann Generisches: Regeln für box-sizing, Deklarationen von font-faces und normalisierende Styles auf Tagnamen
  4. dann: allgemeinere Objekte, dann kleinere Komponenten
@import '_____settings/settings';
@import '____utils/utils';
@import '___generics/generics';
@import '__objects/objects';
@import '_components/components';

Nach Atomic Design könnte man auch von Atomen und Molekülen oder auch Modulen sprechen. Für diesen Artikel behalte ich die Bezeichung Komponente bei. Wichtig ist jedoch immer die Unterteilung von Generisch nach Spezifisch.

Fall Talents Connect

Für das Projekt Talents Connect (also das Recruiter- und Bewerbercockpit samt zugehöriger Landingpages) wird eine Zwischenebene nach den Utilities einzogen, um die Trennung in einzelne Bereiche zu ermöglich, aber gleichzeitig gemeinschaftliche Regeln zu erlauben.

Portal
  Common
  Bewerber
  Recruiter
Landingpage
  Common
  Bewerber
  Recruiter
Guest / Public Job Profil

Jeweils unterhalb dieser Ordner wird die Objekt- und Komponentenweise Trennung, von generisch nach spezifisch, fortgesetzt.

Generischere Nutzung

Sobald eine "spezifisch" genutzte Komponente in mehreren Bereichen genutzt werden soll, muss die entsprechende Datei in die nächsthöhere Ebene, die diese beiden Bereiche umfasst, umgezogen werden.

Das heißt z.B., wenn eine Komponente, die bislang nur im Bewerber-Portal genutzt wurde auch im Recruiter-Portal genutzt werden soll, zieht sie in den Common-Ordner des Portals.

Soll sie auch in einer der Landingpages genutzt werden, müsste man sie sogar in den allgemeinen Common-Ordner hochziehen.

Ordneraufbau je Komponente


Je Modul/Kompontente gibt es einen umfassenden Ordner.

Index Datei

Dieser Ordner enthält zunächst eine Art "Index"-Datei, die gleichnamig zum Ordner ist (mit Präfix _.

Diese Datei importiert die weiteren Unterdateien des Ordners.
Nur diese Indexdatei selbst wird von weiter oben in der Hierarchie stehenden Dateien inkludiert.

Jede Komponente kann ebenfalls nach Settings und Unterelementen in mehrere Dateien aufgeteilt werden.

Beispiel: Komponente "Card"

/card
-- _card.scss
-- _card-settings.scss
-- _card-head.scss
...

Datei

In diesem Abschnitt geht es um den Stil innerhalb einer SCSS-Datei.

Prinzip: single responsibility

Sofern es sich um eine Objekt-/Komponentenbezogene .scss-Datei handelt:
Eine Datei beinhaltet jeweils nur relevante Regeln, die sinngemäß für diese eine Komponente sind, keine ihr ganz fremden Regeln.

Naming things is hard

Die Benamung von Klassen wird (grob) nach BEM vorgenommen, der bekannten Namenskonvention von Yandex.
"Block" entspricht bei uns dem Namen der Komponente, diese kann UnterElemente haben oder sehr spezifisch Modifiziert werden.

Beispiel: Datei namens \_card.scss

.card {...}
.card__head {...}
.card__body {...}
.card--job {...}

Hier zeigt sich schon, dass Unterelemente nicht zwingend dem Elternelement untergewordnet sein müssen, um die Spezifität niedrig zu halten.

Modifier- / Hilfsklassen

Falls man eine zu lange Element-Modifier-Kombination vermeiden möchte, kann das Sass-Ampersand zur Bildung einer Hilfsklasse nutzen.

Bsp: Statt

.card__head {...}
.card__head--active {...}

könnte auch folgendes geschrieben werden.

.card__head {
  ...
  &.-active {...}
}

Das hat mehrere Vorteile. Die Klasse .-active könnte einfacher per JavaScript gesetzt werden.
Auch das zugehörige HTML ist DRYer.
class="card__head card__head--active" wird zu class="card__head -active".

Die folgende Variante wäre durch die Ampersand-Technik auch möglich:

.card__head {
  ...
  &--active {...}  //please not
}

Diese Schreibweise sollte aber vermieden werden, denn dies verhindert, dass der gesamte Ausdruck .card__head--active per CMD+F zu finden ist.

Kontextbezogene Regeln

Speziell modifizierte Regeln für Komponenten, die sich innerhalb eines anderen Objekts befinden, können mittels des "nachgestellten Ampersand" gebildet werden

Beispiel:

.card {
... // rules for card

  .panel & {
      // special modifiers for cards in a panel
  }
}

Spezifität


Wir wollen die Spezifität des Selektors gering halten, um einfaches Überschreiben von Regeln von anderer Stelle zu ermöglichen. Spezifitätskriege müssen vermieden werden.

Das heißt:

  • keine ID Selektoren
  • kein !important *
  • keine Tagnamen vor Klassen *

(* gilt vor allem in Regeln für Komponenten)

Außerdem gilt: Kein (allzu) tiefes Nesting von Regeln.
Eine grobe Daumenregel kann hier sein "nicht tiefer als drei Selektoren plus Media-Query-Block".

Man sollte sich darum die Frage stellen, warum kann eine Klasse nicht auch außerhalb einer Klammer allein stehen?

Zu spezifisches Scoping verhindert die Wiederverwendbarkeit von Regeln. Sogenanntes "tight coupling" von HTML-Struktur und Sass-Nesting, verhindert auch ein flexibles Refactoring.

Aus den gleichen Gründen, sollten keine Tagnamen vor Klassen verwendet werden, damit div.card beispielsweise auch li.card sein kann, sollte besser nur die Deklaration .card genutzt werden.

Innerhalb der Rulesets

(Definition Begriff Ruleset)

Nutzung von Helpern

Es sollten wiederverwendbare Eigenschaften über Mixins bzw. Helper inkludiert werden.
Dabei sind @include gegenüber @extend zu bevorzugen.

Nur per @include-mixin, werden die Regeln beim Kompilieren auch in dieses Rule, an Ort und Stelle, eingefügt.

Bei @extend wird ein gesammelter Block aus allen Selektoren gebaut die diesen Nutzen, was schwerer zu debuggen ist und zu Unübersichtlichkeit führt. Diese können im fein abgesteckten Scope (innerhalb einer Komponente) genutzt werden, nicht jedoch Ordnerübergreifend.

Beispiele:
Wir nutzen ein @include _headline($font-size), um konsistente Überschriften an verschiedenen Stellen im gesamten Projekt zu nutzen.

Die Schreibweise @extend %_card könnte beispielsweise innerhalb der Klassen .card__job und .card__company genutzt werden, wenn diese sich stark ähneln und strukturell nah beeinander stehen.
Nochmals: Dies sorgt für die Regel: .card__job, .card__company an der Stelle im generierten CSS, wo das Ruleset von %_card stand, nicht an Stelle der Benutzung.

Anstelle unhandlicher Media-Queries nutzen wir ein _respond-to()-Mixin, welches Anhand mehrerer möglicher Parameterwerte die Breakpoints vereinheitlicht.

Nutzung von Variablen für Settings

Zur wahrung Konsistenz innerhalb von Komponenten und auch über das ganze Projekt hinweg, werden Variablen für verschiedene Settings genutzt.

Eine Card Komponente kann beispielsweise nutzen:

.card {
  ...
  border-radius: $card-border-radius;  
}

wobei $card-border-radius in der zugehörigen Datei _card-settings.scss definert könnte als:

$card-border-radius: $global-border-radius;

Reihenfolge

Rulesets sollten zuerst mit Regeln für Positionierung, Display/Box-Model und Abstände anfangen, danach folgen Regeln für das Design, wie Farben und Font-bezogenes, usw.

Rulesets sollten "mobile-first" geschrieben werden, also erst für den mobilen View gelten, weiter unten Modifizierungen für größere Screens vornehmen. Das sorgt in der Regel für weniger Regeln im Gesamten.

Erst nach diesen _respond-to()-Blöcken, sollen Blöcke folgen, die neue Klammern eröffnen, wie z.B. Pseudo-Elemente wie :before, :after usw.

Vendor Präfixe

Vendor Präfixe für "moderne" Regeln schreiben wir nicht selbst, sondern lassen diese von PostCSS/Autoprefixer generieren.

Das sorgt für lesbareren Code und stellt sicher, dass nur tatsächlich benötigte Präfixe im generierten CSS landen.
Es reicht also in der Regel die Basisvariante zu schreiben.

Ausnahmen bilden spezielle Regeln, die es nur im jeweiligen Browser gibt, die also nicht aus anderen generierbar sind, wie etwa -webkit-font-smoothing.

kthxbye

Wir haben diese Best Practices aus Artikeln, Talks und Dokumentationen abgekupfert, denn die meisten guten Ideen wurden bereits irgendwo von irgendwem erdacht. Vielen Dank an diese Menschen und den Open Source Gedanken!

Sascha L

Sascha L

Sascha entwickelt seit Dekaden benutzerfreundliche und innovative Frontend Konzepte.

Mehr lesen