zurück zum Artikel

CSS-Präprozessoren im Vergleich

Werkzeuge
CSS-Präprozessoren im Vergleich

CSS-Präprozessoren ergänzen CSS mit Variablen, Funktionen und Mixins zu einer runden Sprache und helfen, den Code schlank zu halten. Die populären Projekte LESS, Sass und Stylus decken sich zu 80 Prozent in ihren Funktionen, die restlichen 20 liegen in den Details.

Viele kleine und große Webanwendungen nutzen das Konzept der CSS-Präprozessoren seit mehreren Jahren. Und trotzdem wird immer noch vor jedem Projektstart gründlich recherchiert und evaluiert, ob LESS, Sass oder doch Stylus die perfekte Wahl ist. Von den persönlichen Vorlieben abgesehen, bieten alle drei Kandidaten ein ähnliches Spektrum an Möglichkeiten und die Entscheidung fällt meist anhand von Details.

Der Sinn von CSS lässt sich in einem Satz zusammenfassen: die Elemente eines Dokuments mittels Selektoren und Attribute in Form bringen. Während der Einsatz für Designer ohne technischen Hintergrund ein Vorteil sein kann, ist CSS auf der anderen Seite eine wenig mächtige Sprache, die einem der wichtigsten Programmierprinzipien widerspricht. Entgegen dem DRY-Prinzip (Don't repeat yourself), sind Eigenschaften für jeden Selektor zu wiederholen.

CSS-Präprozessoren eröffnen in dem Zusammenhang eine völlig neue Welt: Mithilfe von Variablen, Mixins und Funktionen lassen sich plötzlich die einmal definierten Eigenschaften und Patterns wieder und wieder verwenden.

CSS ja, Präprozessoren vielleicht

CSS-Präprozessoren sind nicht das Allheilmittel für schlechten Code, sie verpacken ihn nur schöner. Es ist also reiner Aberglaube, dass CSS und somit die Webseite durch den bloßen Einsatz profitiert. Folgender Code zeigt ein typisches Beispiel:

.container {
font-size: 12px;
}

.container .wichtig {
font-size: 14px;
}

.container .wichtig span {
color: red;
}

Und einfach mittels Präprozessor:

.container {
font-size: 12px;
.wichtig {
font-size: 14px;
span {
color: red;
}
}
}

Die Klasse container wird nicht mehr wiederholt und der Code ist auf den ersten Blick schöner und vor allem aufgeräumter. Eine solch unüberlegte Verschachtelung kann allerdings schnell gefährlich werden, denn ein weiterer Entwickler könnte den Gedanken missverstehen und den Code wie folgt erweitern:

.container2 {
font-size: 11px;
.wichtig {
@extend container.wichtig; // kopiert alle
// styles der Klasse wichtig
// innerhalb des Containers
}
}

Durch das extend-Konzept (später dazu mehr) wurden alle Eigenschaften der Klasse wichtig aus container in eine Klasse selben Namens in container2 kopiert.

Ganz klar handelt es sich dabei um ein interessantes Feature der Präprozessoren. In diesem Zusammenhang bringt es allerdings mehr Komplexität in den Code, als es eigentlich entfernen sollte, denn bei jeder zukünftigen wichtig-Klasse ist der Schritt nun zu wiederholen. Nur weil CSS-Präprozessoren die Möglichkeit einer Verschachtelung mitbringen, bedeutet das nicht, dass man sie unbedingt verwenden muss. Eine Verschachtelung bis zu maximal vier Ebenen kann sinnvoll sein [1], alles was darüber hinausgeht, bleibt nur in den seltensten Fällen praktikabel.

Zurück zum Ursprung, jetzt mit einfachem CSS-Code:

.container {
font-size: 12px;
}

.wichtig {
fonz-size: 14px;
color: red;
}

Die Klasse wichtig lässt sich nun in jedem Kontext verwenden und falls sie einmal abweichen sollte, kann der Programmierer immer noch eine zusätzliche Klasse erstellen. Das mag ein etwas weit hergeholtes Beispiel sein, aber es zeigt, dass ein CSS-Präprozessor keinen Entwickler daran hindern kann, aus nicht so schönem noch weniger schönen Code zu machen. Da diese "Bad Practice" nun bekannt ist, sind alle Voraussetzungen gegeben, um richtig mit CSS-Präprozessoren arbeiten zu können.

Vor dem Einsatz

Ursprünglich wurden LESS und Sass in Ruby entwickelt. Mittlerweile ist LESS [2] eine JavaScript-Bibliothek, was die Installation gerade unter Windows vereinfacht, denn Ruby ist – im Gegensatz zum Mac – unter Windows noch explizit zu installieren [3]. Ist die Sprache im System vorhanden, lässt sich Sass über die Eingabeaufforderung beziehungsweise den Terminal einrichten [4].

LESS lässt sich zusätzlich in ein HTML-Dokument durch das Referenzieren einer JavaScript-Datei einbinden – für Produktivsysteme ist solch ein Vorgehen verständlicherweise allerdings nicht möglich.

Um Stylus installieren zu können, setzt das Programm Node.js voraus. Dementsprechend hat sich der junge Preprozessor als beliebter Konkurrent in der Node-Community etabliert. Das entsprechende npm-Modul (Node Package Manager) lässt sich mit einem einfachen Befehl installieren:

npm install stylus -g

Für LESS und Sass sind ebenfalls npm-Module für die Integration in Node.js-Anwendungen vorhanden.

Syntax

Syntaktische Grundlagen

Alle drei Sprachen verstehen die reguläre CSS-Syntax, was das Konvertieren bereits bestehender CSS-Dateien vergleichsweise unkompliziert gestaltet. Sowohl Sass als auch Stylus gehen noch einen Schritt weiter und bieten eine etwas kürzere Schreibweise an:

LESS – gewohnte CSS-Syntax:

h1 {
color: #000000;
}

Sass – verzichtet auf Klammern und Semikolons (Dateiendung *.sass), kann aber wie erwähnt auch mit der regulären CSS-Syntax arbeiten (Dateiendung *.scss)

h1 {
color: #000000;
}

h1
color: #000000

Stylus geht den größten Kompromiss ein und erlaubt die normale CSS-Syntax, wobei Klammern, Doppelpunkte und Semikolons optional sind:

h1 {
color: #000000;
}

h2
color blue

Selbst ein gemischtes Verwenden innerhalb einer Datei ist erlaubt und das Ergebnis kompiliert fehlerfrei.

Variablen im Einsatz

Für Variablennamen verwendet LESS den @-, Sass den $- und Stylus einen optionalen $-Präfix. Das Dollarzeichen hat in CSS keinerlei Bedeutung und lässt sich problemlos einsetzen, während das @ durch die Verwendung bei @media- und anderen Angaben für Verwirrung sorgen kann.

LESS:

@siteColor: black;
@borderStyle: dotted;

body {
color: @siteColor;
border: 1px @borderStyle @siteColor;
}

Sass:

$siteColor: black;
$borderStyle: dotted;

body {
color: $siteColor;
border: 1px $borderStyle $siteColor;
}

Stylus:

siteColor = black;
borderStyle = dotted;

body {
color: siteColor;
border: 1px borderStyle siteColor;
}

Die kompilierte Ausgabe sieht bei allen identisch aus:

body {
color: black;
border: 1px dotted black;
}

Sass bringt noch eine Besonderheit beim Scope mit: Es gibt nur globale Variablen. Werden also globale durch lokale Variablen überschrieben, bekommt die globale Variable den Wert der lokalen:

$color: black;           
.header {
$color: white;
color: $color;
}
.content {
color: $color;
// Bei LESS und Stylus hat $color den Wert black (global)
// Bei Sass hat $color den Wert white (Überschrieben)
}

Stylus unterstützt ein weiteres Feature namens Property Lookup. Es ermöglicht das Referenzieren von Eigenschaften, ohne deren Wert in einer Variable gesetzt zu haben:

.box {
width: 150px
height: 80px
margin-left: -(@width / 2)
margin-top: -(@height / 2)
}

Einzige Voraussetzung dabei ist es, einen bereits gesetzten Wert zu haben, da die Berechnung bei der Kompilierung erfolgt und es somit nutzlos ist, wenn etwa die Breite im Nachhinein noch geändert wird. Das Feature haben Nutzer auch für Sass nicht nur einmal beantragt, die Entwickler haben es aber aus genau dem Grund – dass sich ein korrekter Wert nur nach dem Berechnen im Browser ermitteln lässt – zurückgewiesen. Die nicht benötigten Variablen sind also der einzige Vorteil, den dieses Komfort-Feature mit sich bringt.

Mixins und extend

Mixins

Mit Mixins sollen sich Eigenschaften eines Regelsets in ein anderes einbeziehen lassen. Sie ermöglichen so die Wiederverwendung von Eigenschaften innerhalb unterschiedlicher Stylesheets. Durch die einmalige Mixin-Definition gehört vor allem bei Änderungen das lästige Durchsuchen des Codes der Vergangenheit an.

In Sass können Entwickler die @mixin-Direktive verwenden, in LESS werden sie über einen Klassenselektor definiert. Stylus hingegen verzichtet ganz auf einen Präfix:

LESS

.crop(@width: 100px) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: @width;
}

.title {
.crop();
}

.text {
.crop(200px);
}

Sass

@mixin crop($width: 100px) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: $width;
}

.title {
@include crop();
}

.text {
@include crop(200px);
}

Stylus

crop(width= 100px) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: width;
}

.title {
crop();
}

.text {
crop(200px);
}

Aufgeräumter Code dank extend

Um Styles an mehrere Selektoren gleichzeitig zu setzen, kommt im Standard-CSS folgender Code zum Einsatz:

p, ul, ol {
/* … */
}

Auch wenn der Ansatz wunderbar funktioniert, kann das wiederholte Verwenden der Klassen die Wartbarkeit des Codes erschweren. Die CSS-Präprozessoren kontern mit Inheritance, was im Grunde nichts anderes heißt, als Eigenschaften eines Selektors einem anderen zu vererben. Sass und Stylus machen es vor:

.nav {
padding: 5px;
}
.side-nav {
@extend .nav;
}

Die kompilierte Ausgabe:

.nav,
.side-nav {
padding: 5px;
}

LESS enthält erst seit einer neueren Version (>1.4) eine extend-Funktion [5] und weicht bei ihrer Implementierung etwas ab: Statt der at-rule (@extend) wie bei Sass und Stylus, implementiert LESS eine Syntax für Pseudo-Klassen, die sich am Selektor oder in ihm verwenden lässt:

.nav {
padding: 5px;
}
.side-nav:extend(.nav);

// Alternativ:
.sidebar-nav {
&:extend(.nav);
}

Die kompilierte Ausgabe:

.nav,
.side-nav {
padding: 5px;
}

Mehrere Klassen lassen sich mit einem Komma getrennt übergeben. Ein zusätzlicher Parameter all sorgt dafür, dass auch die verschachtelten Klassen "extended" werden:

.main-nav {
&:extend(.nav all, .side-nav);
}

Import und Schleifen

Kontrollierter Import

CSS-Dateien mit der @import-Syntax direkt zu importieren ist in den meisten Fällen keine gute Idee, denn die daraus entstehenden Anfragen (ein http-Request pro Datei) verlangsamen den Ladevorgang zusätzlich. CSS-Präprozessoren importieren (ebenfalls mit dem @import-Befehl) auf eine völlig andere Art und Weise: Während des Kompiliervorgangs wird der Inhalt der importierten Datei direkt mit einbezogen, um am Ende nur eine einzige CSS-Datei auszuliefern. Durch das Konkatenieren reicht es völlig aus, Mixins und Variablen an einer zentralen Stelle zu definieren, um sie von überall verwenden zu können.

Aufgepasst: CSS-Präprozessoren ignorieren beim @import-Befehl Dateien mit einer .css-Endung. Importieren lassen sich somit nur proprietäre Dateien, die beispielswiese auf .less, .scss, .sass oder .styl enden.

/* file.{type} */
body {
background: black;
}

@import "reset.css";
@import "file.{type}";
p {
background: white;
}

Die kompilierte Ausgabe zeigt, dass reset.css nicht berücksichtigt wurde:

@import "reset.css";
body {
background: black;
}
p {
background: white;
}

Farbe und Mathematik

Color Functions sind eingebaute Funktionen, die Farben bei der Kompilierung ändern können. Sie können vor allem bei Farbverläufen oder bei :hover-Effekten nützlich sein.

lighten($color, 10%); /* gibt eine Farbe, die 10% heller als $color ist, zurück */
darken($color, 10%); /* gibt eine Farbe, die 10% dunkler als $color ist, zurück */

saturate(@color, 10%);
desaturate(@color, 10%);

$color: #0982C1;
h1 {
background: $color;
border: 3px solid darken($color, 50%);
}

Das sind bei weitem nicht alle enthaltenen Farbfunktionen – weitere Informationen dazu gibt es in der LESS [6]-, Sass [7]- und Stylus [8]-Dokumentation.

In allen Sprachen lassen sich mathematische Operationen durchführen. Sass und Stylus unterstützen arithmetische Operationen zwischen unterschiedlichen Einheiten. Die Präprozessoren eignen sich aber nicht nur für einfache Mathematik, sondern können auch komplexere Aufgaben, wie Rundungen oder einen Min- beziehungsweise Max-Wert aus einer Liste zu ermitteln, lösen.

body {
margin: (14px/2);
top: 50px + 100px;
right: 100px - 50px;
left: 10 * 10;
}

Schleifen aller Arten

In Sass und Stylus lassen sich normale if/else-Anweisungen verwenden. LESS hingegen setzt auf sogenannte CSS Guards, die hilfreich sind, wenn nicht auf einfache Werte, sondern auf Expressions gematcht wird, genau wie es die @media-Query-Feature-Spezifikation beschreibt.

Sass

@if lightness($color) > 30% {
background-color: black;
}
@else {
background-color: white;
}

LESS

.mixin (@color) when (lightness(@color) > 30%) {
background-color: black;
}
.mixin (@color) when (lightness(@color) =<; 30%) {
background-color: white;
}

Stylus

if lightness(color) > 30% {
background-color black;
} else {
background-color white;
}

Sowohl Sass als auch Stylus unterstützen die for-Direktive. LESS setzt wiederum auf CSS Guards und rekursive Mixins:

Sass

@for $counter from 1px to 3px {
.border-#{counter} {
border: $counter solid black;
}
}

LESS

.loop(@counter) when (@counter > 0){
.loop((@counter - 1));

.border-@{counter} {
border: 1px * @counter solid black;
}
}

Stylus

for counter in (1..3) {
.border-{counter} {
border: 1px * counter solid black;
}
}

Verschachtelung

Alles gut verpackt

Klassen zu verschachteln vermindert den Schreibaufwand und hält den Code schlank. Darüber hinaus hilft solch eine Vorgehensweise, die Styles unabhängig voneinander zu halten. Vor allem beim Einhalten des DRY-Prinzips dienen Verschachtelungen dazu, Wiederholungen von Klassennamen zu vermeiden:

nav {
margin: 0;
width: 100%;
ul {
padding: 0;
margin: 0;
}
}

Sass geht dazu noch einen Schritt weiter und erlaubt es, Eigenschaften ineinander zu verschachteln:

nav {
margin: 0;
width: 100%;
ul {
padding: 0;
margin: 0;
}
border: {
style: solid;
left: {
width: 1px;
color: #aaa;
}
right: {
width: 2px;
color: #000;
}
}
}

Die kompilierte Ausgabe sieht wie folgt aus:

nav {
margin: 0;
width: 100%;
border-style: solid;
border-left-width: 1px;
border-left-color: #aaa;
border-right-width: 2px;
border-right-color: #000;
}
nav ul {
padding: 0;
margin: 0;
}

Zusätzlich lässt sich mit dem &-Symbol die Elternkomponente auswählen, um beispielsweise Styles auf Pseudo-Elemente wie :hover anwenden zu können – ähnlich dem this in JavaScript.

button {
background-color: green;
&:hover {
background-color: blue;
}
}

Die kompilierte Ausgabe sieht wie folgt aus:

button {
background-color: green;
}

button:hover {
background-color: blue;
}

Mehr Möglichkeiten durch Erweiterungen

Kaum eine Erweiterung ist so verbreitet wie Compass [9] für Sass. Aber auch für LESS gibt es Mixin-Bibliotheken wie LESSHat [10] und Stylus kann mit Nib [11] aufwarten.

Je nach Auswahl der Sprache bringen Erweiterungen jede Menge Vorteile mit sich. Sie umfassen neben einer Vielzahl von hilfreichen Mixins, die insbesondere den Einsatz der neuen CSS3-Techniken vereinfachen, auch einheitliche Schreibweisen, um zum Beispiel Eigenschaften mit den Präfixen der einzelnen Browserhersteller zu übersetzen:

.box {
@include border-radius(4px, 4px);
}

// Ausgabe:
.box {
-webkit-border-radius: 4px 4px;
-moz-border-radius: 4px / 4px;
-khtml-border-radius: 4px / 4px;
border-radius: 4px / 4px;
}

Fehlermeldungen und Fazit

Fehlermeldungen näher betrachtet

Eine übersichtliche Fehlerausgabe kann gerade bei großen Dateien Gold wert sein. Ein kurzes Beispiel zeigt, wie sich die einzelnen Präprozessoren im Falle eines fehlenden Anführungszeichens verhalten:

.container {
.navigation {
background: url("abc.gif);
}
}

Sass

Syntax error: Invalid CSS after "url(": expected ")", was ""abc.gif);" 
on line 3 of demo.less

LESS

ParseError: unmatched `"` in /Users/roberto/projects/test/demo.less 
on line 3, column 25:
2 .navigation {
3 background: url("abc.gif);
4 }

Stylus

/usr/local/lib/node_modules/stylus/bin/stylus:537
if (err) throw err;
^
ParseError: stdin:3
1| .container {
2| .navigation {
> 3| background: url("abc.gif);
4| }
5| }
Sowohl Sass, LESS als auch Stylus bieten bei einem fehlenden Anführungszeichen eine ordentliche Fehlerausgabe. (Abb. 1)
Sowohl Sass, LESS als auch Stylus bieten bei einem fehlenden Anführungszeichen eine ordentliche Fehlerausgabe. (Abb. 1)


Alle drei weisen im vorliegenden Beispiel auf den Fehler hin und zeigen eine verständliche Ausgabe, wobei es auch auf die Art des Fehlers ankommt, wie präzise und detailliert der einzelne Präprozessor agiert.

Fazit

CSS-Präprozessoren sind nichts Neues, trotzdem führen Entwickler in zahlenreichen Artikeln noch immer hitzige Diskussionen über die richtige Wahl. In 80 Prozent der Funktionen decken sich die drei Präprozessoren Sass, LESS und Stylus ziemlich gut, selbst wenn sie so manches auf unterschiedliche Weise umsetzen. Sollten die restlichen 20 Prozent keine klare Entscheidungsgrundlage bieten, hilft manchmal die verwendete Umgebung, denn ein Node-Entwickler wird beispielsweise mit großer Wahrscheinlichkeit durch die guten Interaktionsmöglichkeiten zu Stylus tendieren.

Auch wenn Stylus von der Syntax her die meisten Freiheiten bietet, empfiehlt sich in vielen Fällen, trotzdem die reguläre CSS-Syntax oder mindestens eine konsistente Schreibweiße zu verwenden. Denn selbst wenn sich viele über die Akzeptanz des Compilers bei einem fehlenden Semikolon freuen, ist die Schreibweise grundsätzlich falsch.

Egal ob man LESS für seine Philosophie, Sass aufgrund der vielfältigen Gestaltungsmöglichkeiten oder Stylus als Newcomer aus der Node.js-Ecke favorisiert – ein Präprozessor gehört in den Werkzeugkasten eines jeden Webentwicklers. (jul [12])

Roberto Bez [13]
ist passionierter Webentwickler und begeistert von neuen Technologien, die er versucht, in die tägliche Anwendungsentwicklung einzubringen.


URL dieses Artikels:
http://www.heise.de/-2288284

Links in diesem Artikel:
[1] http://thesassway.com/beginner/the-inception-rule
[2] http://lesscss.org/
[3] http://www.rubyinstaller.org
[4] http://sass-lang.com/install
[5] http://lesscss.org/features/#extend-feature
[6] http://lesscss.org/#-color-functions
[7] http://sass-lang.com/documentation/Sass/Script/Functions.html
[8] http://learnboost.github.io/stylus/docs/bifs.html
[9] http://compass-style.org/
[10] http://lesshat.madebysource.com/
[11] https://github.com/visionmedia/nib
[12] mailto:jul@heise.de
[13] http://devangelist.de