Comment forcer le navigateur à recharger les fichiers CSS / JS mis en cache?

993

J'ai remarqué que certains navigateurs (en particulier Firefox et Opera) sont très zélés dans l'utilisation de copies en cache des fichiers .css et .js , même entre les sessions du navigateur. Cela entraîne un problème lorsque vous mettez à jour l'un de ces fichiers, mais le navigateur de l'utilisateur continue d'utiliser la copie mise en cache.

La question est: quelle est la manière la plus élégante de forcer le navigateur de l'utilisateur à recharger le fichier lorsqu'il a changé?

Idéalement, la solution ne forcerait pas le navigateur à recharger le fichier à chaque visite de la page. Je posterai ma propre solution comme réponse, mais je suis curieux de savoir si quelqu'un a une meilleure solution et je laisserai vos votes décider.

Mise à jour :

Après avoir autorisé la discussion ici pendant un certain temps, j'ai trouvé la suggestion de John Millikin et da5id utile. Il s'avère qu'il y a un terme pour cela: la version automatique .

J'ai posté une nouvelle réponse ci-dessous qui est une combinaison de ma solution d'origine et de la suggestion de John.

Une autre idée suggérée par SCdF serait d'ajouter une fausse chaîne de requête au fichier. (Du code Python pour utiliser automatiquement l'horodatage comme chaîne de requête bidon a été soumis par pi .). Cependant, il y a une discussion pour savoir si le navigateur mettra en cache ou non un fichier avec une chaîne de requête. (N'oubliez pas que nous souhaitons que le navigateur mette le fichier en cache et l'utilise lors de vos prochaines visites. Nous souhaitons uniquement qu'il récupère le fichier lorsqu'il a changé.)

Puisqu'il n'est pas clair ce qui se passe avec une fausse chaîne de requête, je n'accepte pas cette réponse.

Pmpr
la source
J'ai dans mon .htaccess, et jamais de problèmes avec les fichiers mis en cache: ExpiresActive On ExpiresDefault "modification".
Frank Conijn
2
Je suis tout à fait d'accord pour dire que l'ajout d'informations de version à l'URL du fichier est de loin la meilleure solution. Cela fonctionne, tout le temps, pour tout le monde. Mais, si vous ne l'utilisez pas, et que vous avez juste besoin de recharger ce fichier CSS ou JS de temps en temps dans votre propre navigateur ... ouvrez-le simplement dans son propre onglet et appuyez sur SHIFT-reload (ou CTRL-F5)! Vous pouvez faire la même chose efficacement en utilisant JS en chargeant un fichier dans une iframe (cachée), en attendant qu'il se charge, puis en appelant iframe.contentWindow.location.reload(true). Voir la méthode (4) de stackoverflow.com/a/22429796/999120 - il s'agit d'images, mais la même chose s'applique.
Doin
2
J'apprécie vraiment la façon dont cette question a été posée et a été mise à jour depuis. Il décrivait complètement ce à quoi je devais m'attendre dans les réponses. Je vais suivre cette approche dans mes questions à partir de maintenant. À votre santé!
rd22

Réponses:

455

Mise à jour: réécriture pour incorporer les suggestions de John Millikin et da5id . Cette solution est écrite en PHP, mais devrait être facilement adaptée à d'autres langages.

Mise à jour 2: Incorporation des commentaires de Nick Johnson selon lesquels l' .htaccessexpression régulière originale peut causer des problèmes avec des fichiers comme json-1.3.js. La solution consiste à réécrire uniquement s'il y a exactement 10 chiffres à la fin. (Parce que 10 chiffres couvrent tous les horodatages du 9/9/2001 au 11/20/2286.)

Tout d'abord, nous utilisons la règle de réécriture suivante dans .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Maintenant, nous écrivons la fonction PHP suivante:

/**
 *  Given a file, i.e. /css/base.css, replaces it with a string containing the
 *  file's mtime, i.e. /css/base.1221534296.css.
 *  
 *  @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *                starting with slash).
 */
function auto_version($file)
{
  if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
    return $file;

  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}

Maintenant, où que vous incluiez votre CSS, changez-le à partir de ceci:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

Pour ça:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

De cette façon, vous n'aurez plus jamais à modifier la balise de lien et l'utilisateur verra toujours la dernière CSS. Le navigateur pourra mettre en cache le fichier CSS, mais lorsque vous apporterez des modifications à votre CSS, le navigateur le verra comme une nouvelle URL, donc il n'utilisera pas la copie mise en cache.

Cela peut également fonctionner avec des images, des favicons et JavaScript. Fondamentalement, tout ce qui n'est pas généré dynamiquement.

Kip
la source
16
Mon propre serveur de contenu statique fait exactement la même chose, sauf que j'utilise un paramètre pour le contrôle de version (base.css? V = 1221534296) plutôt qu'un changement de nom de fichier (base.1221534296.css). Je pense que votre chemin peut être un peu plus efficace. Très cool.
Jens Roland
4
@Kip: Solution très fluide. La réécriture d'URL a évidemment bien plus à offrir que de simples URL jolies.
James P.
37
Je vois un problème avec cela, qu'il accède au système de fichiers plusieurs fois - exactement - nombre de liens * nombre de requêtes / sec ... qui peut ou non être un problème pour vous.
Tomáš Fejfar
3
@AlixAxel: Non, les navigateurs le récupéreront lorsque le paramètre changera, mais certains proxys publics ne mettront pas en cache les fichiers avec les paramètres d'URL, donc la meilleure pratique est d'inclure la version dans le chemin. Et le surcoût mod_rewrite est minuscule par rapport à tous les autres goulots d'étranglement de performances dans WPO
Jens Roland
8
Le premier file_existscontrôle est-il vraiment nécessaire? filemtimeretournera false en cas d'échec, alors pourquoi ne pas simplement attribuer la valeur filemtime à une variable et vérifier si elle est fausse avant de renommer le fichier? Cela réduirait une opération de fichier inutile qui s'additionnerait vraiment.
Gavin
184

Technique simple côté client

En général, la mise en cache est bonne. Il existe donc quelques techniques, selon que vous résolvez le problème par vous-même lorsque vous développez un site Web ou que vous essayez de contrôler le cache dans un environnement de production.

Les visiteurs généraux de votre site Web n'auront pas la même expérience que vous avez lorsque vous développez le site. Étant donné que le visiteur moyen vient sur le site moins fréquemment (peut-être seulement quelques fois par mois, sauf si vous êtes un réseau Google ou hi5), il est donc moins susceptible d'avoir vos fichiers en cache, et cela peut suffire. Si vous souhaitez forcer une nouvelle version dans le navigateur, vous pouvez toujours ajouter une chaîne de requête à la demande et augmenter le numéro de version lorsque vous apportez des modifications majeures:

<script src="/myJavascript.js?version=4"></script>

Cela garantira que tout le monde obtient le nouveau fichier. Cela fonctionne car le navigateur regarde l'URL du fichier pour déterminer s'il a une copie dans le cache. Si votre serveur n'est pas configuré pour faire quoi que ce soit avec la chaîne de requête, il sera ignoré, mais le nom ressemblera à un nouveau fichier pour le navigateur.

En revanche, si vous développez un site Web, vous ne voulez pas changer le numéro de version à chaque fois que vous enregistrez une modification dans votre version de développement. Ce serait fastidieux.

Ainsi, pendant que vous développez votre site, une bonne astuce serait de générer automatiquement un paramètre de chaîne de requête:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

L'ajout d'une chaîne de requête à la demande est un bon moyen de versionner une ressource, mais pour un site Web simple, cela peut être inutile. Et rappelez-vous, la mise en cache est une bonne chose.

Il convient également de noter que le navigateur n'est pas nécessairement avare de conserver les fichiers en cache. Les navigateurs ont des politiques pour ce genre de choses, et ils respectent généralement les règles définies dans la spécification HTTP. Lorsqu'un navigateur fait une demande à un serveur, une partie de la réponse est un en-tête EXPIRES. Une date qui indique au navigateur combien de temps il doit être conservé dans le cache. La prochaine fois que le navigateur rencontre une demande pour le même fichier, il voit qu'il a une copie en cache et regarde la date EXPIRE pour décider s'il doit être utilisé.

Croyez-le ou non, c'est en fait votre serveur qui rend le cache du navigateur si persistant. Vous pouvez ajuster les paramètres de votre serveur et modifier les en-têtes EXPIRES, mais la petite technique que j'ai écrite ci-dessus est probablement un moyen beaucoup plus simple de vous y prendre. Étant donné que la mise en cache est bonne, vous souhaitez généralement définir cette date très loin dans le futur (un "en-tête expirant dans un futur éloigné") et utiliser la technique décrite ci-dessus pour forcer un changement.

Si vous êtes intéressé par plus d'informations sur HTTP ou comment ces demandes sont faites, un bon livre est "High Performance Web Sites" de Steve Souders. C'est une très bonne introduction au sujet.

keparo
la source
3
L'astuce rapide pour générer une chaîne de requête avec Javascript fonctionne très bien pendant le développement actif. J'ai fait la même chose avec PHP.
Alan Turing,
2
C'est le moyen le plus simple d'obtenir le résultat souhaité de l'affiche originale. La méthode mod_rewrite fonctionne bien si vous souhaitez forcer un rechargement du fichier .css ou .js CHAQUE fois que vous chargez la page. Cette méthode permet toujours la mise en cache jusqu'à ce que vous modifiiez réellement le fichier et que vous souhaitiez vraiment qu'il force le rechargement.
scott80109
@keparo, j'ai un nombre suffisant de jquery dans toutes les pages si je vais changer cela manuellement, cela prendra un mois. Si vous pouvez m'aider à résoudre en tout sans coder pour chaque page.
cracker
1
Cela ne semble pas fonctionner pour mon CSS lorsque j'utilise:<link href='myCss.css?dev=14141'...>
Noumenon
3
Ce n'est pas une solution viable. Un bon nombre de navigateurs refuseront simplement de mettre en cache quoi que ce soit avec une chaîne de requête. C'est la raison pour laquelle Google, GTMetrix et des outils similaires lèveront un indicateur si vous avez des chaînes de requête sur les références au contenu statique. Bien que ce soit certainement une solution décente pour le développement, ce n'est absolument pas une solution pour la production. En outre, le navigateur contrôle la mise en cache, pas le serveur. Le serveur SUGGÈRE simplement quand il doit être rafraîchi; un navigateur n'a PAS à écouter le serveur (et souvent non). Les appareils mobiles en sont un excellent exemple.
Nate I
113

Mod_pagespeed de Google plugin pour apache effectuera la version automatique pour vous. C'est vraiment lisse.

Il analyse le HTML à sa sortie du serveur Web (fonctionne avec PHP, rails, python, HTML statique - n'importe quoi) et réécrit les liens vers CSS, JS, fichiers image afin qu'ils incluent un code d'identification. Il sert les fichiers aux URL modifiées avec un contrôle de cache très long sur eux. Lorsque les fichiers changent, il modifie automatiquement les URL de sorte que le navigateur doit les récupérer de nouveau. Cela fonctionne simplement, sans aucune modification de votre code. Cela réduira également votre code à la sortie.

Leopd
la source
1
C'est super, mais toujours en version bêta. Peut-il être utilisé pour le service d'entreprise?
Sanghyun Lee
26
C'est MAUVAIS (tripoter automatiquement la source) quand il s'agit clairement d'un problème de navigateur. Donnez-nous (développeurs) un véritable rafraîchissement du cerveau: <ctrl> + F5
T4NK3R
25
mod_pagespeed est fonctionnellement équivalent à une étape de compilation / compilation entièrement automatique pour votre html / css / js. Je pense que vous auriez du mal à trouver des développeurs sérieux qui pensent que les systèmes de construction sont intrinsèquement mauvais, ou qu'il y a quelque chose de mal à ce qu'il soit complètement automatique. L'analogie d'une construction propre est d'effacer le cache de mod_pagespeed : code.google.com/p/modpagespeed/wiki/… ?
Leopd
3
@ T4NK3R mod_pagespeed n'a rien à faire avec votre source pour gérer le cache, il a simplement été mentionné qu'il peut aider avec des choses comme la minification. Quant à savoir si c'est "FAUX", c'est complètement subjectif. Cela peut être mauvais pour vous, mais cela ne signifie pas que c'est mauvais en soi .
Madbreaks
2
Il fonctionne également avec nginx, mais vous devez le créer à partir de la source: developers.google.com/speed/pagespeed/module/…
Rohit
93

Au lieu de changer la version manuellement, je vous recommande d'utiliser un hachage MD5 du fichier CSS réel.

Donc, votre URL serait quelque chose comme

http://mysite.com/css/[md5_hash_here]/style.css

Vous pouvez toujours utiliser la règle de réécriture pour supprimer le hachage, mais l'avantage est que vous pouvez maintenant définir votre stratégie de cache sur "cache pour toujours", car si l'URL est la même, cela signifie que le fichier est inchangé.

Vous pouvez ensuite écrire un script shell simple qui calculerait le hachage du fichier et mettrait à jour votre balise (vous voudriez probablement le déplacer vers un fichier séparé pour l'inclusion).

Exécutez simplement ce script chaque fois que CSS change et vous êtes bon. Le navigateur rechargera UNIQUEMENT vos fichiers lorsqu'ils seront modifiés. Si vous effectuez une modification puis l'annulez, il n'y a aucune difficulté à déterminer la version à laquelle vous devez revenir pour que vos visiteurs ne téléchargent pas à nouveau.

levik
la source
1
malheureusement je ne sais pas comment l'appliquer. Des conseils s'il vous plaît ... plus de détails ...
Michael Phelps
Une implémentation en coquille, rubis, etc. serait formidable
Peter
3
Très belle solution .. mais je pense que cela consomme beaucoup de ressources pour calculer le hachage du fichier dans chaque demande de fichier (css, js, images, html..etc) pour chaque visite de page.
DeepBlue
Il s'agit d'une solution standard pour ceux qui utilisent le regroupement js ou css avec gulp, grunt ou webpack, l'implémentation diffère pour chaque solution, mais le hachage de vos fichiers lors d'une étape de construction est courant et suggéré pour les applications groupées modernes
Brandon Søren Culley
@DeepBlue - la réponse dit "exécutez ce script chaque fois que CSS change" . Ce n'est PAS à chaque visite de page. OTOH La réponse laisse de côté les détails majeurs - comment le hachage modifié devient-il une partie de l'URL? Je ne sais pas ...
ToolmakerSteve
71

Je ne sais pas pourquoi vous prenez tant de peine à mettre en œuvre cette solution.

Tout ce que vous devez faire si obtenir l'horodatage modifié du fichier et l'ajouter en tant que chaîne de requête au fichier

En PHP, je le ferais comme:

<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

filemtime est une fonction PHP qui renvoie l'horodatage modifié du fichier.

Phantom007
la source
Vous pouvez simplement utiliser mycss.css?1234567890.
Gavin
3
très élégant, bien que je l'ai légèrement modifié <link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>, juste au cas où certains des arguments sur ce fil concernant la mise en cache des URL avec des variables GET (dans le format suggéré) sont corrects
luke_mclachlan
suite à mon dernier commentaire, j'ai vu que wordpress utilise ?ver=alors qui sait!
luke_mclachlan
Excellente solution. De plus, pour moi, j'ai trouvé que filemtime ne fonctionnait pas pour un nom de domaine complet (FQDN), j'ai donc utilisé le FQDN pour la partie href et $ _SERVER ["DOCUMENT_ROOT"] pour la partie filemtime. EX: <link rel = "stylesheet" href = "http: //theurl/mycss.css? V = <? Php echo filemtime ($ _ SERVER [" DOCUMENT_ROOT "]. '/Mycss.css')?>" />
rrtx2000
Grand merci. Simple et bon. Le voici en Python: progpath = os.path.dirname (sys.argv [0]) def versionize (fichier): timestamp = os.path.getmtime ('% s /../ web /% s'% (progpath , fichier)) retourner '% s? v =% s'% (fichier, horodatage) imprimer <link href = "% s" rel = "feuille de style" '' type = "text / css" /> '\% versionize ( 'css / main.css')
dlink
52

Vous pouvez simplement mettre ?foo=1234à la fin de votre import css / js, en changeant 1234 pour être ce que vous voulez. Jetez un œil à la source SO html pour un exemple.

L'idée étant que le? les paramètres sont de toute façon ignorés / ignorés sur la demande et vous pouvez modifier ce nombre lorsque vous déployez une nouvelle version.


Remarque: Il existe un argument concernant la façon exacte dont cela affecte la mise en cache. Je pense que l'essentiel est que les requêtes GET, avec ou sans paramètres, doivent être cachable, donc la solution ci-dessus devrait fonctionner.

Cependant, c'est au serveur Web de décider s'il veut adhérer à cette partie de la spécification et au navigateur que l'utilisateur utilise, car il peut tout de même aller de l'avant et demander une nouvelle version de toute façon.

SCdF
la source
Absurdité. La chaîne de requête (également appelée paramètres GET) fait partie de l'URL. Ils peuvent et seront mis en cache. C'est une bonne solution.
troelskn
9
@troelskn: La spécification HTTP 1.1 dit le contraire (en ce qui concerne les requêtes GET et HEAD avec des paramètres de requête): les caches NE DOIVENT PAS traiter les réponses à ces URI comme fraîches, sauf si le serveur fournit un délai d'expiration explicite. Voir w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
Michael Johnson
4
J'ai essayé le type de version de chaîne de requête avec tous les principaux navigateurs et ils mettent en cache le fichier, les spécifications ou non. Cependant, je pense qu'il est préférable d'utiliser le format style.TIMESTAMP.css sans abuser des chaînes de requête de toute façon, car il est toujours possible que la mise en cache du logiciel proxy NE cache PAS le fichier.
Tomas Andrle
34
Il convient de noter, pour une raison quelconque, que Stackoverflow lui-même utilise la méthode de la chaîne de requête.
jason
2
Nous avons vérifié que l'utilisation du paramètre? = Ne fera pas que les navigateurs récupèrent le fichier mis en cache lorsque le paramètre change. Le seul moyen est de changer le nom du fichier lui-même par programme à la fin du serveur comme l'a répondu Kip
arunskrish
41

J'ai entendu cela appelé "versioning automatique". La méthode la plus courante consiste à inclure le mtime du fichier statique quelque part dans l'URL et à le supprimer à l'aide de gestionnaires de réécriture ou de confs d'URL:

Voir également:

John Millikin
la source
3
Merci, je suppose que c'était un autre cas où mon idée a été discutée, je ne savais tout simplement pas comment elle s'appelait donc je ne l'ai jamais trouvée dans les recherches Google.
Kip
27

La trentaine de réponses existantes sont d'excellents conseils pour un site Web vers 2008. Cependant, en ce qui concerne une application de page unique (SPA) moderne, il est peut-être temps de repenser certaines hypothèses fondamentales… en particulier l'idée qu'il est souhaitable que le serveur Web ne serve que la version unique et la plus récente d'un fichier.

Imaginez que vous êtes un utilisateur dont la version M d'un SPA est chargée dans votre navigateur:

  1. Votre pipeline de CD déploie la nouvelle version N de l'application sur le serveur
  2. Vous naviguez dans le SPA, qui envoie un XHR au serveur pour obtenir /some.template
    • (Votre navigateur n'a pas actualisé la page, vous utilisez donc toujours la version M )
  3. Le serveur répond avec le contenu de /some.template- voulez-vous qu'il renvoie la version M ou N du modèle?

Si le format a /some.templatechangé entre les versions M et N (ou si le fichier a été renommé ou autre), vous ne voulez probablement pas que la version N du modèle soit envoyée au navigateur qui exécute l'ancienne version M de l'analyseur . †

Les applications Web rencontrent ce problème lorsque deux conditions sont remplies:

  • Les ressources sont demandées de manière asynchrone quelque temps après le chargement initial de la page
  • La logique de l'application suppose des éléments (qui pourraient changer dans les futures versions) sur le contenu des ressources

Une fois que votre application doit servir plusieurs versions en parallèle, résoudre la mise en cache et le «rechargement» devient trivial:

  1. Installez tous les fichiers du site dans dirs versionnés: /v<release_tag_1>/…files…,/v<release_tag_2>/…files…
  2. Définissez des en-têtes HTTP pour permettre aux navigateurs de mettre en cache les fichiers pour toujours
    • (Ou mieux encore, mettez tout dans un CDN)
  3. Mettre à jour toutes <script>et <link>balises, etc. pour pointer vers ce fichier dans l' un des dirs versionnés

Cette dernière étape semble délicate, car elle pourrait nécessiter d'appeler un générateur d'URL pour chaque URL de votre code côté serveur ou côté client. Ou vous pouvez simplement utiliser intelligemment la <base>balise et modifier la version actuelle en un seul endroit.

† Une façon de contourner ce problème consiste à forcer le navigateur à tout recharger lorsqu'une nouvelle version est publiée. Mais pour permettre à toutes les opérations en cours de se terminer, il peut toujours être plus facile de prendre en charge au moins deux versions en parallèle: v-current et v-previous.

Michael Kropat
la source
Michael - votre commentaire est très pertinent. Je suis ici en essayant précisément de trouver une solution pour mon spa. J'ai reçu quelques conseils, mais j'ai dû trouver une solution moi-même. En fin de compte, j'étais vraiment content de ce que j'ai trouvé, j'ai donc écrit un blog et une réponse à cette question (y compris le code). Merci pour les pointeurs
statler
Grand commentaire. Je ne peux pas comprendre pendant que les gens continuent de parler de la suppression du cache et de la mise en cache HTTP comme la vraie solution aux problèmes de mise en cache des sites Web sans évoquer les nouveaux problèmes des SPA, comme s'il s'agissait d'un cas marginal.
David Casillas
1
Excellente réponse et stratégie absolument idéale! Et des points bonus pour avoir mentionné le basetag! Quant à la prise en charge de l'ancien code: ce n'est pas toujours une possibilité, ce n'est pas toujours une bonne idée. Les nouvelles versions de code peuvent prendre en charge les modifications de rupture apportées à d'autres éléments d'une application ou impliquer des correctifs d'urgence, des correctifs de vulnérabilité, etc. Je n'ai pas encore implémenté cette stratégie moi-même, mais j'ai toujours pensé que l'architecture globale devrait permettre aux déploiements de marquer une ancienne version en tant que obsoleteet de forcer un rechargement la prochaine fois qu'un appel asynchrone est effectué (ou tout simplement de-authentifier de force toutes les sessions via WebSockets ).
Jonny Asmar
Agréable de voir une réponse bien pensée en ce qui concerne les applications d'une seule page.
Nate I
C'est "déploiement bleu-vert" si vous souhaitez rechercher plus d'informations.
Fil
15

N'utilisez pas foo.css? Version = 1! Les navigateurs ne sont pas censés mettre en cache les URL avec des variables GET. Selon http://www.thinkvitamin.com/features/webapps/serving-javascript-fast , bien qu'IE et Firefox l'ignorent, Opera et Safari ne le font pas! Utilisez plutôt foo.v1234.css et utilisez des règles de réécriture pour supprimer le numéro de version.

airrob
la source
1
Tout d'abord, les navigateurs ne mettent pas en cache, c'est une fonction de HTTP. Pourquoi http se soucierait-il de la structure d'un URI? Existe-t-il une référence officielle à une spécification qui stipule que le cache HTTP doit comprendre la sémantique d'un URI afin qu'il ne cache pas les éléments avec une chaîne de requête?
AnthonyWJones
13
Un navigateur Web qui inclut la fonctionnalité de mise en cache des objets (vérifiez le répertoire de cache de votre navigateur). HTTP est un protocole comprenant des directives des serveurs aux clients (mandataires, navigateurs, araignées, etc.) suggérant un contrôle du cache.
tzot
13

Dans Laravel (PHP), nous pouvons le faire de manière claire et élégante (en utilisant l'horodatage de modification de fichier):

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

Et similaire pour CSS

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

Exemple de sortie html ( filemtimeheure de retour sous forme d' horodatage Unix )

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
Kamil Kiełczewski
la source
quelle est la sortie de cette commande en html? Et si je dois renouveler uniquement des versions comme? V = 3,? V = 4 et etc. - Ne force pas le navigateur à charger css à chaque fois que l'utilisateur entre sur le site
Gediminas
filemtime : "Cette fonction renvoie l'heure à laquelle les blocs de données d'un fichier étaient en cours d'écriture, c'est-à-dire l'heure à laquelle le contenu du fichier a été modifié." src: php.net/manual/en/function.filemtime.php
Kamil Kiełczewski
11

La RewriteRule a besoin d'une petite mise à jour pour les fichiers js ou css qui contiennent un versionnage de notation par points à la fin. Par exemple json-1.3.js.

J'ai ajouté une classe de négation de points [^.] À l'expression régulière afin .number. est ignoré.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Nick Johnson
la source
2
Merci pour la contribution! Depuis que j'ai écrit ce post, je suis aussi brûlé par ça. Ma solution était de réécrire uniquement si la dernière partie du nom de fichier contient exactement dix chiffres. (10 chiffres couvrent tous les horodatages du 9/9/2001 au 11/20/2286.) J'ai mis à jour ma réponse pour inclure cette expression régulière:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
Kip
Je comprends l'expression régulière, mais je ne comprends pas avec quel problème vous résolvez [^.]ici. De plus, il n'y a aucun avantage à écrire à l' \dintérieur d'une classe de caractères - \d+fera la même chose. Tel que publié, votre modèle correspondra à n'importe quel nombre de caractères (avec avidité), puis un point littéral, puis un non-point, puis un ou plusieurs chiffres, puis un point, puis cssou js, puis la fin du nom de fichier. Aucune correspondance pour votre échantillon d'entrée: regex101.com/r/RPGC62/1
mickmackusa
10

Pour ASP.NET 4.5 et supérieur, vous pouvez utiliser le regroupement de scripts .

La demande http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81concerne le bundle AllMyScripts et contient une paire de chaînes de requête v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. La chaîne de requête v a un jeton de valeur qui est un identifiant unique utilisé pour la mise en cache. Tant que le bundle ne change pas, l'application ASP.NET demandera le bundle AllMyScripts à l'aide de ce jeton. Si un fichier du bundle change, le framework d'optimisation ASP.NET générera un nouveau jeton, garantissant que les requêtes du navigateur pour le bundle obtiendront le dernier bundle.

Le regroupement présente d'autres avantages, notamment des performances accrues lors des premiers chargements de pages avec minification.

user3738893
la source
S'il vous plaît, aidez-moi, je n'effectue aucune modification dans bundle.config, je ne fais que modifier les fichiers css ou js, alors comment résoudre le problème de mise en cache?
vedankita kumbhar
10

Voici une solution JavaScript pure

(function(){

    // Match this timestamp with the release of your code
    var lastVersioning = Date.UTC(2014, 11, 20, 2, 15, 10);

    var lastCacheDateTime = localStorage.getItem('lastCacheDatetime');

    if(lastCacheDateTime){
        if(lastVersioning > lastCacheDateTime){
            var reload = true;
        }
    }

    localStorage.setItem('lastCacheDatetime', Date.now());

    if(reload){
        location.reload(true);
    }

})();

Ce qui précède recherchera la dernière fois que l'utilisateur a visité votre site. Si la dernière visite a eu lieu avant la publication du nouveau code, il utilise location.reload(true)pour forcer l'actualisation de la page à partir du serveur.

J'ai généralement ceci comme premier script dans le <head>donc il est évalué avant tout autre chargement de contenu. Si un rechargement doit avoir lieu, il est à peine perceptible par l'utilisateur.

J'utilise le stockage local pour stocker l'horodatage de la dernière visite sur le navigateur, mais vous pouvez ajouter des cookies au mix si vous souhaitez prendre en charge les anciennes versions d'IE.

Lloyd Banks
la source
J'ai essayé quelque chose comme ça, cela ne fonctionnera que sur la page rechargée, mais si le site a plusieurs pages partageant les mêmes images / css, les autres pages utiliseront toujours les anciennes ressources.
DeepBlue
9

Poste intéressant. Après avoir lu toutes les réponses ici combinées avec le fait que je n'ai jamais eu de problème avec les chaînes de requête "fausses" (dont je ne sais pas pourquoi tout le monde est si réticent à l'utiliser), je suppose que la solution (qui supprime le besoin de règles de réécriture apache comme dans la réponse acceptée) consiste à calculer un court HASH du contenu du fichier CSS (au lieu du fichier datetime) en tant que chaîne de requête bidon.

Cela se traduirait par ce qui suit:

<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

Bien sûr, les solutions datetime font également le travail dans le cas de la modification d'un fichier CSS mais je pense qu'il s'agit du contenu du fichier css et non du datetime du fichier, alors pourquoi les mélanger?

Michiel
la source
8

Pour mon développement, je trouve que le chrome a une excellente solution.

https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload

Avec les outils de développement ouverts, cliquez simplement longuement sur le bouton d'actualisation et relâchez une fois que vous survolez "Cache vide et rechargement dur".

Ceci est mon meilleur ami, et est un moyen super léger pour obtenir ce que vous voulez!

Frank Bryce
la source
Et si vous utilisez Chrome comme environnement de développement, une autre solution non invasive consiste à désactiver le cache: sous le rouage Paramètres, vous pouvez invalider le cache disque en sélectionnant `` Désactiver le cache '' (remarque: les DevTools doivent être visibles / ouverts pour que cela fonctionne).
Velojet
7

Merci à Kip pour sa solution parfaite!

Je l'ai étendu pour l'utiliser comme un Zend_view_Helper. Parce que mon client a exécuté sa page sur un hôte virtuel, je l'ai également étendu pour cela.

J'espère que ça aide quelqu'un d'autre aussi.

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // file exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {

                return $filePath;
            }
        }
    }

}

Bravo et merci.

lony
la source
7

N'ont pas trouvé l'approche DOM côté client créant dynamiquement l'élément de nœud de script (ou css):

<script>
    var node = document.createElement("script"); 
    node.type = "text/javascript";
    node.src = 'test.js?'+Math.floor(Math.random()*999999999);
    document.getElementsByTagName("head")[0].appendChild(node);
</script>
GreQ
la source
6

Google Chrome a dur Recharger ainsi que Vider le cache et dur Recharger option.You pouvez cliquer sur le bouton de rechargement (En mode Inspect) pour sélectionner un.

ajithes111
la source
Pour clarifier, par "Mode d'inspection", ils se réfèrent à "Dev Tools" aka F12, aka ctrl + shift + i, aka ant menu > More Tools> Developer Tools, alias right click> Inspect Element. Il y a aussi un paramètre enfoui quelque part dans les outils de développement (j'oublie l'emplacement) pour recharger à chaque rechargement.
Jonny Asmar
5

Vous pouvez forcer une "mise en cache à l'échelle de la session" si vous ajoutez l'ID de session en tant que paramètre complet du fichier js / css:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

Si vous souhaitez une mise en cache à l'échelle de la version, vous pouvez ajouter du code pour imprimer la date du fichier ou similaire. Si vous utilisez Java, vous pouvez utiliser une balise personnalisée pour générer le lien de manière élégante.

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
helios
la source
5

Supposons qu'un fichier soit disponible sur:

/styles/screen.css

vous pouvez soit ajouter un paramètre de requête avec des informations de version sur l'URI, par exemple:

/styles/screen.css?v=1234

ou vous pouvez ajouter des informations de version, par exemple:

/v/1234/styles/screen.css

À mon humble avis, la deuxième méthode est meilleure pour les fichiers CSS, car ils peuvent faire référence à des images à l'aide d'URL relatives, ce qui signifie que si vous spécifiez une background-imagetelle chose :

body {
    background-image: url('images/happy.gif');
}

son URL sera effectivement:

/v/1234/styles/images/happy.gif

Cela signifie que si vous mettez à jour le numéro de version utilisé, le serveur traitera cela comme une nouvelle ressource et n'utilisera pas de version mise en cache. Si vous basez votre numéro de version sur Subversion / CVS / etc. révision cela signifie que les modifications des images référencées dans les fichiers CSS seront remarquées. Ce n'est pas garanti avec le premier schéma, à savoir l'URL images/happy.gifrelative à/styles/screen.css?v=1235 is /styles/images/happy.gifne contient aucune information de version.

J'ai implémenté une solution de mise en cache en utilisant cette technique avec des servlets Java et je gère simplement les demandes /v/*avec un servlet qui délègue à la ressource sous-jacente (c'est-à-dire /styles/screen.css). En mode de développement , je mis en cache les en- têtes qui indiquent au client de toujours vérifier la fraîcheur de la ressource avec le serveur (ce qui se traduit généralement par un 304 si vous déléguez à Tomcat de DefaultServletet .css, .js, etc. fichier n'a pas changé) en mode déploiement J'ai mis des en-têtes qui disent "cache pour toujours".

Walter Rumsby
la source
L'ajout d'un dossier que vous pouvez renommer si nécessaire fonctionnera si vous n'utilisez que des URL relatives. Et puis vous assurez - vous de rediriger vers le dossier approprié du dossier de base, à savoir en PHP: <?php header( 'Location: folder1/login.phtml' ); ?>.
Gruber
1
En utilisant la deuxième méthode, une modification d'un CSS invalidera les copies en cache de toutes les images référencées avec des URL relatives, ce qui peut être souhaitable ou non.
TomG
5

Vous pouvez simplement ajouter un nombre aléatoire avec l'URL CSS / JS comme

example.css?randomNo=Math.random()
Ponmudi VN
la source
5

Pour ASP.NET, je suppose que la prochaine solution avec des options avancées (mode debug / release, versions):

Fichiers Js ou Css inclus de cette manière:

<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfix et Global.CssPostfix est calculé de la manière suivante dans Global.asax:

protected void Application_Start(object sender, EventArgs e)
{
    ...
    string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
    bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
    int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
    JsPostfix = "";
#if !DEBUG
    JsPostfix += ".min";
#endif      
    JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
    if (updateEveryAppStart)
    {
        Random rand = new Random();
        JsPosfix += "_" + rand.Next();
    }
    ...
}
Ivan Kochurkin
la source
4

J'ai récemment résolu cela en utilisant Python. Voici le code (devrait être facile à adopter dans d'autres langues):

def import_tag(pattern, name, **kw):
    if name[0] == "/":
        name = name[1:]
    # Additional HTML attributes
    attrs = ' '.join(['%s="%s"' % item for item in kw.items()])
    try:
        # Get the files modification time
        mtime = os.stat(os.path.join('/documentroot', name)).st_mtime
        include = "%s?%d" % (name, mtime)
        # this is the same as sprintf(pattern, attrs, include) in other
        # languages
        return pattern % (attrs, include)
    except:
        # In case of error return the include without the added query
        # parameter.
        return pattern % (attrs, name)

def script(name, **kw):
    return import_tag("""<script type="text/javascript" """ +\
        """ %s src="/%s"></script>""", name, **kw)

def stylesheet(name, **kw):
    return import_tag('<link rel="stylesheet" type="text/css" ' +\
        """%s href="/%s">', name, **kw) 

Ce code ajoute fondamentalement l'horodatage des fichiers en tant que paramètre de requête à l'URL. L'appel de la fonction suivante

script("/main.css")

aura pour résultat

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

L'avantage est bien sûr que vous n'aurez plus jamais à changer votre html, toucher le fichier CSS déclenchera automatiquement une invalidation du cache. Fonctionne très bien et les frais généraux ne sont pas perceptibles.

pi.
la source
os.stat () pourrait-il créer un goulot d'étranglement?
hoju
@Richard stat pourrait être un goulot d'étranglement si le disque est très lent et que les demandes sont très nombreuses. Dans ce cas, vous pouvez mettre en cache l'horodatage quelque part en mémoire et purger ce cache à chaque nouveau déploiement. Pourtant, cette complexité ne sera pas nécessaire dans la majorité des cas d'utilisation.
pi.
4

Si vous utilisez git + PHP, vous pouvez recharger le script depuis le cache chaque fois qu'il y a un changement dans le dépôt git, en utilisant le code suivant:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;
lecture
la source
4

Si vous êtes un développeur qui cherche à éviter la mise en cache, l'onglet réseau Chrome a désactivé l'option de cache. Sinon, vous pouvez le faire sans infrastructure de rendu de serveur à l'aide de deux balises de script.

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // can't use myfile.js stuff yet
</script>')
<script type="text/javascript">
    // do something with myfile.js
</script>
astronought
la source
4

Cette question est super ancienne et apparaît en premier lieu lorsque quelqu'un google ce problème. Ce n'est pas une réponse à la question de la façon dont op le souhaite, mais plutôt une réponse aux développeurs avec ce problème lors du développement et des tests. Et je ne peux pas poster une nouvelle question sur ce sujet car sera marqué comme doublon.

Comme beaucoup d'autres, je voulais juste supprimer brièvement la mise en cache.

"keep caching consistent with the file" .. sa manière trop de tracas ..

D'une manière générale, cela ne me dérange pas de charger plus - même de charger à nouveau des fichiers qui n'ont pas changé - sur la plupart des projets - est pratiquement hors de propos. Lors du développement d'une application - nous chargeons principalement à partir du disque, sur localhost:port - donc ce increase in network trafficproblème n'est pas un problème de rupture d'accord .

La plupart des petits projets ne font que jouer - ils ne finissent jamais en production. donc pour eux vous n'avez besoin de rien de plus ..

En tant que tel, si vous utilisez Chrome Dev Tools , vous pouvez suivre cette approche de désactivation de la mise en cache comme dans l'image ci-dessous: comment forcer Chrome à recharger les fichiers mis en cache

Et si vous avez des problèmes de mise en cache de Firefox : comment forcer le rechargement des ressources sur Firefox

comment désactiver la mise en cache dans Firefox pendant le développement Pour ce faire uniquement en développement, vous avez également besoin d'un mécanisme pour forcer le rechargement pour la production, car vos utilisateurs utiliseront les anciens modules invalides du cache si vous mettez à jour votre application fréquemment et que vous ne fournissez pas de mécanisme de synchronisation de cache dédié comme ceux décrits dans les réponses au dessus de.

Oui, ces informations figurent déjà dans les réponses précédentes, mais j'avais encore besoin de faire une recherche Google pour les trouver.

J'espère que cette réponse est très claire et que vous n'en avez plus besoin.

AIon
la source
OP a demandé quelque chose et a répondu autre chose. Il ne s'agit pas de forcer la charge en local mais en production et vous ne pouvez pas demander aux utilisateurs finaux de suivre ci-dessus pour désactiver le cache, etc.
Jitendra Pancholi
3

Il semble que toutes les réponses suggèrent une sorte de versioning dans le schéma de nommage, qui a ses inconvénients.

Les navigateurs doivent bien savoir quoi mettre en cache et quoi ne pas mettre en cache en lisant la réponse des serveurs Web, en particulier les en-têtes http - pendant combien de temps cette ressource est-elle valide? cette ressource a-t-elle été mise à jour depuis ma dernière récupération? etc.

Si les choses sont configurées «correctement», la simple mise à jour des fichiers de votre application devrait (à un moment donné) rafraîchir les caches des navigateurs. Vous pouvez par exemple configurer votre serveur Web pour dire au navigateur de ne jamais mettre en cache les fichiers (ce qui est une mauvaise idée).

Une explication plus approfondie de la façon dont cela fonctionne est ici https://www.mnot.net/cache_docs/#WORK

pic commun
la source
3

Ajoutez simplement ce code, où vous voulez effectuer un rechargement dur (forcer le navigateur à recharger les fichiers CSS / JS mis en cache) Faites-le à l'intérieur du .load pour qu'il ne se rafraîchisse pas comme une boucle

 $( window ).load(function() {
   location.reload(true);
});
Sandeep Ranjan
la source
Ne fonctionne pas sur Chrome. Chargement des ressources du cache disque
Jason Kim
3

Utilisez simplement le code côté serveur pour ajouter la date du fichier ... de cette façon, il sera mis en cache et rechargé uniquement lorsque le fichier change

Dans ASP.NET

<link rel="stylesheet" href="~/css/custom.css?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/css/custom.css")).ToString(),"[^0-9]", ""))" />

<script type="text/javascript" src="~/js/custom.js?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/js/custom.js")).ToString(),"[^0-9]", ""))"></script>    

Cela peut être simplifié pour:

<script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript"></script>

En ajoutant une méthode d'extension à votre projet pour étendre la page:

public static class Extension_Methods
{
    public static string ResolveClientUrlUnique(this System.Web.UI.Page oPg, string sRelPath)
    {
        string sFilePath = oPg.Server.MapPath(sRelPath);
        string sLastDate = System.IO.File.GetLastWriteTime(sFilePath).ToString();
        string sDateHashed = System.Text.RegularExpressions.Regex.Replace(sLastDate, "[^0-9]", "");

        return oPg.ResolveClientUrl(sRelPath) + "?d=" + sDateHashed;
    }
}
micro
la source
2

Je suggère de mettre en œuvre le processus suivant:

  • versionnez vos fichiers css / js chaque fois que vous déployez, quelque chose comme: screen.1233.css (le numéro peut être votre révision SVN si vous utilisez un système de versioning)

  • les minimiser pour optimiser les temps de chargement

Dan Burzo
la source