Come forzare l’aggiornamento dei file JS e CSS

Venerdì 26 Marzo 2010 - 08:02

di Andrea Ganduglia

PHP e Open SourceWeb Design

Quando un file CSS o JS viene aggiornato è necessario che questo cambiamento sia percepito anche dagli utenti, soprattutto da quelli che hanno nella cache del browser la versione precendente dei file, per contro possono verificarsi importanti disfunzionalità a livello logico o di rendering.

Una delle tecniche che uso è quella di apporre un id di versione al file nel HTML della pagina.

<script type="text/javascript" value="/js/common.js?v3.4"></script>

Questa soluzione ha però il limite di richiedere un aggiornamento del sorgente HTML, cosa che in alcune realtà è scomodo o non possibile. Da qualche tempo quindi applico una tecnica più sofisticata, scrivendo una volta per tutte un HTML/PHP apposito e generando dinamicamente un nome file univoco contenente il timestamp dell’ultima modifica al file, infine uso .htaccess per accedere al file fisico sul server.

Ecco il codice HTML/PHP necessario:

<link rel="stylesheet" type="text/css" href="/css/main.<?php echo filemtime('/server/path/css/main.css'); ?>.css" />
<script language="javascript" src="/js/common.<?php echo filemtime('/server/path/js/common.js'); ?>.js"></script>

che produce nomi di file tipo:

/css/main.1269466887.css
/js/common.1269467087.js

In questo modo, ogni volta che il sorgente JS o CSS viene aggiornato verrà generato un nome file univoco che il browser dell’utente sarà “costretto” a scaricare.

Ovviamente, il file main.1269466887.css non esiste sul server ed è necessario reindirizzare questo percorso verso il file originale main.css con alcune semplici regole di rewrite da inserire nel nostro file .htaccess

RewriteEngine On
RewriteRule ^css/(.*).[0-9]+.css /css/$1.css [L]
RewriteRule ^js/(.*).[0-9]+.js /js/$1.js [L]

È tutto e speriamo questo eviti la tipica telefonata (isterica) 4 minuti dopo ogni deploy ^__^.

Tags:

Categoria: PHP e Open Source, Web Design | Permalink

Commenti

1

Scusa ma a questo punto è molto più comodo inserire il timestamp della modifica come parametro GET al nome del file, senza andare a usare file .htacces

# - postato da Pugia - 26 Marzo 2010 - 09:10

2

Può essere una buona soluzione, ma si potrebbero risparmiare alcuni caratteri di codice (ho corretto anche l’inclusione del file JS in modo che il codice HTML sia valido):
.css” />
.js”>

# - postato da Mattia - 26 Marzo 2010 - 09:26

3

Mi scuso per il commento precedente ma non mi fa inserire il codice (si devono sostituire le parentesi quadre con minore e maggiore):
[link rel=”stylesheet” type=”text/css” href=”/css/main.[?=filemtime(’/server/path/css/main.css’);?].css” /]
[script type=”text/javascript” src=”/js/common.[?=filemtime(’/server/path/js/common.js’);?].js”][/script]

# - postato da Mattia - 26 Marzo 2010 - 09:28

4

Non si farebbe prima a richiamare:

value=”/js/common.js?timestampAttuale” ?
oppure
value=”/js/common.js?preventCacheRandomNumber” ?

Questo sempre ed a prescindere?

# - postato da emmebì - 26 Marzo 2010 - 09:37

5

@emmebì
con “timestampAttuale” intendi che ad ogni richiesta viene generato un timestamp?
in questo caso il client deve sempre scaricare un nuovo js/css a ogni richiesta anche se il file non e’ cambiato, perdendo i benefici del caching.

# - postato da Erich - 26 Marzo 2010 - 09:40

6

@emmebì: con value=”/js/common.js? preventCacheRandomNumber”, se non sbaglio, il file non verrebbe mai letto dalla cache, mentre con il timestamp, se il file non viene modificato magari va in cache.

Correggetemi se non è corretto.

# - postato da Marco Rossi - 26 Marzo 2010 - 09:48

7

In effetti quoto @emmebì ..
Alla fine conviene passare il timestamp come parametro GET… “style.css.?timestamp=xxx”
O sbaglio?

# - postato da Max - 26 Marzo 2010 - 09:49

8

Pro e contro (la cache porta effetti positivi e negativi)..

# - postato da emmebì - 26 Marzo 2010 - 09:54

9

Un’idea carina potrebbe essere quella di richiamare:

/js/loader.js?preventCacheRandomNumber

a prescindere (ed evitiamo problemi di copia cache non aggiornata, ovvero verrà sempre richiesta una copia di loader.js da remoto).

Il loader sarà però un file molto piccolo, di include - che aggiorneremo ad ogni aggiornamento di qualche scripr nel seguente modo:

document.write(”");
document.write(”");

In qs. modo, class1_v1.0.js è già in cache ed il browser la prende da lì; class2v_4.11.js è nuova ed il browser la prende da remoto.

# - postato da emmebì - 26 Marzo 2010 - 10:10

10

Nei document.write saltati c’era l’inclusione dei file class1_v1.0.js e lass2v_4.11.js.

NOTA: perchè questo blog non usa htmlentities al posto di eliminare i caratteri HTML??

# - postato da emmebì - 26 Marzo 2010 - 10:12

11

@Puglia
Per forzare il caricamento si può usare la query string, certamente, ma così inibisci il caching di moltissimi proxy che ignorano comunque gli header come cache public in questi casi.

Meglio l’url rewrite secondo me.

# - postato da Riot - 26 Marzo 2010 - 11:38

12

Se l’utente prende l’html dalla cache, da un proxy, o dal copia cache, con il metodo che dici vedra in azione un nuovo js su una vecchia pagina, quindi con delle inevitabili incompatibilità.

Inoltre per ogni pagina richiesta il server va a scandire tutto l’elenco delle directory e infine va a leggere l’attributo ultima modifica.

Io faccio così prima versione
main1.js, seconda versione main2.js ecc.
e lascio tutti i file javascript sul server.

Molto più semplice e con zero problemi.

# - postato da Mik - 26 Marzo 2010 - 13:52

13

@Mik
E’ vero, ma per come lavoro in genere l’html risulta essere sempre di pochi KB, soprattutto se compresso con gzip e quindi il caching lato client influisce davvero poco (quello lato server invece è molto più importante ovviamente).

Un’alternativa per far risparmiare questi pochi KB potrebbe quello di leggere via server l’header if modified e restituire un bel 304 se nessuno degli elementi della pagina è stato modificato da quel momento, cosa piuttosto rara per siti dinamici.

# - postato da Riot - 26 Marzo 2010 - 14:16

14

@mik
Dimenticavo, le informazioni dei file sono cachate (esistenza, data modifica…) in modo trasparente su php quindi non impatta molto.

Inoltre l’utilità di avere vecchie pagine cachate, magari con link a pagine non più presenti o fuzionalità cambiate o modificate non farebbe un bell’effetto.

L’unico caso in cui poco si può fare sono le copie cache dei motori di ricerca.

# - postato da Riot - 26 Marzo 2010 - 14:24

15

@Riot

Quindi vorresti addirittura impedire il cache dell’html?

Però non puoi dire al client “non mettermi in cache la pagina” e poi pretendere che ti faccia una richiesta if modified since. Sono due cose opposte.
I file statici, come i file js, sono già gestiti dai webserver con “last modified” e “if modified since”, ma a volte i browser, a seconda delle impostazioni se ne fregano, e se lo fanno, non distinguono fra html o js.

Per risolvere un tuo piccolissimo problema, che si può fare come ho detto (io faccio anche di piu, ho una pagina di compilazione che mi comprime ecc.) lo scarichi sugli utenti e sul server.

Una pagina è costituita non solo dall’html ma anche da tutti gli elementi che incorpora, se il client ha l’html non aggiornato gli do la possibilità di recuperare quella pagina così come era, e non fargli vedere delle mostruosità.

Nessun extra lavoro per il server, tutto trasparente, compatibile, usabile, veloce …
Troppo semplice?

# - postato da Mik - 26 Marzo 2010 - 20:50

16

Buongiorno a tutti..
se devo essere sincero leggere i vari commenti mi ha un po’ confuso le idee..
qual è la soluzione migliore??

# - postato da Luca - 29 Marzo 2010 - 12:31

17

Mi unisco alla richiesta di Luca

# - postato da Fabio - 29 Marzo 2010 - 15:12

18

Questa è un’ottima tecnica.
Ma il motivo per cui il timestamp della modifica è inserito fra il nome del file e l’estensione e NON nella query string è dato dal fatto che alcune versioni di uno dei più conosciuti server proxy (Squid) non supporta il caching di URL con querystring.

Ciò significa che se mettessimo il timestamp in querystring, le persone che vedranno le pagine tramite un server proxy (es: nelle aziende, scuole, ecc) non memorizzeranno nella cache (del proxy) il file.

# - postato da Andrea Zilio - 29 Marzo 2010 - 18:18

19

Ciao ragazzi,

1. il metodo di rewrite inizia quando il file ‘/css/main.1269466887.css’ (ad esempio) non viene trovato nella cache e poi nemmeno nel server? (altrimenti non capisco a che serva…)

2. se ho n files per i css o per i js come devo procedere (fare il timestamp n volte?!?)?

Grazie!

# - postato da neofita - 05 Luglio 2010 - 15:08

20

un’altra domanda mi sorge: il file in questione viene salvato nella cache con quale nome? main.1269466887.css mi auguro…

Leggere anche le domande precedenti!

# - postato da neofita - 05 Luglio 2010 - 15:20

21

mi auguro proprio di non dover fare n volte il timestamp! :|
il file main.1269466887.css nn riesco mica a trovarlo in cache..

# - postato da rikygio - 02 Febbraio 2011 - 11:14

Inserisci il tuo commento:





(puoi usare i seguenti tag HTML per formattare il testo -
a href, b, i, br/, p, strong, em, ul, ol, li, blockquote, pre):

 

Anteprima del commento