Jörg Reinholz: Das Skript zum Sonntag; Minifiziertes und gezipptes CSS senden

Beitrag lesen

Moin!

CSS-Files werden manchmal so groß, dass man diese erst minifizieren und dann auch noch kuhzippen will. Auch sollen die Browser diese cachen, wozu es ggf. spezielle HTTP-Header braucht.

Natürlich wäre es unschön, wenn die Datei bei jedem Abruf minifiziert und gepackt wird ... also werden diese Versionen (und ein Etag für die noch zu schreibende Cache-Optimierung) gespeichert.

Das folgende PHP-Skript wird dieses automatisch einrichten. Man muss nur nach einer Veränderung entweder die minifizierte Version (z.B. style.css.min) oder die gezippte Version style.css.min.gz oder die Datei mit dem ETag löschen. Oder aber css_packer.php?pack=style.css&renew=1 aufrufen.

Erst einmal ein paar Voreinstellungen:

## file: php.ini (im gleichen Verzeichnis wie css_packer.php)
allow_url_fopen = Off

Damit man nicht viele Seiten ändern muss ist dieses für den Apache hoffentlich hilfreich:

## file: .htaccess (im gleichen Verzeichnis wie css_packer.php)
RewriteEngine on
RewriteRule   ^(.*\.css)$ css_packer.php?pack=$1

Eine Readme fürs Erinnern:

## file: css_packer.readme (im gleichen Verzeichnis wie css_packer.php)
Benutzung:
Rufen Sie dieses Skript statt der CSS-Datei auf.

Automatisch geht dieses (in vielen aber nicht allen Fällen) mit einer Umleitung in einer Datei .htaccess :

Beispiel:
RewriteEngine on
RewriteRule   ^(.*\.css)$ css_packer.php?pack=$1

Es ist aber möglich,  css_packer.php?pack=style.css direkt anzugeben.

Hinweise:

Die packcss.php und die .htaccess muss im gleichen Verzeichnis wie die CSS-Datei sein!
Die CSS-Datei MUSS die Endung '.css' haben!

Bei einem Aufruf mit css_packer.php?renew=1&pack=style.css werden die gepackten und gezippten Dateien und das Etag-File erneuert.
Es muss nur die wirklich menschenlesbare Version manuell gepflegt werden...

Und dann das Skript:

<?php
/** file: css_packer.php
Autor: Jörg Reinholz, www.fastix.org
Lizenz: GPL 2.0 http://www.gnu.org/licenses/old-licenses/gpl-2.0.de.html **/

/** Konfiguration:
Wie lange soll der Cache gültig sein? **/
define ('CACHE_DAYS', 7); #Tage

/** Programm: **/

define ('MINIMAL_CSS'            , './' . $_GET['pack'] . '.min');
define ('MINIMAL_CSS_ZIPPED'     , './' . $_GET['pack'] . '.min.gz');
define ('ETAG'                   , './' . $_GET['pack'] . '.etag');

if ( empty($_GET['pack']) )  {
    error_log('Fehler: Keine Datei übergeben. (?pack=)');
    echo "Fehler: Keine Datei übergeben.\n\n";
    include('pack_css.readme');
    exit;
}

if ( substr($_GET['pack'],-4) != '.css' ) {
    error_log('Hinweis: Netter Versuch: (?pack=' . $_GET['pack'] . ')');
    echo "Netter Versuch!\n\n";
    include('pack_css.readme');
    exit;
}

if ( strpos($_GET['pack'], '..') ) {
    error_log('Hinweis: Netter Versuch. (?pack=' . $_GET['pack'].')');
    echo "Netter Versuch!\n\n";
    include('pack_css.readme');
    exit;
}

$_GET['pack']='./' . $_GET['pack'];

if (! empty($_GET['renew']) && $_GET['renew']) {
    // Datei(en) nicht vorhanden? Egal!
    @unlink(MINIMAL_CSS);
    @unlink(MINIMAL_CSS_ZIPPED);
    @unlink(ETAG);
}

if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
        $_SERVER['HTTP_ACCEPT_ENCODING']=false;
}
if (
    (! is_file(MINIMAL_CSS) )
or
    (! is_file(MINIMAL_CSS_ZIPPED) )
or
    (! is_file(ETAG) )
) {
    if ( ! is_file ($_GET['pack']) || ! is_readable ($_GET['pack']) ) {
        die('Fehler: Die CSS-Datei ' . $_GET['pack'] . ' gibt es nicht.');
    }
    if ( ! is_writable (__DIR__) ) {
        error_log('In das Verzeichnis ' . __DIR__ . ' kann nicht geschrieben werden.');
        die('Fehler: In das Verzeichnis kann nicht geschrieben werden.');
    }

    umask(0022);
    $file = file_get_contents($_GET['pack'], NULL, NULL);
    $file = str_replace(array("\n", "\r", "\t"),' ',$file);
    $file = preg_replace('/ {2, }/', ' ', $file);
    $file = str_replace(array(', ',' ,','; ',' ;','{ ',' {','} ',' }',': ',' :'), array(',',',',';',';','{','{','}','}',':',':'), $file);
    file_put_contents(MINIMAL_CSS, $file);
    file_put_contents(MINIMAL_CSS_ZIPPED, gzencode($file, 9));
    file_put_contents(ETAG, dechex(crc32($file)));
}

$max_age = CACHE_DAYS * 86400;
$exp_gmt = gmdate("D, d M Y H:i:s", time() + $max_age) . ' GMT';
$mod_gmt = gmdate("D, d M Y H:i:s",  filemtime($_GET['pack']) ) . ' GMT';

if (false===strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
    header ('Content-Type:text/css');
    header ("Expires:$exp_gmt");
    header ('Cache-Control:public');
    header ("Cache-Control: max-age=$max_age, public");
    header ("Last-Modified:$mod_gmt");
    header ('Etag: "' . file_get_contents(ETAG) . '"');
    readfile (MINIMAL_CSS);
    exit;
} else {
    header ('Content-Type:text/css');
    header ("Expires:$exp_gmt");
    header ('Cache-Control:public');
    header ("Cache-Control: max-age=$max_age, public");
    header ("Last-Modified:$mod_gmt");
    header ('Etag: "' . file_get_contents(ETAG) . '"');
    header ('Vary:Accept-Encoding');
    header ('Content-Encoding:gzip');
    readfile (MINIMAL_CSS_ZIPPED);
    exit;
}

Falls sich jemand aufregen will: Das ist meine Schönschrift. Und das Skript läuft auf fastix.org.

Jörg Reinholz