CSS: Knapp, frisch und lange haltbar
24.04.2010 in: CSS, Interna, Webdesign und WordPress • 19 Kommentare
Vor einiger Zeit habe ich mal gezeigt, wie man mittels eines UNIX-Timestamps in der Adresse dem Browser immer das aktuelle Stylesheet darbietet:
<link rel="stylesheet
" media="screen,projection
" href="/wp-content/themes/toscho/css?d=1229632675
" title="screen+projection
" type="text/css
" >
Nachteile der alten Variante
Das Grundprinzip verwende ich immer noch, aber im Zuge des letzten Redesigns habe ich es um ein paar Nuancen verfeinert. An der alten Variante haben mich nämlich einige Dinge gestört:
- Ich mußte den Cache des Clients immer per PHP validieren, was unnötig Zeit kostet.
- Die resultierende URL sieht lang und umständlich aus. Durch das Fragezeichen darin fühlten sich nicht alle Browser zum Cachen angeregt.
- Das Stylesheet mußte unter dem selben Host liegen wie das Theme, was parallele Requests erschwert.
Umzug auf die Subdomain
Nun habe ich hier noch eine ungenutzte Domain herumliegen – mundonaut.de –, auf der mein Bruder eigentlich ein bißchen bloggen wollte. Oder sollte.
Trotz Prüfungen, Umzug und Magisterarbeit hat er das bis heute seltsamerweise nicht geschafft, und so gammelte die Domain nur so vor sich hin. Schade.
Ich habe dort jetzt eine Subdomain angelegt: t4.mundonaut.de, auf der ich alle kleinen Dateien für dieses Theme parke: Bilder, Javascripte und Stylesheets.
Die können jetzt von den Browsern parallel zu dieser Seite und ihren Inhaltsbildern heruntergeladen werden, die nur wenige Verbindungen pro Host öffnen – IE 7 beispielsweise nur zwei. Außerdem exisitieren für die Subdomain keine Cookies; das reduziert auch den HTTP-Overhead ein wenig.
Schöner Dateiname
Den UNIX-Timestamp habe ich vom Parameter in den Dateinamen verlegt. Die neue Adresse des Stylesheets – ich benutze jetzt nur noch eines für alle Medientypen und eins für alte Internet Explorer – sieht nun beispielsweise so aus:
http://t4.mundonaut.de/1271536949.css
Den lokalen Dateipfad habe ich fest ins Theme geschrieben; mittels filemtime() erzeuge ich den passenden Namen.
In der .htaccess der Subdomain t4 lenke ich die Anfragen dann auf die jeweils passende Datei:
# Ein UNIX-Timestamp enthält genau 10 Ziffern: \d{10} RewriteEngine On RewriteRule ^\d{10}\.css main.css [L] RewriteRule ^\d{10}\.ie\.css ie.css [L]
Jetzt kümmert sich der Server um die Validierung des Client-Caches; der macht das natürlich viel schneller als ich.
Kompression
Auch die übernimmt der Server. Bei meinem Webhoster all-inkl.com ist die Kompression für Textdateien ohnehin schon angeschaltet; auch darum muß ich mich nicht mehr kümmern. Müßte ich es, so verwendete ich mod_deflate:
AddOutputFilterByType DEFLATE text/plain text/css
Der Apache speichert das Ergebnis; so hält sich die Mehrarbeit in engen Grenzen.
Lokal komprimiere ich das Stylesheet vor: Ich entferne unnötige Leerzeichen, Zeilenumbrüche und Kommentare. Für das Script gibt es auch eine Weboberfläche im Labor: CSS Compressor.
ETag
Damit die Cache-Validierung besser klappt, habe ich noch ETags aktiviert:
FileETag MTime Size
Obacht! Ältere Apachen (vor Version 2.2.12) erzeugen einen kaputten ETag für automatisch komprimierte Dateien. Der sieht dann so aus:
ETag: "47b5361382042"-gzip
Damit kann man den Cache des Clients bestenfalls verhindern. Ich habe All-Inkl darauf hingewiesen – und einen Tag darauf hatte ich einen aktualisierten Server. Das nenne ich Service.
Expires
Als Sahnehäubchen gibt es noch eine ausgedehnte Verfallszeit:
# Lang lebe euer Cache! ExpiresActive On ExpiresByType image/png"access plus 1 year"
ExpiresByType image/jpeg"access plus 1 year"
ExpiresByType image/gif"access plus 1 year"
ExpiresByType image/x-icon"access plus 1 year"
ExpiresByType image/icon"access plus 1 year"
ExpiresByType application/x-javascript"access plus 3 months"
ExpiresByType text/css"access plus 3 months"
Nachteile der neuen Variante
Das Theme ist jetzt fest an diese Domain gebunden und nicht mehr portabel. Das spielt hier keine Rolle; aber wer Themes schreibt, deren Einsatzort unbekannt ist, kann damit wenig anfangen.
Die ETag-Direktive kann nur bei einem halbwegs aktuellen Server benutzt werden, und mod_rewrite braucht man auch.
Andererseits läßt sich das Verfahren auf jedes Layout übertragen, ob es nun von WordPress erzeugt wird oder nicht.
Schepp am 24.04.2010 · 11:38
Wenn Du einen Timestamp verwendest, dann kannst Du die ganze eTag-Apparatur auch ganz abschalten.
Das eTag-Konstrukt ist ja nur dann von Nutzen, wenn der Browser die Datei nochmal unter demselben Namen anfragt, trotz dass er sie schon hat, und der Server nun entscheiden können soll, ob er die Daten nochmal sendet oder ein 304er zurückschickt.
Da Du ein aggressives clientseitiges Caching per mod_expires erzwingst, fragen die Browser die Datei unter diesem Timestamp eh nicht nochmal an (bzw. so gut wie nie). Und tun sie es doch, dann nur weil sich der Timestamp der CSS-Datei geändert hat, und dann musst Du die Datei definitiv schicken.
Dein Ding ist demnach doppelt gemoppelt :)
Thomas Scholz am 24.04.2010 · 11:47
@Schepp: Technisch gesehen stimmt das natürlich. In der Praxis validieren WebKit und manchmal auch Opera ungeachtet des Expires-Headers ihren Cache vor Ablauf der Haltbarkeit beim Server. Das mag ich nicht ignorieren.
Schepp am 24.04.2010 · 11:54
Das solltest Du dann aber noch dazuschreiben, denn wenn das stimmen sollte, ist das eine sehr interessante Info (und natürlich auch eine das vermeintlich Doppeltgemoppelte erklärende).
Hast Du da irgendwelche weiterführenden Infos zu, die man sich mal reindröhnen kann?
Francesco am 24.04.2010 · 13:29
Gibt es einen bestimmten Grund, warum du die
media
-Anweisungim HTML-Code angibst? Ich persönlich finde es sinnvoller, sämtliche
media
-Zuordnungen in der CSS-Datei zu regeln. Dann bleibt wirklich alles an einem Ort und macht den Code insgesamt übersichtlicher bzw. wartbarer.David am 24.04.2010 · 13:49
Kann man denn davon ausgehen, dass alle Browser mit den komprimierten Dateien was anfangen können?
Schepp am 24.04.2010 · 13:52
@David Die IEs ab IE6 SP2+ (2004) und alle anderen Browser können das.
Thomas Scholz am 24.04.2010 · 14:12
@Schepp: Ich habe hier mal versehentlich den Expires-Header für alles hochgesetzt – inklusive Posts. Daraufhin haben Firefox-Nutzer nach dem Absenden ihre eigenen Kommentare nicht sehen können. Logisch.
Opera und Webkit hingegen testen ganz smart nach einem POST-Request sicherheitshalber nochmal alle gecachten Dateien. Das entspricht zwar nicht den Vorgaben aus HTTP/1.1, aber es ist pragmatisch.
Weiterführende Informationen dazu habe ich jetzt nicht zur Hand. Ich habe das mit einem Schulterzucken unter »Aha. Mist!« abgelegt …
Thomas Scholz am 24.04.2010 · 14:20
@Francesco: Manche Mobilbrowser wollen das
handheld
explizit notiert sehen; sonst verwerten sie es nicht. Ich möchte mein Stylesheet auch nicht fürbraille
oderspeech
angewandt wissen; deshalb keinmedia="all"
.Natürlich habe ich auch im Stylesheet (lesbare Version) separate @media-Regeln, aber die orientieren sich nicht ausschließlich am Medientyp.
Thomas Scholz am 24.04.2010 · 14:25
@David: Die komprimierte Datei liefert der Apache nur aus, wenn der Accept-Encoding-Header stimmt, also
deflate
,gzip
oderx-gzip
mit einer Priorität größer Null darin steht. Oh, und IE 5 kann Gzip nicht auspacken, wenn der Zugriff über HTTPS läuft; er fordert es aber an. Das trifft mich nicht.Francesco am 24.04.2010 · 14:40
Du schreibst
Ich nehme mal an, dass das automatisiert abläuft? Wenn ja: Verwendest du dafür ein spezielles Plugin?
Thomas Scholz am 24.04.2010 · 15:01
@Francesco: Dafür braucht man kein Plugin, nur ein paar einfache Ersetzungen. Lokal durchläuft das Stylesheet eine Funktion, die (aufs Wesentliche reduziert) so aussieht:
Das erwischt auch Leerstellen innerhalb Generated Contents; aber da sollte man meistens ohnehin geschützte Leerzeichen verwenden.
Webstandard-Team (Heiko) am 30.04.2010 · 08:00
Interessante Herangehensweise Thomas, ohne lokale Komprimierung geht bei mir auch nichts raus.
David am 16.11.2010 · 13:18
Wie darf ich diesen Satz verstehen? Bedeutet das, Apache komprimiert die Dateien bei der ersten Anfrage und legt dann die komprimierte Variante irgendwo ab?
Ich hab bei dem Gedanken daran, dass CSS- und JS-Dateien bei jedem Zugriff neu eingedampft werden irgendwie ein ungutes Gefühl was die Serverlast angeht. Ich bin am Überlegen ob es sinnvoll ist, die Komprimierten Dateien vorzuhalten und dann per Script direkt zu senden, wenn die Header stimmen. Wenn Apache das aber von selbst übernimmt, wäre das ja unsinnig.
Thomas Scholz am 16.11.2010 · 13:40
@David:
Ja, genau. Ich finde gerade den Bug dazu nicht mehr, aber er ist schon lange gefixt.
David am 16.11.2010 · 16:01
Diese Funktion war wohl mal verbugt? Na wenn es inzwischen so ist, kann man ja beruhigt ModDeflate einsetzen.
David am 16.11.2010 · 17:43
Ich werd wohl doch die Scripte und CSS-Datein über ein PHP-Script senden müssen, da HostEurope ModExpires nicht unterstützt. Hat sich All-Inkl. da auch so affig?
Thomas Scholz am 17.11.2010 · 00:30
@David: Das ist wirklich ärgerlich. Geht wenigstens mod_headers? Dann könntest du so vorgehen (ungetestet):
Und nein, bei All-Inkl gibt es damit keine Probleme. Da hätte ich schon laut gebellt. ;)
David am 17.11.2010 · 15:50
Ja, das passt. Super Tipp, danke! ☺
Ich hatte schon mit dem Gedanken gespielt zu wechseln, bisher war ich eigentlich immer sehr zufrieden- Bei meinem Anbieter ist ModExpires deaktiviert, da dieses Modul „eine sehr hohe I/O-Last“ erzeugen würde. Davon hab ich allerdings keine Ahnung.
Gibt es client-seitig einen qualitativen Unterschied zwischen den Headern
Cache-Controll max-age=
undExpires
?Thomas Scholz am 17.11.2010 · 19:41
@David: Die Begründung für das Abschalten will ich lieber nicht kommentieren … Ob mod_expires wirklich teurer ist als eine handgefrickelte PHP-Lösung, kann man sich ja leicht selbst ausrechnen.
Lektüre zu deiner zweiten Frage: Expires vs. max-age. Kurzfassung:
max-age
genügt und kann nicht so leicht schiefgehen.