Innovation

Programmieren von CSS: Bündelung und Minimierung

Dino Esposito

Dino EspositoEin altes Mantra der Webentwicklung besagt, dass sich zu viele Anforderungen negativ auf die Leistung von Seiten auswirken. Wenn Sie einen Kunstgriff kennen, um die Anzahl von HTTP-Anforderungen zu reduzieren, die von Ihren Webseiten ausgelöst werden, müssen Sie diesen unbedingt anwenden. Der visuelle Inhalt von Webseiten wird immer umfangreicher, wodurch die Kosten für das Herunterladen von zugehörigen Ressourcen wie CSS, Skripts und Bildern merkbar steigen. Zum größten Teil werden diese Ressourcen möglicherweise lokal vom Browser zwischengespeichert, aber dem Anfangsbedarf gerecht zu werden, kann schwierig sein. Durch weniger und kleinere Anforderungen wird außerdem eine geringere Bandbreite beansprucht, die Latenz verringert und die Akkulaufzeit verlängert – wichtige Faktoren beim mobilen Browsen. Ein allgemein akzeptierter Lösungsansatz für diese Aspekte besteht aus zwei kombinierten Aktionen: Bündelung und Minimierung.

In diesem Artikel gehe ich auf die Bündelung und Minimierung von CSS-Dateien ein, wobei ich die Softwaretools verwende, die in ASP.NET MVC 4 verfügbar sind. Der Vorläufer hierzu ist mein vorheriger Artikel, „Erstellen von für Mobilgeräte optimierten Ansichten in ASP.NET MVC 4, Teil 2: Verwenden von WURFL“ (msdn.microsoft.com/magazine/dn342866).

Grundlegendes zur Bündelung und Minimierung

Bündelung ist das Zusammenfassen einer Reihe von unterschiedlichen Ressourcen in einer einzelnen Ressource, die heruntergeladen werden kann. Ein solches Paket kann zum Beispiel aus mehreren JavaScript- oder CSS-Dateien bestehen, die durch eine einzelne HTTP-Anforderung an einen Ad-hoc-Endpunkt auf den lokalen Computer übertragen werden. Minimierung ist hingegen eine Transformation, die auf eine Ressource angewendet wird. Genauer gesagt heißt Minimierung, alle unnötigen Zeichen aus einer textbasierten Ressource zu entfernen, wobei die erwartete Funktionalität nicht verändert wird. Das bedeutet das Kürzen von Bezeichnern, Verwenden von Aliasen für Funktionen und Entfernen von Kommentaren, Leerzeichen und neuen Zeilen – also im Grunde allen Zeichen, die normalerweise der besseren Lesbarkeit dienen, aber Platz benötigen und keinen funktionalen Zweck haben.

Bündelung und Minimierung können zusammen angewendet werden, bleiben aber voneinander unabhängige Prozesse. Abhängig vom Bedarf können Sie sich dafür entscheiden, nur Pakete zu erstellen oder einzelne Dateien zu minimieren. Allerdings gibt es auf Produktionswebsites normalerweise keinen Grund, der gegen die Bündelung und Minimierung aller CSS- und JavaScript-Dateien spricht. Eine Ausnahme können vielleicht allgemeine Ressourcen wie jQuery sein, die wahrscheinlich in den wohlbekannten Netzwerken für die Inhaltsübermittlung (Content Delivery Network, CDN) vorliegen. Beim Debuggen stellt sich die Lage wiederum ganz anders dar: Es ist sehr schwierig, eine minimierte oder gebündelte Ressource zu lesen und schrittweise durchzugehen, daher ist die Aktivierung von Bündelung und Minimierung hierbei nicht empfehlenswert.

Viele Frameworks stellen Bündelungs- und Minimierungsdienste mit leicht unterschiedlichen Stufen von Erweiterbarkeit und verschiedenen Featuresätzen bereit. Die Dienste bieten überwiegend alle dieselben Funktionen, sodass die Entscheidung für einen Dienst ganz von Ihnen abhängt. Wenn Sie eine ASP.NET MVC 4-Anwendung schreiben, ist Microsoft ASP.NET Web Optimization Framework für die Bündelung und Minimierung prädestiniert. Das in Abbildung 1 dargestellte Framework steht als NuGet-Paket zur Verfügung (bit.ly/1bS8u4B).

Installing the Microsoft ASP.NET Web Optimization Framework
Abbildung 1: Installieren von Microsoft ASP.NET Web Optimization Framework

Verwenden der CSS-Bündelung

Am besten verstehen Sie die Funktionsweise der CSS-Bündelung, wenn Sie mit einem vollständig leeren ASP.NET MVC-Projekt starten. Dazu erstellen Sie ein neues Projekt anhand der Vorlage „Leeres Projekt“ und entfernen nicht verwendete Verweise und Dateien. Als Nächstes fügen Sie eine Layoutdatei hinzu, die das Projekt mit einigen CSS-Dateien verknüpft:

    <link rel="stylesheet"
      href="@Url.Content("~/content/styles/site.css")"/>

Wenn Sie die Seite anzeigen und ihre Netzwerkaktivität mit Fiddler oder den Internet Explorer-Entwicklertools überwachen, sehen Sie zwei parallel ablaufende Downloads. Dies ist das Standardverhalten.

In ASP.NET MVC 4 können Sie den bisherigen Markupcode sehr viel kompakter gestalten, indem Sie ihn mithilfe der neuen Styles.Render-Funktion umschreiben:

@Styles.Render(
  "~/content/styles/site.css",
  "~/content/styles/site.more.css")

Die Style-Klasse, die sich unter „System.Web.Optimization“ befindet, ist viel leistungsfähiger, als es zunächst scheint. Die Styles.Render-Methode unterstützt auch Pakete. Das bedeutet, die Methode verfügt über zahlreiche Überladungen, von denen eine ein Array aus CSS-URLs akzeptiert. Eine andere Überladung nimmt dagegen den Namen eines vorher erstellten Pakets an (weitere Informationen dazu in Kürze). In diesem Fall gibt sie ein einzelnes <link>-Element aus, das auf eine automatisch generierte URL zeigt, die alle Stylesheets in gebündelter oder minimierter Form zurückgibt.

Erstellen von CSS-Paketen

In der Regel erstellen Sie Pakete programmgesteuert in der Datei „global.asax“. In Übereinstimmung mit dem ASP.NET MVC 4-Muster für Konfigurationscode erstellen Sie eine BundleConfig-Klasse unter dem Ordner „App_Start“ und machen aus dieser Klasse eine statische Initialisierungsmethode verfügbar:

BundleConfig.RegisterBundles(BundleTable.Bundles);

Ein CSS-Paket ist einfach eine Auflistung von Stylesheets. Hier ist der Code, mit dem Sie die zwei oben erwähnten CSS-Dateien in einem einzigen Download gruppieren:

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
    // Register bundles first
    bundles.Add(new Bundle("~/mycss").Include(
      "~/content/styles/site.css",
      "~/content/styles/site.more.css"));
    BundleTable.EnableOptimizations = true;
  }
}

Sie erstellen eine neue Bundle-Klasse und übergeben dem Konstruktor den virtuellen Pfad, der verwendet wird, um das Paket in einer Ansicht oder Webseite zu referenzieren. Sie können den virtuellen Pfad auch später durch die Path-Eigenschaft festlegen. Um dem Paket CSS-Dateien zuzuordnen, verwenden Sie die Include-Methode. Diese Methode akzeptiert ein Array von Zeichenfolgen, die virtuelle Pfade darstellen. Sie können die CSS-Dateien wie im Beispiel oben explizit angeben, oder Sie können eine Musterzeichenfolge angeben, wie in diesem Beispiel gezeigt:

bundles.Add(new Bundle("~/mycss")
  .Include("~/content/styles/*.css");

Die Bundle-Klasse hat auch eine IncludeDirectory-Methode, mit der Sie den Pfad zu einem bestimmten virtuellen Verzeichnis angeben können, und möglicherweise eine Mustervergleichs-Zeichenfolge sowie ein boolesches Flag, um auch die Suche in Unterverzeichnissen zu aktivieren.

Im vorstehenden Codeausschnitt sehen Sie, dass für die BundleTable-Klasse die boolesche EnableOptimization-Eigenschaft festgelegt wurde. Dies geht darauf zurück, dass eine explizite Aktivierung der Bündelung erforderlich ist. Die Bündelung funktioniert nur, wenn sie programmtechnisch aktiviert wird. Wie erwähnt ist die Bündelung eine Form der Optimierung, und sie ist daher zumeist bei einer Website in der Produktion sinnvoll. Mithilfe der EnableOptimization-Eigenschaft können Sie die Bündelung auf komfortable Weise so einrichten, wie sie in der Produktion erfolgen soll. Deaktivieren Sie die Eigenschaft aber, bis die Website im Debugmodus kompiliert wird. Sie können sogar den folgenden Code verwenden:

if (!DEBUG)
{
  BundleTable.EnableOptimizations = true;
}

Erweiterte Bündelungsfeatures

Was CSS-Pakete angeht, gibt es außer der Minimierung keine weiteren relevanten Features. Die BundleCollection-Klasse ist allerdings eine allgemein verwendbare Klasse, mit der Sie auch Skriptdateien bündeln können. Insbesondere hat die BundleCollection-Klasse einige Features, die erwähnt werden sollten, obwohl sie besonders nützlich sind, wenn Skriptdateien anstelle von CSS-Dateien gebündelt werden.

Das erste Feature ist das Sortieren. Die BundleCollection-Klasse verfügt über eine Eigenschaft namens „Orderer“ vom Typ „IBundleOrderer“. Obwohl es offensichtlich erscheinen mag, ein Orderer-Element ist dafür zuständig, die Reihenfolge zu bestimmen, in der Sie die Dateien für den Download bündeln möchten. Standardmäßig dient hierzu die DefaultBundleOrderer-Klasse. Diese Klasse sortiert die Dateien in der Reihenfolge, die aus den Einstellungen hervorgeht, die durch „FileSetOrderList“ – eine Eigenschaft von „BundleCollection“ – festgelegt werden. Die FileSetOrderList-Eigenschaft ist als eine Auflistung von BundleFileSetOrdering-Klassen konzipiert. Jede dieser Klassen definiert ein Muster für Dateien (zum Beispiel „jquery-*“), und die Reihenfolge, in der die BundleFileSetOrdering-Klassen zu „FileSetOrderList“ hinzugefügt werden, bestimmt die Reihenfolge der Dateien. Zum Beispiel werden bei Verwendung der Standardkonfiguration alle jQuery-Dateien immer vor Modernizr-Dateien gebündelt.

Die Auswirkung der DefaultBundleOrderer-Klasse auf CSS-Dateien ist eingeschränkter. Wenn Sie auf der Website eine Datei namens „reset.css“ oder „normalize.css“ haben, werden diese Dateien automatisch vor den gesamten CSS-Dateien gebündelt, wobei „reset.css“ immer „normalize.css“ vorausgeht. Wenn Sie nicht mit reset-/normalize-Stylesheets vertraut sind – der Zweck dieser Stylesheets ist die Bereitstellung eines Standardsatzes von Stilattributen für alle HTML-Elemente, sodass die Seiten keine browserspezifischen Einstellungen wie Schriftarten, -größen und Seitenränder erben. Obwohl es für beide Arten von CSS-Dateien einigen empfohlenen Inhalt gibt, bleibt der letztlich verwendete Inhalt Ihnen überlassen. Wenn Sie Dateien mit diesen Namen im Projekt haben, unternimmt ASP.NET MVC 4 zusätzliche Anstrengungen, um sicherzustellen, dass sie vor allem anderen gebündelt werden. Wenn Sie das Standardsortierelement außer Kraft setzen und vordefinierte Reihenfolgen von Paketdateigruppen ignorieren möchten, haben Sie zwei Möglichkeiten. Erstens können Sie ein eigenes Sortierelement erstellen, das pro Paket funktioniert. Hier ein Beispiel, mit dem vordefinierte Sortierreihenfolgen einfach ignoriert werden:

public class PoorManOrderer : IBundleOrderer
{
  public IEnumerable<FileInfo> OrderFiles(
    BundleContext context, IEnumerable<FileInfo> files)
  {
     return files;
  }
}

Sie verwenden es folgendermaßen:

var bundle = new Bundle("~/mycss");
bundle.Orderer = new PoorManOrderer();

Zusätzlich können Sie mit folgendem Code alle Reihenfolgen zurücksetzen:

bundles.ResetAll();

In diesem Fall hat die Verwendung vom Standardsortierelement oder von „PoorManOrderer“ dieselbe Auswirkung. Allerdings setzt „ResetAll“ auch Skriptreihenfolgen zurück.

Das zweite, anspruchsvollere Feature ist die Liste ignorierter Elemente. Die Liste wird durch die IgnoreList-Eigenschaft der BundleCollection-Klasse festgelegt und definiert die Mustervergleichs-Zeichenfolgen für Dateien, die zum Einfügen ausgewählt wurden, stattdessen aber ignoriert werden sollen. Der Hauptvorteil von Paketen, so wie sie in ASP.NET MVC 4 implementiert sind, besteht darin, dass Sie alle JavaScript-Dateien (*.JS) im Ordner in einem einzigen Aufruf erhalten können. Wahrscheinlich stimmt *.JS auch mit Dateien überein, die Sie nicht herunterladen möchten, zum Beispiel vsdoc.js-Dateien. Die Standardkonfiguration für „IgnoreList“ berücksichtigt die meisten häufigen Szenarios und kann außerdem von Ihnen angepasst werden.

CSS-Bündelung in Aktion

Die CSS-Bündelung ist ein leistungsfähiges Optimierungsfeature, aber wie funktioniert sie in der Praxis? Betrachten Sie den folgenden Code:

var bundle = new Bundle("~/mycss");
bundle.Include("~/content/styles/*.css");           
bundles.Add(bundle);

Der entsprechende HTTP-Datenverkehr wird in Abbildung 2 gezeigt.

The Second Request Is a Bundle with Multiple CSS Files
Abbildung 2: Die zweite Anforderung ist ein Bündel mit mehreren CSS-Dateien

Die erste Anforderung ist für die Homepage. Die zweite Anforderung zeigt nicht auf eine bestimmte CSS-Datei, sondern verweist auf ein Paket, dass alle CSS-Dateien im Inhalts-/Stilordner enthält (siehe Abbildung 3).

An Example of Bundled CSS
Abbildung 3: Ein Beispiel für gebündelten CSS-Code

Hinzufügen von Minimierung

Die Bundle-Klasse hat nur die Aufgabe, mehrere Ressourcen zusammenzufassen, sodass sie in einem einzelnen Download erfasst und zwischengespeichert werden.

Wie Sie im Code in Abbildung 3 sehen können, ist der heruntergeladene Inhalt aber mit Leerzeichen und Zeilenvorschubzeichen aufgefüllt, die der besseren Lesbarkeit dienen. Browser interessieren sich allerdings nicht für die Lesbarkeit, und für einen Browser gleicht der CSS-Code in Abbildung 3 haargenau der Zeichenfolge im folgenden minimierten Code:

    html,body{font-family:'segoe ui';font-size:1.5em;margin:10px}
      html,body{background-color:#111;color:#48d1cc}

Für den einfachen CSS-Code, mit dem ich in diesem Beispiel arbeite, ist die Minimierung wahrscheinlich kein wesentlicher Faktor. Bei Unternehmenswebsites mit umfangreichen Stylesheets ist minimierter CSS-Code allerdings sehr sinnvoll.

Wie wird die Minimierung hinzugefügt? Dazu wird einfach die Bundle-Klasse durch „StyleBundle“ ersetzt. „StyleBundle“ ist überraschend unkompliziert. „StyleBundle“ erbt von „Bundle“ und besteht nur aus einem anderen Konstruktor:

public StyleBundle(string virtualPath)
  : base(virtualPath, new IBundleTransform[] { new CssMinify() })
{
}

Die Bundle-Klasse hat einen Konstruktor, der eine Liste von IBundleTransform-Objekten akzeptiert. Diese Transformationen werden nacheinander auf den Inhalt angewendet. Die StyleBundle-Klasse fügt lediglich den CssMinify-Transformator hinzu. „CssMinify“ ist der Standardminimierer für ASP.NET MVC 4 (und neuere Versionen) und basiert auf den WebGrease-Optimierungstools (webgrease.codeplex.com). Selbstverständlich können Sie zu einem anderen Minimerer wechseln. Sie müssen lediglich die Klasse haben – eine Implementierung von „IBundleTransform“ – und diese über den Konstruktor der Bundle-Klasse übergeben.

Keine Ausreden mehr

Das Web Optimization Framework zusammen mit ASP.NET MVC 4 fügt eine minimale Funktionalität hinzu, aber diese ist in hohem Maße sinnvoll. Es gibt einfach keinen Grund, Ressourcen nicht zu minimieren und zu bündeln. Bis jetzt konnte die fehlende systemeigene Unterstützung in der ASP.NET-Plattform als Entschuldigung gelten. Mit ASP.NET MVC 4 und neueren Versionen ist das nicht mehr der Fall.

Soweit es CSS-Dateien betrifft, ist allerdings ein weiterer Aspekt zu berücksichtigen: die dynamische Stilgenerierung. Als reines Design zur Anwendung auf Seiten konzipiert, entwickelt sich CSS zu einer dynamischeren Ressource, sodass einige Pseudoprogrammiersprachen eingeführt wurden, um CSS programmtechnisch zu generieren. Im nächsten Artikel werde ich mich genau damit beschäftigen.

Dino Esposito ist der Autor von „Architecting Mobile Solutions for the Enterprise” (Microsoft Press, 2012) sowie des in Kürze erscheinenden „Programming ASP.NET MVC 5” (Microsoft Press). Esposito ist Technical Evangelist für die .NET- und Android-Plattformen bei JetBrains und spricht häufig auf Branchenveranstaltungen weltweit. Auf software2cents.wordpress.com und auf Twitter unter twitter.com/despos lässt er uns wissen, welche Softwarevision er verfolgt.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Christopher Bennage (Microsoft)
Christopher Bennage ist Entwickler im Patterns & Practices-Team von Microsoft. Seine Aufgabe ist es, Methoden zu erforschen, zu sammeln und weiterzugeben, mit denen die Softwareentwicklung Spaß macht. In letzter Zeit hat er sich vor allem mit JavaScript und (gelegentlich) mit der Spieleentwicklung beschäftigt. Seinen Blog finden Sie unter dev.bennage.com.