Vor ca. 2 Monaten gab es auf diesem Blog schon einmal einen Artikel über JavaScript & CSS Performance und die dort vorgestellte Methode habe ich inzwischen noch etwas verfeinert, um noch mehr Performance herauszukitzeln. Wer den Artikel noch nicht gelesen hat, dem empfehle ich das nachzuholen, denn wir bauen auf dem Ergebnis weiter auf.
In Moment funktioniert es ja so, dass unser PHP-Skript die einzelnen JavaScript bzw. CSS-Dateien sammelt und zusammen ausgibt. Noch besser wäre es aber natürlich, wenn das Skript die Dateien nicht bei jedem Aufruf neu sammeln muss und dazu gibt es natürlich nur eine Lösung: Caching.
Serverseiter Cache
Beim Serverseitigen Caching nehmen wir unsere gesammelten Dateien und legen sie zwischenzeitlich unter einer einzigen Datei ab. Das einlesen einer Datei ist selbstverständlich schneller für den Server als wenn er vier einzelne Dateien lesen muss. Jetzt bleibt nur noch die Frage, wie wir vier Dateien in einer zusammenfassen können. Hier arbeiten wir am besten mit einem MD5-Hashwert. Ein Beispiel:
$files = $_GET["f"]; $cacheFile = dirname(__FILE__)."/cache/".md5($files).".js"; if(!file_exists($cacheFile)) { // lade Inhalte der angefragten JS-Dateien... file_put_contents($cacheFile, $inhalt); } echo file_get_contents($cacheFile); |
Wenn wir jetzt nach index.php?f=jquery.js,prototype.js,mootools.js fragen, wird eine Cache-Datei generiert, die relativ kurz und einzigartig ist, aber trotzdem wiedererkannt wird.
Clientseitiger Cache
Nicht nur auf dem Server können wir einen Cache für unsere Dateien laufen lassen, auch der Client (unser Browser) cached die Dateien. Das wird im Normalfall von Webserver automatisch geregelt, aber das funktioniert nur bei statischen Inhalten (HTML-Dokumente, Textdateien, Bilder). Bei dynamischen Websites wird es da schon schwieriger, aber mit PHP können wir da etwas nachhelfen. Mit dem Befehl
header("HTTP/1.0 304 Not Modified"); |
sagen wir dem Browser „die Datei hat sich nicht verändert, nimm die letzte verfügbare Version aus deinem Cache“. Diese Funktion lässt sich natürlich hervorragend in unserem JavaScript/CSS-Loader verwenden. Und so sieht das neue Skript dann aus:
/** * Compressing CSS files. * * @package Yumee * @license <http://www.yumee.de/pyl.txt> Public Yumee License * @version $Id: index.php 275 2010-01-21 20:56:02Z Basti-sama $ */ header("Content-Type: text/css"); $allowedExtensions = array("css"); $cacheExpires = 86400; // 24 Stunden $disableCache = false; $files = $_GET["f"]; $modified = false; $cacheFile = dirname(__FILE__)."/cache/".md5($files).".css"; if($disableCache || !file_exists($cacheFile) || filemtime($cacheFile) < time() - $cacheExpires) { $files = explode(",", $files); $dir = realpath(dirname(__FILE__)); $in = ""; foreach($files as $file) { $file = realpath($dir."/".$file); $ext = pathinfo($file, PATHINFO_EXTENSION); if(file_exists($file) && strpos($file, $dir) !== false && in_array($ext, $allowedExtensions)) { $in .= file_get_contents($file)."\n"; } } if(!is_dir(dirname($cacheFile))) { mkdir(dirname($cacheFile), 0777, true); } file_put_contents($cacheFile, $in); $modified = true; } if(!$modified && isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) { header("HTTP/1.0 304 Not Modified"); exit; } $out = file_get_contents($cacheFile); header("Last-Modified: ".gmdate("D, d M Y H:i:s\G\M\T", time())); if(function_exists("ob_start") && !empty($out)) { ob_start("ob_gzhandler"); } echo $out; |
Erst generieren wir den Pfad zu einer Cache-Datei und überprüfen, ob die Datei existiert. Wenn nicht, sammeln wir wieder alle angefragten CSS-Dateien und speichern diese im Cache ab. Zusätzlich geben wir an, dass der Cache automatisch alle 24 Stunden erneut werden muss.
Kurz vor der Ausgabe, schauen wir aber noch nach, ob der Client die Informationen überhaupt braucht oder ob sie geändert wurden. Wenn ja, senden wir einen „Not Modified“-Header und beenden das Skript. Ansonsten senden wir den Last-Modified Parameter mit dem aktuellen Datum, aktivieren die Gzip-Komprimierung und geben die CSS-Informationen ganz normal aus.
Anmerkung: Wird der Headerparameter Last-Modified gesendet, kann dieser später mit $_SERVER[„HTTP_IF_MODIFIED_SINCE“] abgefragt werden.
Wenn das mal kein performantes Konzept ist… Naja, ein Problem gibt es doch noch. Im produktiven Einsatz ist der Cache super, aber während der Entwicklung extrem störend, denn der Cache muss manuell bei jeder Änderung erneuert werden. Daher während der Entwicklung immer den Flag $disableCache auf true setzen.