Pagespeed optimieren durch Zusammenfassen von CSS und Javascript Files

5. June 2015, Lukas Schnieper [Pimcore, php, Tricks und Tools]

Die Seitenladezeit ist ein Faktor, der die Position der Seite in den Suchmaschinen beeinflusst. Von Google gibt es ein PageSpeed Tool, welches diese analysiert und Verbesserungsvorschläge ausgibt. Diese nehmen wir uns zu Herzen und optimieren die von uns erstellten Webseiten.

Pagespeed analyse vorher

Ohne Optimierung binden wir jedes JavaScript und CSS File separat ein. Je nach anzahl jQuery Plugins kann sich das anhäufen. Der Grund weshalb Google hier reklamiert ist, dass jede Datei einen eigene Verbindung zum Server aufbauen muss. Je mehr Dateien desto öfter wird eine weitere unnötige Verbindung aufgebaut.

Die Lösung ist ganz einfach: Der Inhalt der Dateien wird in jeweils eine Zusammengefasst. So hat man noch genau 2 Dateien anstatt wie in einem unserer Extremfälle knapp über 30.

Die Ausgangslage: Zend_View_Helper HeadScript und HeadLink

Um CSS/JS einzubinden, verwenden wir aktuell die Zend_View_Helper HeadScript und HeadLink. Diese sind Teil des Zend Framework. Im Einsatz sieht das so aus:

// magnific
$this->headScript()->prependFile('/website/static/js/jquery.magnific.js', 'text/javascript');
$this->headLink()->prependStylesheet('/website/static/css/jquery.magnific.css', 'screen,print', false);
// mmenu
$this->headScript()->prependFile('/website/static/js/jquery.mmenu.js', 'text/javascript');
$this->headLink()->prependStylesheet('/website/static/css/jquery.mmenu.css', 'screen,print', false);
//bootstrap
$this->headLink()->prependStylesheet('/website/static/css/bootstrap.css', 'screen,print', false);
$this->headScript()->prependFile('/website/static/js/bootstrap.js', 'text/javascript');
//jquery
$this->headScript()->prependFile('/website/static/js/jquery-1.10.2.js', 'text/javascript');
[...]
echo $this->headMeta().PHP_EOL;
echo $this->headLink().PHP_EOL;
echo $this->headScript().PHP_EOL;

Und schon sieht man wie schnell sich hier die Anzahl Files ansammeln. In der verwenden möchten wir möglichst gleich bleiben. Ganz Konkret ersetzen wir die Methoden headScript durch minifyHeadScript und headLink durch minifyHeadLink. Damit diese Aufrufe dann auch etwas bewirken, erstellen wir im Verzeichnis /website/view das helper Verzeichnis. Darin kommen die zwei Klassen Zend_View_Helper_MinifyHeadScript und Zend_View_Helper_MinifyHeadLink.

Der HeadLink ViewHelper

Der HeadLink ViewHelper erbt von der bestehenden HeadLink Klasse und überschreibt im Prinzip nur die toString methode. Darin gehen wir sämtliche Einträge durch, und gruppieren diese. Den media-Tag beachten wir ebenfalls, so dass wir nur auch wirklich zusammengehörende Ziele (print,screen) zusammenfassen. Sonst haben wir plötzlich Gestaltungsanweisungen für den Druck im Browser. Auch ignoriert werden Conditional Stylesheets, also z.B. solche nur für IE, sowie solche die wir von anderen Servern nachladen.

Im Debug-Mode allerdings verzichten wir auf das Zusammenhängen, da es mit einzelnen Dateien einfacher ist, allfällige Probleme über den Browser-Debugger zu orten.

$this->getContainer()->ksort();

foreach ($this as $item) {
  if ($item->type == 'text/css' && $item->conditionalStylesheet == false && strpos($item->href, 'http://') === false) {
    $combine[$item->media][] = $item->href;
    if (Pimcore::inDebugMode()) {
      $items[] = $this->itemToString($item);
    }
  } else {
    $items[] = $this->itemToString($item);
  }
}

Den Inhalt aller dieser Dateien lesen wir aus und erstellen eine Datei im tmp Verzeichnis an, welche dann alle Files an Band enthält. Wir gehen davon aus, dass man sich im DebugMode befindet, wenn man an der Webseite und somit an den CSS und JavaScript Dateien arbeitet. So verwenden wir als Dateiname ein Hash der darin enthaltenen Dateien. Im Debug Mode wird diese gelöscht und so sicher neu erzeugt, wenn man was ändert. So spaaren wir uns alle Dateien jedes mal auf Änderungen zu prüfen, was nur unnötig Zeit kosten würde.

foreach ($combine as $media => $styles) {
  //tmp filename
  $file = $this->tmpPath.'/'.md5(json_encode($styles)).".css";
  if (Pimcore::inDebugMode()) {
    //delete cached file in debug mode
    @unlink(PIMCORE_DOCUMENT_ROOT.$file);
  } else {
    if (!is_file(PIMCORE_DOCUMENT_ROOT.$file)) {
      foreach ($styles as $style) {
        //search for a minified version of the file
        $split = explode(".", $style);
        $ext = array_pop($split);
        $minfile = join(".", $split).".min.".$ext;
        if (is_file(PIMCORE_DOCUMENT_ROOT.$minfile)) {
          $style = $minfile;
        }

        //append the file to the cache file
        file_put_contents(PIMCORE_DOCUMENT_ROOT.$file, file_get_contents(PIMCORE_DOCUMENT_ROOT.$style)."\n",  FILE_APPEND);
      }
    }
    //append minified stylesheet
    $minStyles = new stdClass();
    $minStyles->rel = 'stylesheet';
    $minStyles->type = 'text/css';
    $minStyles->href = $this->tmpPath.'/'.md5(json_encode($styles)).".css";
    $minStyles->media = $media;
    $minStyles->conditionalStylesheet = false;
    $items[] = $this->itemToString($minStyles);
  }
}

Eine kurze Ergänzung noch zum Code. Diese prüft beim Zusammenhängen der Dateien ob eine Datei mit dem Zusatz "min" vorhanden ist. Sofern das der Fall ist, wird diese verwendet. Darin sind dann in der Regel sogleich noch alle Kommentare, Zeilenumbrüche und Leerzeichen entfernt. Diese sind für den Browser nämlich auch unnötig. So wird man gleichzeitig auch der Anforderung nach Komprimierung gerecht.

Seitenladezeit optimiert!

Und somit wird die Seitenladezeit mit einfachen Mitteln optimiert, ohne dass man sich als Entwickler noch darum kümmern muss.

Der HeadScript fürs JavaScript funktioniert bis auf ganz kleine Unterschiede genau gleich.

Google gibt uns zum Schluss noch genau einen Abzug, weil ein Javascript File ein Expiration Date in den HTTP Header bekommen sollte. Das fiese daran: Es handelt sich um das Google Analytics Javascript File. Das können wir leider nicht beeinflussen.

zurück