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ý prototype s lightboxem 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