Cómo forzar al navegador a recargar archivos CSS y JavaScript en caché

1074

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

¿Cuál es la forma más elegante de obligar al navegador del usuario a volver a cargar el archivo cuando ha cambiado?

Idealmente, la solución no obligaría al navegador a recargar el archivo en cada visita a la página.


He encontrado útil la sugerencia de John Millikin y da5id . Resulta que hay un término para esto: control de versiones automático .

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 que sugirió SCdF sería agregar una cadena de consulta falsa al archivo. (Algunos códigos de Python, para usar automáticamente la marca de tiempo como una cadena de consulta falsa, fueron enviados por pi .)

Sin embargo, existe cierta discusión sobre si el navegador almacenaría en caché un archivo con una cadena de consulta. (Recuerde, queremos que el navegador almacene el archivo en caché y lo use en futuras visitas. Solo queremos que recupere el archivo nuevamente cuando haya cambiado).

5
  • Tengo esto en mi .htaccess, y nunca ningún problema con los archivos almacenados en caché: ExpiresActive On ExpiresDefault "modification". 15 de mayo de 2014 a las 14:06
  • 2
    Definitivamente estoy de acuerdo en que agregar información de versiones a la URL del archivo es, con mucho, la mejor manera de hacerlo. Funciona, todo el tiempo, para todos. Pero, si no lo está usando, y solo necesita volver a cargar ese archivo CSS o JS ocasionalmente en su propio navegador ... ¡simplemente ábralo en su propia pestaña y presione SHIFT-reload (o CTRL-F5)! Puede hacer efectivamente lo mismo usando JS cargando un archivo en un iframe (oculto), esperando hasta que se cargue y luego llamando iframe.contentWindow.location.reload(true). Consulte el método (4) de stackoverflow.com/a/22429796/999120 ; se trata de imágenes, pero se aplica lo mismo.
    Doin
    27/12/15 a las 5:17
  • 2
    Realmente aprecio la forma en que se hizo esta pregunta y se ha actualizado desde entonces. Describió completamente lo que debería esperar en las respuestas. Voy a seguir este enfoque en mis preguntas a partir de ahora. ¡Salud!
    rd22
    1 de agosto de 2016 a las 9:46
  • 1
    Como referencia: la respuesta eliminada de da5id es "Si una actualización es lo suficientemente grande / importante, generalmente cambio el nombre del archivo". . 28 nov.20 a las 3:48
  • Si los cambios no son muy frecuentes, tengo una sugerencia. Simplemente cambie el nombre del archivo y edite el código fuente para incluir el nuevo nombre del archivo. Entonces no hay ningún archivo en caché para que el navegador lo lea. 24 feb a las 10:00
475

Esta solución está escrita en PHP, pero debería adaptarse fácilmente a otros lenguajes.

La .htaccessexpresión regular original puede causar problemas con archivos como json-1.3.js. La solución es reescribir solo si hay exactamente 10 dígitos al final. (Porque 10 dígitos cubren todas las marcas de tiempo desde el 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 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 incluya su CSS, cámbielo 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 manera, nunca más tendrá que modificar la etiqueta del enlace y el usuario siempre verá el último 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, favicons y JavaScript. Básicamente, cualquier cosa que no se genere dinámicamente.

38
  • dieciséis
    Mi propio servidor de contenido estático hace exactamente lo mismo, excepto que uso un parámetro para el control de versiones (base.css? V = 1221534296) en lugar de un cambio de nombre de archivo (base.1221534296.css). Sin embargo, sospecho que su camino puede ser un poco más eficiente. Muy genial. 2/06/11 a las 20:55
  • 4
    @Kip: Solución muy hábil. La reescritura de URL, obviamente, tiene mucho más que ofrecer que solo URL bonitas. 6 de agosto de 2011 a las 12:51
  • 37
    Veo un problema con esto, que accede al sistema de archivos muchas veces, exactamente, número de enlaces * número de solicitudes / seg ... que puede o no ser un problema para usted. 24/09/12 a las 23:34
  • 3
    @AlixAxel: No, los navegadores lo recuperarán cuando cambie el parámetro, pero algunos proxies públicos no almacenarán en caché los archivos con parámetros de URL, por lo que la mejor práctica es incluir la versión en la ruta. Y la sobrecarga de mod_rewrite es minúscula en comparación con cualquier otro cuello de botella de rendimiento en WPO 27/11/12 a las 11:54
  • 8
    ¿Es file_existsrealmente necesario el primer control? filemtimedevolverá falso en caso de falla, así que ¿por qué no simplemente asignar el valor filemtime a una variable y verificar si es falso antes de cambiar el nombre del archivo? Eso reduciría una operación de archivo innecesaria que realmente se sumaría.
    Gavin
    4 de junio de 2014 a las 23:21
207

Técnica simple del lado del cliente

En general, el almacenamiento en caché es bueno ... Por lo tanto, hay un par de técnicas, dependiendo de si está solucionando el problema usted mismo 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 usted tiene cuando está desarrollando el sitio. Dado que el visitante promedio llega al sitio con menos frecuencia (tal vez solo unas pocas veces al mes, a menos que sea un Google o una red hi5), es menos probable que tenga sus archivos en 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 la caché. Si su servidor no está configurado para hacer nada con la cadena de consulta, se ignorará, pero el nombre se verá como un nuevo archivo en el navegador.

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

Entonces, 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 manera 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 a la hora de mantener archivos en caché. Los navegadores tienen políticas para este tipo de cosas y, por lo general, siguen 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 cuánto tiempo debe mantenerse en caché. La próxima vez que el navegador encuentre una solicitud para el mismo archivo, verá que tiene una copia en la caché y buscará la fecha de Caducidad para decidir si debe usarse.

Así que lo crea o no, en realidad es su servidor lo que hace que la caché del navegador sea tan persistente. Puede ajustar la configuración de su servidor y cambiar los encabezados Expires , pero la pequeña técnica que escribí anteriormente es probablemente una forma mucho más sencilla de hacerlo. Dado que el almacenamiento en caché es bueno, normalmente desea establecer esa fecha en un futuro lejano (un "Encabezado de vencimiento del futuro lejano") 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.

12
  • 3
    El truco rápido de generar cadenas de consulta con Javascript funciona muy bien durante el desarrollo activo. Hice lo mismo con PHP. 21 de mayo de 2013 a las 18:04
  • 2
    Ésta es la forma más sencilla de lograr el resultado deseado del póster original. El método mod_rewrite funciona bien si desea forzar una recarga del archivo .css o .js CADA vez que cargue la página. Este método aún permite el almacenamiento en caché hasta que realmente cambie el archivo y realmente desee que se fuerce la recarga. 19/0214 a las 0:22
  • 1
    Esto no parece funcionar para mi CSS cuando uso: <link href='myCss.css?dev=14141'...> 24/07/2015 a las 17:26
  • 5
    Ésta no es una solución viable. Un buen número de navegadores simplemente se negarán a almacenar en caché cualquier cosa que tenga una cadena de consulta. Esta es la razón por la que Google, GTMetrix y herramientas similares generarán una señal si tiene cadenas de consulta en referencias a contenido estático. Si bien es ciertamente una solución decente para el desarrollo, no es en absoluto una solución para la producción. Además, el navegador controla el almacenamiento en caché, no el servidor. El servidor simplemente SUGIERE cuándo debe actualizarse; un navegador no TIENE que escuchar al servidor (ya menudo no lo hace). Los dispositivos móviles son un excelente ejemplo de esto.
    Nate I
    24/04/18 a las 17:59
  • 1
    La solución document.write funciona demasiado bien, ahora no puedo establecer un punto de interrupción en Chrome porque la URL sigue cambiando y, por lo tanto, sigue actualizándose y perdiendo mis puntos de interrupción. 26/07/19 a las 15:30
114

El complemento mod_pagespeed de Google para Apache hará el control automático de versiones por usted. Es realmente resbaladizo.

Analiza HTML al salir del servidor web (funciona con PHP, Ruby on Rails , Python, HTML estático, cualquier cosa) y reescribe enlaces a CSS, JavaScript, archivos de imagen 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, cambia automáticamente las URL para que el navegador tenga que volver a buscarlos. Básicamente, simplemente funciona, sin ningún cambio en su código. Incluso minimizará su código al salir también.

8
  • 1
    Eso es genial, pero todavía está en beta. ¿Se puede utilizar para servicios empresariales? 15 de julio de 2011 a las 4:00
  • 27
    Esto es INCORRECTO (juguetear automáticamente con la fuente) cuando es claramente un problema del navegador. Danos (a los desarrolladores) una verdadera actualización del borrado del cerebro: <ctrl> + F5
    T4NK3R
    20 de septiembre de 2011 a las 12:50
  • 25
    mod_pagespeed es funcionalmente equivalente a un paso de compilación / compilación completamente automático para su html / css / js. Creo que sería difícil encontrar desarrolladores serios que piensen que los sistemas de compilación son intrínsecamente incorrectos, o que hay algo de malo en que sea completamente automático. La analogía de una construcción limpia es borrar la caché de mod_pagespeed : code.google.com/p/modpagespeed/wiki/… ?
    Leopd
    20/09/11 a las 17:38
  • 3
    @ T4NK3R mod_pagespeed no tiene que hacer nada con su fuente para administrar la caché, simplemente se mencionó que puede ayudar con cosas como la minificación. En cuanto a si es "INCORRECTO" o no, es completamente subjetivo. Puede que sea incorrecto para usted, pero eso no significa que sea instirínsecamente malo . 3 de agosto de 2012 a las 18:10
  • 2
    También funciona con nginx, aunque debe compilarlo desde la fuente: developers.google.com/speed/pagespeed/module/…
    Rohit
    22/01/15 a las 7:41
94

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

Entonces tu URL sería algo así como

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

Aún puede usar la regla de reescritura para eliminar el hash, pero la ventaja es que ahora puede configurar su política de caché en "caché para siempre", ya que si la URL es la misma, eso significa que el archivo no se modifica.

Luego puede escribir un script de shell simple que calcularía el hash del archivo y actualizaría su etiqueta (probablemente desee moverlo a un archivo separado para su inclusión).

Simplemente ejecute ese script cada vez que CSS cambie y estará bien. El navegador SOLO volverá a cargar sus archivos cuando se modifiquen. Si realiza una edición y luego la deshace, no hay problema en averiguar a qué versión debe regresar para que sus visitantes no vuelvan a descargar.

6
  • 1
    lamentablemente no sé cómo implementarlo. Consejo por favor ... más detalles ... 20/10/2014 a las 9:56
  • Una implementación en shell, ruby, etc. sería genial
    Peter
    9/12/14 a las 19:28
  • 3
    Muy buena solución ... pero creo que consume recursos para calcular el hash del archivo en cada solicitud de archivo (css, js, imágenes, html, etc.) para cada visita a una página. 2 de mayo de 2015 a las 22:18
  • Esta es una solución estándar para aquellos que usan js o css empaquetados con gulp, grunt o webpack, la implementación difiere para cada solución, pero el hash de sus archivos como paso de compilación es común y se sugiere para aplicaciones empaquetadas modernas 13/04/18 a las 22:45
  • @DeepBlue - la respuesta dice "ejecutar ese script cada vez que cambie CSS" . Eso NO está en cada visita a la página. OTOH La respuesta omite detalles importantes: ¿cómo el hash modificado se convierte en parte de la URL? No sé... 14/04/19 a las 21:35
83

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

Todo lo que necesita hacer es obtener la marca de tiempo modificada del archivo y agregarla como una cadena de consulta al archivo.

En PHP lo haría como:

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

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

9
  • Puedes usar mycss.css?1234567890.
    Gavin
    4 de junio de 2014 a las 23:43
  • 5
    muy elegante, aunque lo he modificado ligeramente <link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>, por si acaso algunos de los argumentos en este hilo sobre el almacenamiento en caché de URL con variables GET (en el formato sugerido) son correctos 24/12/15 a las 13:52
  • Además de mi último comentario, he visto que wordpress usa, ¡ ?ver=quién sabe! 24/12/15 a las 14:25
  • 1
    Gran solucion. Además, para mí encontré que filemtime no funcionaba para un nombre de dominio completo (FQDN), así que usé el FQDN para la parte href y $ _SERVER ["DOCUMENT_ROOT"] para la parte filemtime. EJ: <link rel = "stylesheet" href = "http: //theurl/mycss.css? V = <? Php echo filemtime ($ _ SERVER [" DOCUMENT_ROOT "]. '/Mycss.css')?>" /> 18 de agosto de 2016 a las 16:56
  • Muchas gracias. Sencillo y bueno. Aquí está en Python: progpath = os.path.dirname (sys.argv [0]) def versionize (archivo): timestamp = os.path.getmtime ('% s /../ web /% s'% (progpath , file)) return '% s? v =% s'% (file, timestamp) print <link href = "% s" rel = "stylesheet" '' type = "text / css" /> '\% versionize ( 'css / main.css')
    dlink
    24 dic 2017 a las 22:00
55

Puede poner ?foo=1234al final de su importación de CSS / JavaScript, cambiando 1234 para que sea lo que quiera. Eche un vistazo a la fuente HTML de Stack Overflow para ver un ejemplo.

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


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

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

8
  • Disparates. La cadena de consulta (también conocida como parámetros GET) son parte de la URL. Pueden y se almacenarán en caché. Esta es una buena solución. 23 de septiembre de 2008 a las 15:39
  • 9
    @troelskn: La especificación HTTP 1.1 dice lo contrario (con respecto a las solicitudes GET y HEAD con parámetros de consulta): los cachés NO DEBEN tratar las respuestas a tales URI como nuevas a menos que el servidor proporcione un tiempo de vencimiento explícito. Consulte w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9 23 de septiembre de 2008 a las 18:52
  • 4
    Probé el tipo de control de versiones de la cadena de consulta con todos los navegadores principales y SÍ almacenan en caché el archivo, con especificaciones o no. Sin embargo, creo que es mejor usar el formato style.TIMESTAMP.css sin abusar de las cadenas de consulta de todos modos porque todavía existe la posibilidad de que el software proxy de almacenamiento en caché NO almacene el archivo en caché. 8/10/2009 a las 15:24
  • 34
    Vale la pena señalar, por la razón que sea, que Stackoverflow en sí mismo usa el método de cadena de consulta.
    jason
    1 de mayo de 2010 a las 20:13
  • 2
    Haber verificado que el uso del parámetro? = No hará que los navegadores recuperen el archivo en caché cuando cambie el parámetro. La única forma es cambiar el nombre del archivo de forma programática en el extremo del servidor según lo respondido por Kip 2 de junio de 2013 a las 6:10
43

Escuché que esto se llama "control automático de versiones". El método más común es incluir el tiempo de modificación del archivo estático en algún lugar de la URL y eliminarlo usando controladores de reescritura o configuraciones de URL:

Ver también:

1
  • 3
    Gracias, creo que este fue otro caso en el que se discutió mi idea, simplemente no sabía cómo se llamaba, así que nunca la encontré en las búsquedas de Google.
    Kip
    23 de septiembre de 2008 a las 12:12
30

Las aproximadamente 30 respuestas existentes son un gran consejo para un sitio web de alrededor de 2008. Sin embargo, cuando se trata de una aplicación moderna de una sola página (SPA), podría ser el momento de repensar algunas suposiciones fundamentales ... específicamente la idea de que es deseable que el servidor web sirva solo la versión más reciente y única de un archivo. .

Imagine que es un usuario que tiene la versión M de un SPA cargada en su navegador:

  1. Su canalización de CD implementa la nueva versión N de la aplicación en el servidor
  2. Navega dentro del SPA, que envía un XMLHttpRequest (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 M )
  1. El servidor responde con el contenido de /some.template: ¿desea que devuelva la versión M o N de la plantilla?

Si el formato de /some.templatecambió entre las versiones M y N (o se cambió el nombre del archivo o lo que sea) , probablemente no desee que la versión N de la plantilla se envíe al navegador que ejecuta la versión anterior M del analizador . †

Las aplicaciones web tienen este problema cuando se cumplen dos condiciones:

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

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

  1. Instalar todos los archivos del sitio en directorios versionados: /v<release_tag_1>/…files…,/v<release_tag_2>/…files…
  2. Configure encabezados HTTP para permitir que los navegadores almacenen archivos en caché para siempre
  • (O mejor aún, pon todo en un CDN)
  1. Actualice todas las etiquetas <script>y <link>, etc. para apuntar a ese archivo en uno de los directorios versionados

Ese último paso suena complicado, ya que podría requerir llamar a un creador de URL para cada URL en su código del lado del servidor o del lado del cliente. O simplemente podría hacer un uso inteligente de la <base>etiqueta y cambiar la versión actual en un solo lugar.

† Una forma de evitar esto es ser agresivo al obligar al navegador a recargar todo cuando se lanza una nueva versión. Pero para permitir que se completen las operaciones en curso, puede que sea más fácil admitir al menos dos versiones en paralelo: v-current y v-previous.

5
  • Michael: tu comentario es muy relevante. Estoy aquí precisamente tratando de encontrar una solución para mi SPA. Recibí algunos consejos, pero tuve que encontrar una solución yo mismo. Al final, estaba muy contento con lo que se me ocurrió, así que escribí una publicación en el blog y una respuesta a esta pregunta (incluido el código). Gracias por los consejos
    statler
    12 de mayo de 2016 a las 8:23
  • Gran comentario. No puedo entender mientras la gente sigue hablando de la eliminación de caché y el almacenamiento en caché HTTP como la solución real a los problemas de almacenamiento en caché de los sitios web sin comentar los nuevos problemas de las SPA, como si este fuera un caso marginal. 30 de junio de 2017 a las 15:46
  • 2
    ¡Excelente respuesta y estrategia absolutamente ideal! ¡Y puntos de bonificación por mencionar la baseetiqueta! En cuanto al soporte de código antiguo: esto no siempre es una posibilidad, ni siempre es una buena idea. Las nuevas versiones de código pueden admitir cambios importantes en otras partes de una aplicación o pueden incluir arreglos de emergencia, parches de vulnerabilidad, etc. Todavía tengo que implementar esta estrategia por mí mismo, pero siempre he sentido que la arquitectura general debería permitir que las implementaciones etiqueten una versión anterior como obsoletey forzar una recarga la próxima vez que se realice una llamada asincrónica (o simplemente anular la autenticación forzosamente de todas las sesiones a través de WebSockets ). 23 dic 2017 a las 0:30
  • Es bueno ver una respuesta bien pensada con respecto a las aplicaciones de una sola página.
    Nate I
    16/04/19 a las 15:33
  • Eso es "implementación azul-verde" si desea buscar más información.
    Fil
    16/07/19 a las 11:10
dieciséis

¡No lo use foo.css?version=1!

Se supone que los navegadores no deben almacenar en caché las URL con variables GET. Según http://www.thinkvitamin.com/features/webapps/serving-javascript-fast , aunque Internet Explorer y Firefox ignoran esto, Opera y Safari no lo hacen. En su lugar, use foo.v1234.css y use reglas de reescritura para eliminar el número de versión.

3
  • 1
    En primer lugar, los navegadores no almacenan en caché, eso es una función de HTTP. ¿Por qué http se preocuparía por la estructura de un URI? ¿Existe una referencia oficial a una especificación que indique que el almacenamiento en caché HTTP debe comprender la semántica de un URI para que no almacene elementos en caché con una cadena de consulta? 23 de septiembre de 2008 a las 8:17
  • 14
    Un navegador web que incluye la funcionalidad de almacenar objetos en caché (verifique el directorio de caché de su navegador). HTTP es un protocolo que incluye directivas de los servidores a los clientes (proxies, navegadores, arañas, etc.) que sugieren el control de la caché.
    tzot
    29 de septiembre de 2008 a las 15:47
  • El enlace de thinkvitamin.com está roto (el dominio parece existir, pero no hay ningún reenvío). 22 nov.20 a las 0:58
dieciséis

En Laravel (PHP) podemos hacerlo de la siguiente manera clara y elegante (usando la marca de tiempo de modificación del archivo):

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

Y similar para CSS

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

Salida HTML de ejemplo ( filemtimetiempo de retorno como una marca de tiempo de Unix )

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
2
  • ¿Cuál es la salida de este comando en html? ¿Y si necesito renovar solo versiones como? V = 3,? V = 4 y etc. - No obliga al navegador a cargar css cada vez que el usuario ingresa al sitio web 28 de septiembre de 2017 a las 5:38
  • filemtime : "Esta función devuelve el momento en el que se escribieron los bloques de datos de un archivo, es decir, el momento en que se modificó el contenido del archivo". src: php.net/manual/en/function.filemtime.php 28 de septiembre de 2017 a las 11:12
12

RewriteRule necesita una pequeña actualización para los archivos JavaScript o CSS que contienen una versión de notación de puntos al final. Por ejemplo, json-1.3.js .

Agregué una clase de negación de puntos [^.] A la expresión regular, entonces .number. se ignora.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
2
  • 2
    ¡Gracias por el aporte! Desde que escribí esta publicación, esto también me ha quemado. Mi solución fue reescribir solo si la última parte del nombre del archivo contiene exactamente diez dígitos. (10 dígitos cubren todas las marcas de tiempo desde el 9/9/2001 al 20/11/2286). Actualicé mi respuesta para incluir esta expresión regular:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
    Kip
    5 de agosto de 2010 a las 21:07
  • Entiendo las expresiones regulares, pero no entiendo con qué problema estás resolviendo [^.]aquí. Además, escribir \ddentro de una clase de personaje no tiene ningún beneficio : \d+hará lo mismo. Como se publicó, su patrón coincidirá con cualquier número de caracteres (con avidez), luego un punto literal, luego un no punto, luego uno o más dígitos, luego un punto, luego csso js, luego el final del nombre del archivo. No coincide con su entrada de muestra: regex101.com/r/RPGC62/1 5 de diciembre de 2019 a las 6:52
11

Publicación interesante. Habiendo leído todas las respuestas aquí combinadas con el hecho de que nunca he tenido ningún problema con cadenas de consulta "falsas" (que no estoy seguro de por qué todos son tan reacios a usar esto) supongo que la solución (que elimina la necesidad de reglas de reescritura de Apache como en la respuesta aceptada) es calcular un hash corto del contenido del archivo CSS (en lugar de la fecha y hora del archivo) como una cadena de consulta falsa.

Esto daría como resultado lo siguiente:

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

Por supuesto, las soluciones de fecha y hora también hacen el trabajo en el caso de editar un archivo CSS, pero creo que se trata del contenido del archivo CSS y no de la fecha y hora del archivo, entonces, ¿por qué mezclarlos?

1
  • Sí, creo que esta es la solución más simple y funciona. 17 de mayo a las 21:05
11

Para ASP.NET 4.5 y superior, puede utilizar la agrupación de secuencias de comandos .

The request http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 is for the bundle AllMyScripts and contains a query string pair v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. The query string v has a value token that is a unique identifier used for caching. As long as the bundle doesn't change, the ASP.NET application will request the AllMyScripts bundle using this token. If any file in the bundle changes, the ASP.NET optimization framework will generate a new token, guaranteeing that browser requests for the bundle will get the latest bundle.

La agrupación tiene otros beneficios, incluido un mayor rendimiento en las cargas de página por primera vez con minificación.

1
  • Por favor, ayúdenme. No estoy haciendo ningún cambio en bundle.config, solo cambiando en los archivos css o js, ​​entonces, ¿cómo puedo resolver el problema de almacenamiento en caché? 27/02/2017 a las 6:43
11

Aquí hay una solución de JavaScript pura

(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);
    }

})();

Lo anterior buscará la última vez que el usuario visitó su sitio. Si la última visita fue antes de que publicara el nuevo código, se usa location.reload(true)para forzar la actualización de la página desde el servidor.

Por lo general, tengo esto como el primer script dentro del, <head>por lo que se evalúa antes de que se cargue cualquier otro contenido. Si es necesario que se produzca una recarga, el usuario apenas lo nota.

Estoy usando el almacenamiento local para almacenar la marca de tiempo de la última visita en el navegador, pero puede agregar cookies a la combinación si desea admitir versiones anteriores de IE.

1
  • 1
    Intenté algo como esto, esto solo funcionará en la página recargada, pero si el sitio tiene varias páginas que comparten el mismo css / imágenes, otras páginas seguirán usando recursos antiguos. 3 de mayo de 2015 a las 0:29
9

Para mi desarrollo, encuentro que Chrome tiene una gran solución.

https://superuser.com/a/512833

Con las herramientas de desarrollador abiertas, simplemente haga clic prolongadamente en el botón de actualización y suéltelo una vez que pase el mouse sobre "Vaciar caché y recargar duro".

Este es mi mejor amigo, ¡y es una forma súper liviana de obtener lo que quieres!

4
  • Y si está utilizando Chrome como su entorno de desarrollo, otra solución no invasiva es deshabilitar el caché: en el engranaje de Configuración, puede invalidar el caché del disco seleccionando 'Desactivar caché' (nota: DevTools debe estar visible / abierto para que esto funcione).
    Velojet
    5 de febrero de 2016 a las 21:52
  • ¿Qué es un "clic largo" ? 28 nov.20 a las 5:05
  • El vínculo está (efectivamente) roto. Redirige a la página genérica "Chrome DevTools" - developers.google.com/web/tools/chrome-devtools 28 nov.20 a las 5:05
  • @PeterMortensen Cuando haces clic y mantienes presionado el botón de clic. 13 de enero a las 20:56
8

¡Gracias a Kip por su solución perfecta !

Lo extendí para usarlo como Zend_view_Helper. Debido a que mi cliente ejecuta su página en un host virtual, también la extendí para eso.

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

No he encontrado el enfoque DOM del lado del cliente creando el elemento de nodo de script (o CSS) dinámicamente:

<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>
1
  • Lo que ha encontrado entonces? ¿Puedes dejar eso más claro? Preferiblemente editando su respuesta (pero sin "Editar:", "Actualizar:" o similar), no aquí en los comentarios. 28/11/20 a las 6:16
7

Supongamos que tiene un archivo disponible en:

/styles/screen.css

Puede agregar un parámetro de consulta con información de versión en el URI, por ejemplo:

/styles/screen.css?v=1234

O puede anteponer la información de la versión, por ejemplo:

/v/1234/styles/screen.css

En mi humilde opinión, el segundo método es mejor para los archivos CSS, porque pueden referirse a imágenes usando URL relativas, lo que significa que si especificas algo background-imageasí:

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

Su URL será efectivamente:

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

Esto significa que si actualiza el número de versión utilizado, el servidor lo tratará como un nuevo recurso y no utilizará una versión en caché. Si basa su número de versión en la revisión de Subversion , CVS , etc., esto significa que se notarán los cambios en las imágenes a las que se hace referencia en los archivos CSS. Que no se garantiza con el primer esquema, es decir, la dirección URL images/happy.gifrelativa a /styles/screen.css?v=1235decir /styles/images/happy.gifque no contienen ninguna información de la versión.

Implementé una solución de almacenamiento en caché usando esta técnica con servlets de Java y simplemente manejo las solicitudes /v/*con un servlet que delega al recurso subyacente (es decir /styles/screen.css). En el modo de desarrollo que el almacenamiento en caché conjunto cabeceras que le dicen al cliente que compruebe siempre la frescura de los recursos con el servidor (esto típicamente resulta en un 304 si delega a Tomcat de DefaultServlety .css, .js, etc archivo no ha cambiado), mientras que en el modo de implementación Establezco encabezados que dicen "caché para siempre".

2
  • Simplemente agregar una carpeta a la que pueda cambiar el nombre cuando sea necesario funcionará si solo usa URL relativas. Y luego se asegura de redirigir a la carpeta correcta de la carpeta de base, es decir en PHP: <?php header( 'Location: folder1/login.phtml' ); ?>.
    Gruber
    20 de septiembre de 2012 a las 8:56
  • 2
    Con el segundo método, un cambio en un CSS invalidará las copias en caché de todas las imágenes a las que se hace referencia con URL relativas, lo que puede ser deseable o no.
    TomG
    6 de noviembre de 2013 a las 15:55
7

Google Chrome tiene la opción Hard Reload , así como la opción Empty Cache y Hard Reload . Puede hacer clic y mantener presionado el botón de recarga (en el modo de inspección ) para seleccionar uno.

1
  • 1
    Para aclarar, por "Inspeccionar Mode", se refieren a "Herramientas de desarrollo" también conocido como F12, también conocido como Ctrl + Shift + i, alias ant menu> More Tools> Developer Tools, alias right click> Inspect Element. También hay una configuración escondida en algún lugar de las herramientas de desarrollo (olvido la ubicación) para recargar duro en cada recarga. 23 dic 2017 a las 0:20
6

Puede forzar un "almacenamiento en caché de toda la sesión" si agrega el ID de sesión como un parámetro falso del archivo JavaScript / CSS:

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

Si desea un almacenamiento en caché de toda la versión, puede agregar algún código para imprimir la fecha del archivo o similar. Si está usando Java, puede usar una etiqueta personalizada para generar el enlace de una manera elegante.

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

Simplemente puede agregar un número aleatorio con la URL de CSS y JavaScript como

example.css?randomNo = Math.random()
6

Para ASP.NET propongo la siguiente solución con opciones avanzadas (modo debug / release, versiones):

Incluya archivos JavaScript o CSS de esta manera:

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

Global.JsPostfix y Global.CssPostfix se calculan de la siguiente manera en 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();
    }
    ...
}
6

Recientemente resolví esto usando Python. Aquí está el código (debería ser fácil de adoptar en otros idiomas):

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 %s src="/%s"></script>', name, **kw)

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

Básicamente, este código agrega la marca de tiempo de los archivos como un parámetro de consulta a la URL. La llamada de la siguiente función

script("/main.css")

resultará en

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

La ventaja, por supuesto, es que nunca más tendrá que cambiar su contenido HTML, tocar el archivo CSS activará automáticamente una invalidación de caché. Funciona muy bien y la sobrecarga no se nota.

4
  • ¿Podría os.stat () crear un cuello de botella?
    hoju
    23/07/12 a las 3:19
  • @Richard stat podría ser un cuello de botella si el disco es muy lento y las solicitudes son muchas. En ese caso, puede almacenar en caché la marca de tiempo en algún lugar de la memoria y purgar esta caché en cada nueva implementación. Sin embargo, esta complejidad no será necesaria en la mayoría de los casos de uso.
    pi.
    23/07/12 a las 11:06
  • Sé que esto es antiguo, pero para cualquiera que esté leyendo, una marca de tiempo es demasiado agresiva. Significa que nunca tendrá almacenamiento en caché y, si lo desea, puede administrarlo con encabezados personalizados para archivos estáticos. 11 de enero a las 22:43
  • @LarryBud: Es la marca de tiempo del archivo, no la marca de tiempo actual. Definitivamente tendrás almacenamiento en caché.
    pi.
    14 de enero a las 8:56
5

Si está usando Git y PHP, puede volver a cargar el script desde la caché cada vez que haya un cambio en el repositorio de Git, usando el siguiente código:

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

Para el desarrollo: use una configuración del navegador: por ejemplo, Chrome network tabtiene una disable cacheopción.

Para producción: agregue un parámetro de consulta único a la solicitud ( por ejemplo , q?Date.now()) con un marco de representación del lado del servidor o código JavaScript puro.

// Pure JavaScript unique query parameter generation
//
//=== myfile.js

function hello() { console.log('hello') };

//=== end of file

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // document.write is considered bad practice!
    // We can't use hello() yet
</script>')

<script type="text/javascript">
    hello();
</script>
1
  • Este ejemplo necesita ser editado. La idea es buena, pero existen confusiones con las etiquetas del script de inicio y finalización en lo anterior.
    Magnus
    2 oct.20 a las 8:25
4

Parece que todas las respuestas aquí sugieren algún tipo de control de versiones en el esquema de nomenclatura, lo que tiene sus desventajas.

Los navegadores deben saber qué almacenar en caché y qué no almacenar en caché leyendo la respuesta del servidor web, en particular los encabezados HTTP, ¿durante cuánto tiempo es válido este recurso? ¿Se actualizó este recurso desde la última vez que lo recuperé? etc.

Si las cosas están configuradas 'correctamente', simplemente actualizar los archivos de su aplicación debería (en algún momento) actualizar las cachés del navegador. Por ejemplo, puede configurar su servidor web para decirle al navegador que nunca almacene archivos en caché (lo cual es una mala idea).

Una explicación más detallada de cómo funciona esto se encuentra en Cómo funcionan los cachés web .

4

Simplemente agregue este código donde desee realizar una recarga completa (obligue al navegador a recargar los archivos CSS y JavaScript en caché):

$(window).load(function() {
    location.reload(true);
});

Haga esto dentro del .load, para que no se actualice como un bucle.

1
  • No funciona en Chrome. Todavía cargando activos desde la memoria caché del disco 12/10/2017 a las 18:08
4

Simplemente use el código del lado del servidor para agregar la fecha del archivo ... de esa manera se almacenará en caché y solo se volverá a cargar cuando el archivo cambie.

En ASP.NET:

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

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

Esto se puede simplificar a:

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

Al agregar un método de extensión a su proyecto para extender la página :

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;
    }
}
0
4

Para desarrolladores con este problema mientras desarrollan y prueban:

Elimine el almacenamiento en caché brevemente.

"keep caching consistent with the file" .. es demasiada molestia ..

En términos generales, no me importa cargar más, incluso volver a cargar archivos que no cambiaron, en la mayoría de los proyectos, es prácticamente irrelevante. Mientras desarrollamos una aplicación, la cargamos principalmente desde el disco, por localhost:port lo que este increase in network trafficproblema no es un problema de ruptura .

La mayoría de los proyectos pequeños están simplemente jugando, nunca terminan en producción. Entonces para ellos no necesitas nada más ...

Como tal, si usa Chrome DevTools , puede seguir este enfoque de desactivación del almacenamiento en caché como en la imagen a continuación:

Cómo forzar a Chrome a recargar archivos en caché

Y si tiene problemas con el almacenamiento en caché de Firefox :

Cómo forzar la recarga de activos en Firefox

Cómo deshabilitar el almacenamiento en caché en Firefox durante el desarrollo

Haga esto solo en desarrollo. También necesita un mecanismo para forzar la recarga para producción, ya que sus usuarios usarán módulos antiguos invalidados de caché si actualiza su aplicación con frecuencia y no proporciona un mecanismo de sincronización de caché dedicado como los descritos en las respuestas anteriores.

Sí, esta información ya está en respuestas anteriores, pero aún necesitaba hacer una búsqueda en Google para encontrarla.

1
  • 1
    OP preguntó algo y respondió algo más. No se trata de forzar la carga en local sino en producción y no se puede pedir a los usuarios finales que sigan arriba para deshabilitar el caché, etc. 1/04/20 a las 13:39
3

Puede utilizar SRI para romper la caché del navegador. Solo tiene que actualizar su archivo index.html con el nuevo hash SRI cada vez. Cuando el navegador carga el HTML y descubre que el hash SRI en la página HTML no coincide con el de la versión en caché del recurso, volverá a cargar su recurso desde sus servidores. También viene con un buen efecto secundario de omitir el bloqueo de lectura de origen cruzado.

<script src="https://jessietessie.github.io/google-translate-token-generator/google_translate_token_generator.js" integrity="sha384-muTMBCWlaLhgTXLmflAEQVaaGwxYe1DYIf2fGdRkaAQeb4Usma/kqRWFWErr2BSi" crossorigin="anonymous"></script>
1
  • ¿Qué navegadores, incl. versiones, apoyan esto? Responda actualizando su respuesta (no aquí en los comentarios). 28 nov.20 a las 6:50