GZipování podruhé: skripty a styly

Kdysi v seriálu zapomíná se na… jsem zmínil, že se zapomíná na GZip, tenkrát to bylo myslím čistě ve vztahu k HTML dokumentům. Dnes bych připomenul to ostatní co je možné, vhodné a snadné gzipovat – jsou to skripty a stylopisy.

K čemu je to dobré?

Je to velmi dobré. Je doba JS frameworků, mnohé z nich jsou tak trošku masivní, mnoho-desítek-kilobajtové a zde přichází šance pro kompresi! Místo abyste návštěvníkům cpali například takový prototypelightboxem za 70 KB, můžete jim to samé nabídnout za skvělých 16 KB. Podobně můžete stlačit cojávím jQuery na pouhých 13 KB a potom už přestává platit rovnice „JS FW = ach jo, tolik KB navíc“. A tak dále.

Kód (verze 2008.1)

<?php

/* Superior GZip compressor
 * - version: 2008.1
 *
 * - script for easy compressing of external java-script files, css files or else.
 * - (c) Peter Kahoun aka Kahi
 */

if (!isset($_GET['file']))
        die('// ERROR: use $_GET[\'file\'] variable to select file(s). Multiple filenames separate with +.');

$file = $_GET['file'];

//*********************************
// prepare list of files

$files = explode(' ', $file);

$dates = array();
$report = array();

foreach ($files as $key => $file) {

        $file = basename($file);

        if (empty($file)) {
                unset($files[$key]);
        }

        elseif (!is_file($file)) {
                unset($files[$key]);
                $report[] = " * file: $file ... not found";
        }

        else {
                $files[$key] = $file;
                $dates[] = filemtime($file);
        }

}

if (!count($files))
        die('// ERROR: no file included');


//*********************************
// headers & caching


// headers #1 -- if-modified-since

$top_date = max($dates);
$top_date_gmt = gmdate('D, d M Y H:i:s ', $top_date) . 'GMT';

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])  AND  $topdate <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
        Header('HTTP/1.1 304 Not Modified');
        exit();
}


// headers #2 -- content-type

$content_types = array (
        'js' => 'text/javascript',
        'css' => 'text/css');

        // manual choice
if (isset($_GET['type']) AND array_key_exists($_GET['type'], $content_types)) {
        $content_type = $content_types [$_GET['type']];
}

        // automatic choice
else {
        $extension = $files[0];
        // TODO: handle parametrized-scripts (script.js?par=val)
        $extension = substr($extension, strrpos($extension, '.') + 1);

        if (array_key_exists($extension, $content_types))
                $content_type = $content_types [$extension];
        else
                $content_type = $content_types ['js'];
}

Header('Content-type: ' . $content_type . '; charset: UTF-8');


// headers #3 -- last-modified & expires

Header('Last-Modified: ' . $top_date_gmt);
Header('Expires: ' . gmdate('D, d M Y H:i:s ', time() + 3600*24) . 'GMT'); // maybe you would like to make a change here... default = 1 day


//*********************************
// print it out!

$content = '';

foreach ($files as $file) {

        if ($content_type != 'text/css')
                $content .=  "/// file: $file\n\n";

        $content .= file_get_contents($file);
        $content .= "\n\n";
}

// start compression
if (function_exists('ob_get_level') && (ob_get_level() > 0))
  ob_start("ob_gzhandler");

echo $content;

if (count($report))
        echo "/* REPORT!\n" . implode("\n", $report) . "\n */";

?>

…který umí prakticky všechno co po něm chci – komprimovat skripty a ještě něco navíc.

Co umí navíc, Kahi?

Umí spojovat JS soubory do jednoho, takže můžete ušetřit pár dialogů server-klient, a vaše stránka bude zase o něco svižnější. Tento princip jsem už někde viděl, ale abych si nekřivdil, viděl jsem ho v momentě, kdy jsem ho už dávno sám používal, aby bylo jasno. Jo, a byly přidány kešovací hlavičky, poděkujte dgx.

K tomuto slučování JS souborů bych ještě chtěl poznamenat, že nefunguje vždy – některé kombinace zkrátka mohou způsobit nefunkčnost, nevím přesně proč, dokonce občas může být java-skript znefunkčněn pouhou gzip kompresí, opět nevím proč. Berte to tak, že slučování je funkce ve verzi beta a jestli to chcete používat, tak obezřetně, opatrně.

Mimochodem, není možné definovat cestu k .js souboru – skripty musí být ve stejném adresáři jako kompresní PHP skript. Zatím.

Jak se teda Superior GZip compressor používá?

Dříve:

<script src="/scripts/prototype.js" type="text/javascript"></script>
<script src="/scripts/lightbox.js" type="text/javascript"></script>

Dnes:

<script src="/scripts/_zip.php?file=prototype.js+lightbox.js" type="text/javascript"></script>

Háčky, potenciály, vize

  • Kromě háčků které jsem zmínil výše (to jest problém se slučováním) je tu ještě dost… ne přímo háčků, ale spíše potenciálu.
  • Skript se neumí poprat se situací, kdy chcete zkomprimovat skript doplněný parametrem (konkrétní případ: scriptaculous.js?load=effects)

O verzích

  • 2007.2 – bezpečnostní záplata
  • 2007.3 – přidány kešovací hlavičky, značná reorganizace kódu
  • 2007.4 – automatická volba MIME typu (tzn. pomocí toho samého souboru můžete komprimovat js i css); přidána hlavička Expires pro ještě lepší kešování
  • 2008.1 – úpravy v gzip nastavení, předchozí řešení za určitých okolnostích způsobovalo chyby

Komentáře (8)

k formuláři

RSS kanál komentářů

    • Komentář číslo: 1
    • *
    • Jméno: OndroNR
    • Odesláno:
      18.11. 2007 — 16:44

    a co toto href=„http://­code.google.com/p/jscss­comp/“ rel=„nofollow“>http:/­/code.google.com/p/jscss­comp/ ?

    • Komentář číslo: 2
    • *
    • Jméno: Kahi
    • Odesláno:
      18.11. 2007 — 16:56

    [1] OndroNR: – nojo, pěkný. Ale… nějak to není ono :-)

    • Komentář číslo: 3
    • *
    • Jméno: dgx
    • Odesláno:
      19.11. 2007 — 3:24

    Sleduj, jo?

    1. odešlu ti komentář obsahující <?php delete_uplne_vsechno­(HNED);
    2. jeho kešovaná verze bude uložena v souboru „\wp-content\plugin­s\…\21654564…html“ (dopočítám si)
    3. tento skript pustím přes kahi.cz/blog/scr­ipts/_zip.php?fi­le=\wp-content\plugin­s\…\21654564…html
    4. blog je fuč
    • Komentář číslo: 4
    • *
    • Jméno: Kahi
    • Odesláno:
      19.11. 2007 — 5:40

    Trochu jsem to přepsal, díra zmíněná by dgx už tam není…

    • Komentář číslo: 5
    • *
    • Jméno: Arcao
    • Odesláno:
      19.11. 2007 — 13:08

    Hi, misto Last-Modified hlavicky bych radeji pouzil Expires. Jde o to, ze u Last-Modified se musi udelat pro kazde nacteni stranky dotaz, zda se cilovy soubor nezmenil. Expires tohle proste nedela, pac pres Expires mu reknes, na jak dloho si ma uchovat soubor v cache.

    Osobne pouzivam kod:

    header_mod.php:

    content type
    $ct = Array('css'=>'text/css', 'js'=>'application/x-javascript', 'gif'=>'image/gif', 'png'=>'image/png', 'jpg'=>'image/jepg', 'jpeg'=>'image/jpg');
    
    //nemame li soubor, koncime
    if (!$_GET['file']) {
     header("HTTP/1.0 404 Not Found");
     exit;
    }
    
    $file = './' . $_GET['file'];
    
    //ziskame priponu
    $ext = substr($file, strrpos($file, '.') + 1);
    //nemame content type, koncime (ochrana proti nabourani)
    if (!isset($ct[$ext])) {
     header("HTTP/1.0 404 Not Found");
     exit;
    }
    //a content type
    $content_type = $ct[$ext];
    
    $time = time();
    //pridame 10 dni (doporucuje se dat i vice)
    $time+=10*24*3600;
    
    //nastavime hlavicky
    header("Expires: ".gmdate("D, d M Y H:i:s",$time)." GMT");
    header("Cache-Control: must-revalidate");
    header("Content-Type: $content_type");
    
    //pokud je soubor obrazek, tak nezapneme gz kompresi
    if (strpos($content_type, 'image/') === false) ob_start("ob_gzhandler");
    
    echo file_get_contents($file);
    ?>

    a pak jeste na to aplikuju mod_rewrite:

    .htaccess:

    RewriteEngine on
    RewriteRule (.+\.(css|js|gif|png|jpg|jpeg))$ /header_mod.php?file=$1 [L]
    
    #vypneme ETag
    FileETag none

    Poznamka: Osdobne mam zlib.output_com­pression vypnuty, ale pro lepsi prenositelnost by bylo lepsi pridat testovani, tak jak ho tam ma Kahi.

    • Komentář číslo: 6
    • *
    • Jméno: Arcao
    • Odesláno:
      19.11. 2007 — 13:26

    [3] dgx: tam urco neprotlacis. Minimalne Texy! prevede <a> na entity. Pripadne kazdy poradny RS pro PHP ma osetrene v komentarich, aby jsi nemohl do textu narvat surovy php kod.

    • Komentář číslo: 7
    • *
    • Jméno: Kahi
    • Odesláno:
      21.11. 2007 — 22:13

    [5] Arcao: – zajímavé. Osobně bych možná vyřešil kešování obrázků přes mod_expires a kód obou souborů by se pak mohl trochu zjednodušit…

    • Komentář číslo: 8
    • *
    • Jméno: dgx
    • Odesláno:
      23.11. 2007 — 13:03

    [5] Arcao: nejde o to použít Expires místo Last-Modified, použít by se mělo obojí zároveň. Last-Modified aby dělal dotaz a Expires aby ho dělal třeba jen ob den.

    [6] Arcao: o to tady nešlo. Původní kód obsahoval nějaké díry, přes které bylo možné spustit kód nebo vypsat konfigurační soubor. Ty už Kahi opravil.

Přidat komentář

Nápověda ke psaní komentářů

Zde formátuje Texy!

  • *zvýraznění*
  • **silné zvýraznění**
  • > citace
  • "odkaz":http://kam
  • [4] reakce na komentář
  • zdrojové kódy a více

komentáře

úplně nahoru