Pregunta ¿Cómo forzar al navegador a recargar los archivos CSS / JS en caché?


Me he dado cuenta de que algunos navegadores (en particular, Firefox y Opera) son muy entusiastas al usar copias en caché de .css y .js archivos, incluso entre sesiones del navegador. Esto genera un problema cuando actualiza uno de estos archivos pero el navegador del usuario sigue usando la copia en caché.

La pregunta es: ¿cuál es la forma más elegante de forzar al navegador del usuario a volver a cargar el archivo cuando ha cambiado?

Idealmente, la solución no obligaría al navegador a volver a cargar el archivo en cada visita a la página. Voy a publicar mi propia solución como respuesta, pero tengo curiosidad si alguien tiene una mejor solución y dejaré que vuestros votos decidan.

Actualizar:

Después de permitir la discusión aquí por un tiempo, he encontrado John Millikin y da5idsugerencia de ser útil. Resulta que hay un término para esto: auto-versioning.

He publicado una nueva respuesta a continuación que es una combinación de mi solución original y la sugerencia de John.

Otra idea sugerida por SCdF sería agregar una cadena de consulta falsa al archivo. (Algunos códigos de Python utilizan automáticamente la marca de tiempo como cadena de consulta falsa enviada por Pi.). Sin embargo, existe cierta discusión sobre si el navegador guardará o no un archivo con una cadena de consulta. (Recuerde, queremos que el navegador guarde en caché el archivo y lo use en futuras visitas. Solo queremos que recupere el archivo cuando haya cambiado).

Como no está claro qué sucede con una cadena de consulta falsa, no estoy aceptando esa respuesta.


865
2017-09-23 03:07


origen


Respuestas:


Actualizar:  Reescrito para incorporar sugerencias de John Millikin y da5id. Esta solución está escrita en PHP, pero debe adaptarse fácilmente a otros idiomas.

Actualización 2: Incorporando comentarios de Nick Johnson que el original .htaccess Regex puede causar problemas con archivos como json-1.3.js. La solución es solo reescribir si hay exactamente 10 dígitos al final. (Debido a que 10 dígitos cubre todas las marcas de tiempo del 9/9/2001 al 20/11/2286).

Primero, usamos la siguiente regla de reescritura en .htaccess:

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

Ahora, escribimos la siguiente función de PHP:

/**
 *  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);
}

Ahora, donde sea que incluyas tu CSS, cámbialo de esto:

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

A esto:

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

De esta forma, nunca más tendrá que modificar la etiqueta de enlace, y el usuario siempre verá la última CSS. El navegador podrá almacenar en caché el archivo CSS, pero cuando realice cambios en su CSS, el navegador lo verá como una nueva URL, por lo que no utilizará la copia en caché.

Esto también puede funcionar con imágenes, favicones y JavaScript. Básicamente cualquier cosa que no se genere dinámicamente.


415
2017-09-23 04:04



Técnica simple del lado del cliente

En general, el almacenamiento en caché es bueno. De modo que hay un par de técnicas, dependiendo de si se está solucionando el problema mientras desarrolla un sitio web, o si está tratando de controlar el caché en un entorno de producción.

Los visitantes generales de su sitio web no tendrán la misma experiencia que tiene cuando desarrolle el sitio. Dado que el visitante promedio visita el sitio con menos frecuencia (tal vez solo unas pocas veces al mes, a menos que sea una red de Google o hi5), es menos probable que tenga sus archivos en el caché, y eso puede ser suficiente. Si desea forzar una nueva versión en el navegador, siempre puede agregar una cadena de consulta a la solicitud y aumentar el número de versión cuando realice cambios importantes:

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

Esto asegurará que todos obtengan el nuevo archivo. Funciona porque el navegador mira la URL del archivo para determinar si tiene una copia en el caché. Si su servidor no está configurado para hacer algo con la cadena de consulta, se ignorará, pero el nombre se verá como un archivo nuevo en el navegador.

Por otro lado, si está desarrollando un sitio web, no desea cambiar el número de versión cada vez que guarde un cambio en su versión de desarrollo. Eso sería tedioso.

Por lo tanto, mientras desarrolla su sitio, un buen truco sería generar automáticamente un parámetro de cadena de consulta:

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

Agregar una cadena de consulta a la solicitud es una buena forma de versionar un recurso, pero para un sitio web simple esto puede ser innecesario. Y recuerde, el almacenamiento en caché es algo bueno.

También vale la pena señalar que el navegador no es necesariamente tacaño para mantener los archivos en caché. Los navegadores tienen políticas para este tipo de cosas, y generalmente están jugando según las reglas establecidas en la especificación HTTP. Cuando un navegador realiza una solicitud a un servidor, parte de la respuesta es un encabezado EXPIRES ... una fecha que le dice al navegador por cuánto tiempo debe guardarse en el caché. La próxima vez que el navegador encuentre una solicitud para el mismo archivo, verá que tiene una copia en el caché y observará la fecha EXPIRES para decidir si se debe usar.

Así que créanlo o no, en realidad es su servidor el que hace que el caché de su navegador sea tan persistente. Puede ajustar la configuración del servidor y cambiar los encabezados EXPIRES, pero la pequeña técnica que he escrito anteriormente es probablemente una forma mucho más simple de hacerlo. Debido a que el almacenamiento en caché es bueno, generalmente desea establecer esa fecha en el futuro (un "Encabezado de vencimientos futuros lejanos") y utilizar la técnica descrita anteriormente para forzar un cambio.

Si está interesado en obtener más información sobre HTTP o cómo se realizan estas solicitudes, un buen libro es "Sitios web de alto rendimiento" de Steve Souders. Es una muy buena introducción al tema.


162
2017-09-23 13:25



Google mod_pagespeed el complemento para apache hará la versión automática para usted. Es realmente resbaladizo.

Analiza HTML al salir del servidor web (funciona con PHP, rails, python, HTML estático, cualquier cosa) y reescribe enlaces a CSS, JS, archivos de imágenes para que incluyan un código de identificación. Sirve los archivos en las URL modificadas con un control de caché muy largo sobre ellos. Cuando los archivos cambian, automáticamente cambia las URL para que el navegador los vuelva a buscar. Básicamente solo funciona, sin ningún cambio en tu código. Incluso minificará tu código en el camino de salida también.


110
2017-09-23 03:12



En lugar de cambiar la versión manualmente, recomendaría que use un hash MD5 del archivo CSS real.

Entonces, su URL sería algo así como

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

Todavía puede usar la regla de reescritura para quitar el hash, pero la ventaja es que ahora puede configurar su caché en "caché para siempre", ya que si el URL es el mismo, eso significa que el archivo no ha cambiado.

A continuación, puede escribir un script de shell simple que calcule el hash del archivo y actualice su etiqueta (es probable que desee moverlo a un archivo separado para su inclusión).

Simplemente ejecute ese script cada vez que CSS cambie y esté listo. El navegador SÓLO volverá a cargar sus archivos cuando se modifiquen. Si realiza una edición y luego lo deshace, no tiene sentido averiguar a qué versión debe volver para que sus visitantes no vuelvan a descargarla.


90
2017-09-23 03:21



No estoy seguro de por qué ustedes están tomando tanto dolor para implementar esta solución.

Todo lo que necesita hacer si obtiene la marca de tiempo modificada del archivo y la agrega como una cadena de consulta al archivo

En PHP, lo haría como:

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

FileMtime es una función de PHP que devuelve la marca de tiempo modificada del archivo.


57
2017-09-23 06:02



Puedes simplemente poner ?foo=1234 al final de la importación de css / js, cambiando 1234 para que sea lo que quieras. Eche un vistazo a la fuente SO html para ver un ejemplo.

¿La idea de que sea eso? los parámetros se descartan / ignoran en la solicitud de todos modos y usted puede cambiar ese número cuando despliegue una nueva versión.


Nota: Hay algunos argumentos con respecto a exactamente cómo esto afecta el almacenamiento en caché. Creo que la esencia general es que las solicitudes GET, con o sin parámetros debería ser cachable, por lo que la solución anterior debería funcionar.

Sin embargo, depende del servidor web decidir si quiere adherirse a esa parte de la especificación y al navegador que usa el usuario, ya que puede seguir adelante y solicitar una nueva versión de todos modos.


50
2017-08-05 16:55



He escuchado esto llamado "auto versioning". El método más común es incluir el tiempo mtime del archivo estático en algún lugar de la URL, y quitarlo usando controladores de escritura o confs URL:

Ver también:


37
2018-06-24 23:20



Las aproximadamente 30 respuestas existentes son excelentes consejos para un sitio web circa 2008. Sin embargo, cuando se trata de un moderno, aplicación de una sola página (SPA), podría ser hora de volver a pensar algunas suposiciones fundamentales ... específicamente la idea de que es deseable que el servidor web solo sirva la versión más reciente de un archivo.

Imagina que eres un usuario que tiene una versión METRO de un SPA cargado en su navegador:

  1. Tu tubería de CD implementa la nueva versión norte de la aplicación en el servidor
  2. Navega dentro del SPA, que envía un XHR al servidor para obtener /some.template
    • (Su navegador no ha actualizado la página, por lo que todavía está ejecutando la versión METRO)
  3. El servidor responde con los contenidos de /some.template - ¿Quieres que regrese la versión? METRO o norte de la plantilla?

Si el formato de /some.template cambiado entre versiones METRO y norte (o el archivo fue renombrado o lo que sea) es probable que no quieras la versión norte de la plantilla enviada al navegador que está ejecutando la versión anterior METRO del analizador. †

Las aplicaciones web se topan con este problema cuando se cumplen dos condiciones:

  • Los recursos se solicitan de forma asincrónica en algún momento después de la carga de la página inicial
  • La lógica de la aplicación asume cosas (que pueden cambiar en futuras versiones) sobre el contenido del recurso

Una vez que su aplicación necesita servir varias versiones en paralelo, resolver el almacenamiento en caché y "recargar" se vuelve trivial:

  1. Instale todos los archivos del sitio en directorios versionados: /v<release_tag_1>/…files…, /v<release_tag_2>/…files…
  2. Establecer encabezados HTTP para permitir que los navegadores guarden en caché los archivos para siempre
    • (O mejor aún, poner todo en un CDN)
  3. Actualizar todo <script> y <link> etiquetas, etc. para apuntar a ese archivo en uno de los directorios versionados

El último paso parece complicado, ya que podría requerir la invocación de un generador de URL para cada URL en su código del lado del servidor o del lado del cliente. O podrías hacer un uso inteligente del <base> etiqueta y cambie la versión actual en un solo lugar.

† Una forma de evitar esto es ser agresivo al obligar al navegador a volver a cargar todo cuando se lanza una nueva versión. Pero para poder completar cualquier operación en curso, puede ser más fácil admitir al menos dos versiones en paralelo: v-current y v-previous.


19
2017-09-23 03:23



¡No use foo.css? Version = 1! Los navegadores no deben almacenar en caché las URL con variables GET. De acuerdo a http://www.thinkvitamin.com/features/webapps/serving-javascript-fast, aunque IE y Firefox ignoran esto, ¡Opera y Safari no lo hacen! En su lugar, use foo.v1234.css y use reglas de reescritura para quitar el número de versión.


14
2017-09-23 13:54