Donnerstag, 14. März 2013

Minimieren von HTTP Requests: Single CSS/JS und Sprites

In meinem letzten Artikel über das Verteilen von Ressourcen auf Content Delivery Networks habe ich das Thema schon angeschnitten, dass man die Anzahl HTTP Requests minimieren sollte, um den Multiplikator der Round-Trip-Time gering zu halten. Nun erkläre ich, wie man das Maximum herausholt, indem man Ressourcen zusammenfasst.

Was ist das Problem bei Anforderung zahlreicher Ressourcen?

Die allermeisten Websites laden mehrere CSS und mehrere Javascript Dateien - gerade bei letzteren für einzelne Module der Übersichtlichkeit halber über diverseste Files. Der HTTP 1.1 Standard empfiehlt, dass ein Browser maximal 2 Ressourcen parallel von einem Host anfordert - die meisten Browser fordern zwar mehr an (ca. 6), aber das Problem ist dasselbe: Sind Javascripts über 24 Files verstreut, kann ein Browser diese maximal in einer Sechser-Parallelisierung downloaden - in unserem Fall wären das 4 mal hintereinander 6 parallele Downloads. Folgendes Netzwerk-Audit einer Website mit 20 Javascript Referenzierungen im Source (insgesamt 197 angeforderte Ressourcen)  illustriert dies, man beachte im Tooltip den Wert unter "Blocking" (damit ist gemeint, dass der Browser so lange blockiert war, bis er den Download resp. die Verarbeitung einleiten konnte):


Zum Zeitpunkt der Anforderung dieser Ressource, verbrachte der Browser also rund eine halbe Sekunde nur mit blockierendem Warten.

In meinem Artikel über den Einsatz von Content Delivery Netzwerken (CDN) habe ich berichtet, dass wir dies entschärfen könnten, indem wir diese Ressourcen auf einen anderen Host legen - denn die Limite von 6 parallelen Download bei modernen Browsern gilt per Host.

In einem anderen Artikel von mir habe ich zudem den Einsatz von Cache-Control-Headers empfohlen, womit die Ressourcen nach dem ersten Download im Browser-Cache verbleiben und so nicht jedes Mal neu angefordert werden müssen.

Tipp 1: Zusammenfassen von CSS

Dies ist wohl die einfachste Möglichkeit: CSS wird sequentiell abgearbeitet, also würde es reichen, die CSS Dateien in der richtigen Reihenfolge einfach in eine einzige Datei zusammenzufügen. Damit haben wir nur noch eine CSS Datei, und brauchen damit auch nur einen Request zu machen. 

Es kommt natürlich oft vor, dass gewisse CSS Dateien nur für einzelne Seiten gelten - diese kann man aber auch einzeln belassen. Das Ziel ist einfach, nicht unnötig viele Dateien zu haben. Also weg vom "modularisierten" Denken, wenn es um Performance geht!

Wem dies zu unübersichtlich wird, der kann auch ein kleines Script schreiben, welches ihm die CSS Dateien zusammenfasst und als gecachte Datei auf dem Webserver ablegt. Dies liesse sich soweit automatisieren, dass wenn das Datum der Source Dateien ändert, das Script bei einer neuen Anforderung ein neues Master-CSS generiert und ausliefert. Der Scripting Overhead und die mittlere Filesystemzugriffszeit von einigen Milisekunden machen sich bezahlt, verglichen mit der enormen Zeit für mehrere Round-Trips (vor allem wenn es mehr als 6 sind).

Tipp 2: Zusammenfassen von Javascript

Auch JavaScripts werden sequentiell abgearbeitet, also lassen sich auch diese in einer einzelnen Datei zusammenfassen - nach dem gleichen Prinzip wie bei den CSS Dateien. Ein Paradebeispiel ist jQuery, welches aus einem Master-JavaScript, und für jedes Modul aus einem weiteren CSS-Script besteht. Eine aufwändig interaktive Website wird schnell mal 10-20 solche Module laden, und das sind einfach wieder 4 Round-Trips à 6 parallelen Downloads.

Tipp 3: Arbeiten mit CSS-Sprites

Dabei packen wir das letzte Problem beim Kragen, und das sind übermässig viele Bilder, die einzeln mit HTTP-Requests vom Webserver angefordert werden. Auch hier gilt die Limite von 2 parallelen Downloads gemäss HTTP 1.1 Spezifikation. Was wir tun können ist folgendes: Anstatt 20 kleine Bilder zu erstellen (z.B. Runde Ecken, Buttons, usw.) - erstellen wir ein einzelnes, sehr breites Bild, in welchem alle diese Elemente enthalten sind. Danach positionieren wir die Bilder mit den CSS-Direktiven "background-image" und "background-position", so dass in einem DIV nur jener Ausschnitt des Bildes angezeigt wird, der für das DIV (z.B. eine Schaltfläche) auch interessant ist.

Damit reduzieren wir die Anzahl Requests für 20 Bilder auf einen. Dieser Tipp funktioniert natürlich nur mit Bildern, die wir effektiv per CSS verwenden. Werden die meisten Bilder mit <img> Tags geladen, würde die Umsetzung dieser Strategie zu einem Reeingineering des Web Templates führen. Ob dies den Aufwand rechtfertigt, ist im Einzelfalle abzuklären.

Ein gutes Beispiel für die CSS Sprite Technik sind zum Beispiel Landesflaggen. Anstatt 265 Requests vom Webserver anzufordern, holt man sich das Sprite mit einem einzigen Request. Die Ladeperformance ist definitiv spürbar!

Tipp 4: Minifizieren (verkleinern) von CSS und JS

Es existieren diverse Tools (wie z.B. der YUI Compressor), die aus CSS und Javascripts Leerzeilen, Abstände und Kommentare rausnehmen, um die Grösse der Dateien zu reduzieren. Bei Javascript werden selbst Funktions- und Variablennamen auf ein Minimum abgekürzt. Natürlich ist der Code danach nicht mehr leserlich. Der Hintergedanke ist aber auch, dass man dies nur für die endgültige Version macht, die dann auch bereitgestellt wird. Dies liesse sich mit unter Tipp-1 genanntem Script ebenfalls automatisieren, was ich aber nicht empfehle. Eine minimierte Version eines CSS/JS will trotzdem noch getestet sein.

Bei eingeschalteter serverseitigen Komprimierung ist die Minifizierung fast ohne Gewinn; man holt durch die fehlenden Kommentare usw. in der Regel kaum viel mehr als weitere 5% heraus.

Fazit

Würde man es schaffen, dass eine Website nur aus einem HTML-, einem CSS- und einem Javascript Dokument besteht; und würde man die Bilder mittels der CSS-Sprites Techniken in nur ein Bild packen, so würde die Website mit einem modernen Browser sämtliche Ressourcen im ersten und einzigen Durchgang downloaden können. Während die CSS-Sprite-Technik aufwändig umzusetzen ist, so ist das Zusammenfassen von JS und CSS aber sicher ein kleiner Aufwand. 

Eine umfassende Websites mit 30 CSS und JS Dateien (gerade bei JS Libraries), und einer RTT von 50ms, spart sich damit 200ms an blockierender Wartezeit durch Parallelitätslimitierungen. Kommt ein Besucher aus Übersee, fällt die RTT hier massiver ins Gewicht. Extrem wird es bei Nutzern von mobilen Endgeräten, wo die RTT in der Regel über 200ms liegt. Auf der Strecke Zürich-Bern mit den schweizerischen Bundesbahnen habe ich nicht selten eine RTT von 2000ms über 3G-Netzwerke. Was das für das Laden von 265 Flaggen bedeutet, liegt auf der Hand.

Keine Kommentare:

Kommentar veröffentlichen