Javascript és CSS fájlok automatikus frissítése

Egy folyamatosan zajló webfejlesztés velejárója, hogy a böngészők agresszív cache techinkái minden frissítés után törött oldalakat eredményeznek. Képzeljük csak el, hogy a frissített tartalom töltődik le a látogató böngészőjébe, viszont ha talál cache-elt stíluslapot (.css), vagy külső javascript (.js) fájlt, a böngésző a régi, letárolt változatot fogja használni, anélkül, hogy ellenőrizné ennek frissülését.

A megoldás igen egyszerű: egy Reload, vagy SHIFT + Reload megoldja a problémát, ezt viszont nem írhatjuk ki az oldalra.

Verzió ellenőrzés

Egy frissült verziójú javascript ellenőrzése igen egyszerű. Mivel egy új script beépítése általában a html tartalom frissülésével együtt jár, egy változó párral könnyen ellenőrizhetjük, hogy a látogató gépén is a legfrissebb verzió van-e. Nézzük a képzeletbeli menu.js tartalmát:

menuVersion = 3; function menu () { ... }

A hozzátartozó oldal (inline scriptben) leellenőrizheti, hogy a külső .js fájlokból érkező verziók a legújabbak-e (ehhez az oldalnak tartalmaznia kell a megfelelő verziók számát).

Ez a módszer ugyan működik, de legfeljebb a felhasználó figyelmeztetésére jó, ami nem a legelegánsabb megoldás.

Szelektív cache tiltás (mod_expires)

Ha Apache webszervert hasznáunk (és miért ne használnánk) lehetőségünk van egy kiváló direktíva, az ExpiresByType használatára. Ennek segítségével minden MIME tipusnak beállíthatunk egy saját elévülési időt, aminél tovább a böngésző nem fogja már tárolni. (A következő részletet egy .htaccess fájlba helyezve, vagy a szerver konfiguráció <Directory> direktívájában hazsnálhatjuk.) Nézzünk egy példát:

ExpiresActive on ExpiresByType application/x-javascript A3600 ExpiresByType text/css A86400

Az első sor ExpiresActive aktiválja a szolgáltatást. Majd beállítunk a javascript fájloknak egy órás (3600 másodperc) elévülési időt. (Az A kóddal jelezzük, hogy az aktuális időpillanathoz képest adtuk meg az időt.) Ez gyakorlatilag azt jelenti, minden látogatáskor egy alkalommal letöltődik a külső szkriptünk (mivel egy látogatás ritkán tart ennél tovább). Egy statikus fájl elküldése a szerver számára nem olyan nagy munka, úgyhogy ezt egy józan kompromisszumnak tartom.

A css fájlokkal nem voltam ilyen szigorú. Én úgy érzem egy rossz css nincs olyan végzetes hatással egy oldalra, mint egy rossz javascript. It 1 napot (86400 mp) adok meg. Természetesen ez oldalfüggő, a fejlesztő belátására van bízva.

Nagyon különleges helyzetekben teljesen tilthatjuk is a cache működését (0 másodperc). De erre inkább nem is írok példát. :)

Verzió az URI-ba ágyazva

A legtisztább megoldás minden frissüléskor új nevet adni a scriptnek, ilyenkor a böngésző biztosan a megfelelő fájlt használja. (Megtartjuk a cache funkciót úgy, hogy korábbi cache-elte változatok nem zavarnak be.) Ezt megtehetjük egy növekvő számmal a fájlnév után, vagy akár dátummal is. Példa mindkettőre:

<script type="text/javascript" src="menu_3.js"></script> <script type="text/javascript" src="menu_20060523.js"></script>

A megoldás különlegessége, hogy megtarthatjuk a különböző verziókat egymás mellett, így egy cache-elt oldal (pl. google tárlolt változat) is működőképes marad. Ennek ellenére én a következő megoldásra eskölszöm:

Verziókövetés hamis URI (mod_rewrite) és PHP segítségével

A fantasztikus mod_rewrite, minden URI-k Szent Grálja ebben az esetben is jó szolgálatot tesz. A megoldáshoz PHP is szükséges.

Az elv ugyanaz, mint az előző (dátumos) példában, itt viszont nem kell átneveznünk a fájlt. Az aktuális dátumot (és időt) a PHP fűzi a fájlnév végére, az így keletkezett URI-k (URL-ek) feloldását (mivel nemlézető fájlokra mutatnak) pedig a mod_rewrite végzi.

Először a beillesztés (a példában éppen javascript, de külső css beillesztésére ugyanígy használható):

<script type="text/javascript" src="<? add_timestamp ('menu.js'); ?>"></script>

Az ide vágó php függvény:

function add_timestamp ($uri) { if (substr ($uri, 0, 1) == "/") { // abszolút URI $fname = $_SERVER["DOCUMENT_ROOT"] . $uri; } else { // relatív URI $fname = $uri; } @$ftime = filectime ($fname); echo ($uri . '.' . $ftime); }

A függvény maga igen egyszerű. Levizsgáljuk, hogy relaítív vagy abszolút URI-t kaptunk-e. Relatív hivatkozás esetén feltételezzük, hogy php-ben (cd függvény) nem változtattuk meg az aktuális könyvtárat (vagy visszatértünk oda). Aboszolút hivatkozás esetén a web alapkönyvtárát (az Apache DOCUMENT_ROOT-ját) a hivatkozás elé helyezzük.

Ha megkaptuk a fájl helyét az aktuális fájlrendszerben lekérdezzük az utolsó módosítás idejét. Nem létező file esetén a PHP egy hibát adna, ezt elnyomjuk a sor elején látható @ (at) karakterrel. Egy unix timestamp formátumú azámot kapunk, ez az 1970 január elseje óta eltelt másodpercek száma, egy 9 vagy 10 jegyű szám (2001.09.09-én léptük át a milliárd másodpercet, úgyhogy az ezutániak lesznek a 10 számjegyűek).

Ennek eredménye egy URI (URL), ami automatikusan változik a fájl minden módosulásával:

<script type="text/javascript" src="menu.js.1148404454"></script>

A PHP megtette a dolgát, most jöjjön a mod_rewrite. A mod_rewrite lehetőséget ad, hogy bekapcsolódjunk a folymatba, amikor az Apache az URI feldolgozását végzi, és néhány alap műveletet elvégezzünk vele. A következő kódrészlet szintén egy .htaccess fájlba, vagy egy directory blokkba helyezve működik:

RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} ^(.*)\.[0-9]{9,10}$ RewriteCond %1 -f RewriteRule ^(.*)\.[0-9]+$ $1 [L]

Az első sor aktiválja a modult. A RewriteCond egy előfeltételt jelent, a RewriteRule direktívának. Alapértelmezetten ÉS (AND) kapcsolat működik közöttük. Az egész modul lekét RegEx (Regular Expression - szabányos kifejezések) feldolgozása képezi. Ezek azok a furcsa operátorok a negyedik és hatodik sorban. Az egész szabány pedig kötülbelül így olvasható:

HA a kért URL nem létező fájl ÉS a kért URL nem létező könyvtár ÉS a kért URL vége: egy pont, majd min. 9 max. 10 számjegy ÉS a pont és a számjegyek levágása után létező fájlt kapunk AKKOR vágd le a pontot és a számjegyeket, majd azonnal dolgozd fel a címet (ne futtass további rewrite szabályokat).

Az utolsó (RewriteRule) sor önmagában is elég lenne a működéshez. A feltételek (RewriteCond) azért vannak, hogy csak megfelelő esetben fusson le a rewrite, és ha pl. létezik olyan fájl, ami ilyen furcsán néz ki, és sok számjegyre végződik, akkor azok végéről ne vágjuk le a számokat.

Egyszerüsítsünk: versziózás a query stringben

Miután a weblabor fórumában meghánytuk-vetettük a dolgot, felmerült egy egyszerübb megolás is. A böngészők jól reagáltak a tesztekre, úgyhogy már használom is egy éles helyzetben. Köszönet az ötletért!

Tulajdonképpen az előző módszerről van szó csak a RegEx nélkül. A lényeg, hogy a menu.js.1148404454 típusú virtuáls filenevek helyett menu.js?1148404454 tipusú URL-eket is generálhatunk. Ez minden szempontból az előzőhöz hasonlóan viselkedik, előnye, hogy a webszerver rewrite nélkül is megtalálja és kiszolgálja a fájlt, amire hivatkozunk. A módosításkor változó query string (?...) pedig biztosítja, hogy a böngészők ilyenkor ne használják az elavult cache-es változatot. Az erre módosított PHP függvény:

function add_timestamp ($uri) { if (substr ($uri, 0, 1) == "/") { // abszolút URI $fname = $_SERVER["DOCUMENT_ROOT"] . $uri; } else { // relatív URI $fname = $uri; } @$ftime = filectime ($fname); echo ($uri . '?' . $ftime); }

Módosított konklúzió

A helyzet válogatja mikor melyik a legcélravezetőbb módszer, és néha-néha lehet a rewrite-nál egyszerübb megoldásokat is találni.

Külső linkek