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

993

He notado que algunos navegadores (en particular, Firefox y Opera) son muy entusiastas al usar 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 utilizando la copia en caché.

La pregunta es: ¿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. Publicaré mi propia solución como respuesta, pero tengo curiosidad por saber si alguien tiene una mejor solución y dejaré que sus votos decidan.

Actualización:

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

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. (Algún código de Python para usar automáticamente la marca de tiempo como una cadena de consulta falsa fue enviada por pi ). Sin embargo, se debate si el navegador guardará o no en caché 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 nuevamente cuando haya cambiado).

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

Pmpr
fuente
Tengo esto en mi .htaccess, y nunca ningún problema con los archivos almacenados en caché: ExpiresActive On ExpiresDefault "modification".
Frank Conijn
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 lo mismo de manera efectiva utilizando 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
2
Realmente aprecio la forma en que se hizo esta pregunta y se ha actualizado desde entonces. Describía completamente lo que debería esperar en las respuestas. Voy a seguir este enfoque en mis preguntas de ahora en adelante. ¡Salud!
rd22

Respuestas:

455

Actualización: reescrita 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: Incorporación de comentarios de Nick Johnson de que 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. (Debido a que 10 dígitos cubren 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 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 de 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 verá esto 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 todo lo que no se genera dinámicamente.

Dormir
fuente
16
Mi propio servidor de contenido estático hace exactamente lo mismo, excepto que uso un parámetro para versionar (base.css? V = 1221534296) en lugar de un cambio de nombre de archivo (base.1221534296.css). Sin embargo, sospecho que tu camino puede ser un poco más eficiente. Muy genial.
Jens Roland
44
@Kip: solución muy hábil. Obviamente, la reescritura de URL tiene mucho más que ofrecer que solo url bastante bonitas.
James P.
37
Veo un problema con esto, que accede al sistema de archivos muchas veces, exactamente, número de enlaces * número de solicitudes / segundo ... que puede o no ser un problema para usted.
Tomáš Fejfar
3
@AlixAxel: No, los navegadores volverán a buscarlo cuando cambie el parámetro, pero algunos servidores proxy 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 mod_rewrite es minúscula en comparación con cualquier otro cuello de botella de rendimiento en WPO
Jens Roland
8
¿Es file_existsrealmente necesario el primer cheque? filemtimedevolverá falso en caso de falla, entonces, ¿por qué no simplemente asignar el valor de tiempo de archivo 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 sumaría.
Gavin
184

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 mientras desarrolla un sitio web, o si está tratando de controlar el almacenamiento en caché en un entorno de producción.

Los visitantes generales de su sitio web no tendrán la misma experiencia que tienen cuando desarrollan 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 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 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 para 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 para mantener los archivos en caché. Los navegadores tienen políticas para este tipo de cosas, y generalmente se rigen por las reglas establecidas en la especificación HTTP. Cuando un navegador realiza una solicitud a un servidor, parte de la respuesta es un encabezado CADUCADO ... una fecha que le indica 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 memoria caché y buscará la fecha de CADUCIDAD para decidir si debe usarse.

Entonces, lo creas o no, en realidad es tu servidor el que está haciendo que el caché del navegador sea tan persistente. Puede ajustar la configuración de su servidor y cambiar los encabezados de CADUCES, pero la pequeña técnica que he escrito anteriormente es probablemente una forma mucho más sencilla de hacerlo. Dado que el almacenamiento en caché es bueno, generalmente desea establecer esa fecha en el futuro (un "Encabezado de caducidad de 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.

keparo
fuente
3
El truco rápido de generar una cadena de consulta con Javascript funciona muy bien durante el desarrollo activo. Hice lo mismo con PHP.
Alan Turing
2
Esta es la forma más fácil 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 carga la página. Este método todavía permite el almacenamiento en caché hasta que realmente cambie el archivo y realmente quiera forzar la recarga.
scott80109
@keparo, tengo un número amplio de jquery en todas las páginas, si voy a cambiar esto manualmente, tomará un mes. Si me pueden ayudar a resolverlo todo sin codificar cada página.
galleta
1
Esto no parece funcionar para mi CSS cuando uso:<link href='myCss.css?dev=14141'...>
Noumenon
3
Esta no es una solución viable. Un buen número de navegadores simplemente se negará a almacenar en caché cualquier cosa que tenga una cadena de consulta. Esta es la razón por la cual Google, GTMetrix y herramientas similares levantarán una bandera si tiene cadenas de consulta en referencias a contenido estático. Si bien es sin duda una solución decente para el desarrollo, no es absolutamente 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 (y a menudo no). Los dispositivos móviles son un excelente ejemplo de esto.
Nate I
113

El complemento mod_pagespeed de Google para apache hará el auto versionado por usted. Es muy hábil.

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 imagen para que incluyan un código de identificación. Sirve los archivos en las URL modificadas con un control de caché muy largo en ellos. Cuando los archivos cambian, cambia automáticamente las URL para que el navegador tenga que volver a buscarlas. Básicamente funciona, sin ningún cambio en su código. Incluso minificará tu código al salir también.

Leopd
fuente
1
Eso es genial, pero aún en beta. ¿Se puede utilizar para el servicio empresarial?
Sanghyun Lee
26
Esto es INCORRECTO (juguetear automáticamente con la fuente) cuando es claramente un problema del navegador. Danos (a los desarrolladores) una actualización real de borrado de cerebro: <ctrl> + F5
T4NK3R
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 compilación limpia es borrar el caché de mod_pagespeed : code.google.com/p/modpagespeed/wiki/… ?
Leopd
3
@ T4NK3R mod_pagespeed no tiene que hacer nada con su fuente para administrar la memoria caché, simplemente se mencionó que puede ayudar con cosas como la minificación. En cuanto a si es o no "INCORRECTO", es completamente subjetivo. Puede estar mal para ti, pero eso no significa que sea instintivamente malo .
Madbreaks
2
También funciona con nginx, aunque debes compilarlo desde la fuente: developers.google.com/speed/pagespeed/module/…
Rohit
93

En lugar de cambiar la versión manualmente, le 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 podría 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 ha cambiado.

Luego puede escribir un script de shell simple que calcule el hash del archivo y actualice 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.

levik
fuente
1
desafortunadamente no sé cómo implementarlo. Consejo por favor ... más detalles ...
Michael Phelps
Una implementación en shell, ruby, etc. sería genial
Peter
3
Muy buena solución ... pero creo que consume muchos recursos calcular el hash del archivo en cada solicitud de archivo (css, js, images, html..etc) para cada visita a la página.
DeepBlue
Esta es una solución estándar para aquellos que usan js o css bundling con gulp, grunt o webpack, la implementación difiere para cada solución, pero el hash de sus archivos como un paso de compilación es común y sugerido para las aplicaciones modernas agrupadas
Brandon Søren Culley
@DeepBlue - la respuesta dice "ejecuta ese script cada vez que CSS cambie" . Eso NO está en cada visita a la página. OTOH La respuesta omite los detalles principales: ¿cómo el hash modificado se convierte en parte de la URL? No sé ...
ToolmakerSteve
71

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

Todo lo que debe 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 href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

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

Phantom007
fuente
Solo puedes usar mycss.css?1234567890.
Gavin
3
muy elegante, aunque lo he modificado ligeramente <link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>, en caso de que algunos de los argumentos en este hilo sobre el almacenamiento en caché de URL con variables GET (en el formato sugerido) sean correctos
luke_mclachlan
Además de mi último comentario, he visto que WordPress usa, ¡ ?ver=quién sabe!
luke_mclachlan
Gran solución Además, descubrí que filemtime no funcionaba para un nombre de dominio completo (FQDN), así que utilicé 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')?>" />
rrtx2000
Muchas gracias. Simple y bueno Aquí está en Python: progpath = os.path.dirname (sys.argv [0]) def versionize (archivo): timestamp = os.path.getmtime ('% s /../ web /% s'% (progpath , archivo)) return '% s? v =% s'% (archivo, marca de tiempo) print <link href = "% s" rel = "stylesheet" '' type = "text / css" /> '\% versionize ( 'css / main.css')
dlink
52

Simplemente puede colocar ?foo=1234al final de su importación css / js, cambiando 1234 para que sea lo que quiera. Eche un vistazo a la fuente SO html para ver un ejemplo.

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


Nota: Hay algún argumento con respecto a cómo esto afecta exactamente el almacenamiento en caché. Creo que la esencia general de esto es que las solicitudes GET, con o sin parámetros, deben ser almacenables 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 al navegador que usa el usuario, ya que de todos modos puede seguir adelante y pedir una versión nueva de todos modos.

SCdF
fuente
Disparates. La cadena de consulta (también conocida como parámetros GET) son parte de la URL. Pueden y serán almacenados en caché. Esta es una buena solución.
troelskn
99
@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 frescas a menos que el servidor proporcione un tiempo de vencimiento explícito. Ver w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
Michael Johnson el
44
Probé el tipo de versión de cadena de consulta con todos los principales navegadores y SI almacenan en caché el archivo, 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 caché NO almacene en caché el archivo.
Tomas Andrle
34
Vale la pena señalar, por cualquier razón, que Stackoverflow utiliza el método de cadena de consulta.
Jason
2
Ha verificado que el uso del parámetro? = No hará que los navegadores vuelvan a buscar el archivo en caché cuando cambie el parámetro. La única manera es cambiar el nombre del archivo programáticamente en el extremo del servidor como respondió Kip
arunskrish
41

He escuchado esto llamado "auto versionado". El método más común es incluir el tiempo estático del archivo estático en algún lugar de la URL y eliminarlo utilizando controladores de reescritura o confs de URL:

Ver también:

John Millikin
fuente
3
Gracias, supongo 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
27

Las aproximadamente 30 respuestas existentes son excelentes consejos 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 algunos supuestos 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 canal de CD implementa la nueva versión N de la aplicación en el servidor
  2. Usted 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 M )
  3. El servidor responde con el contenido de /some.template: ¿desea que devuelva la versión M o N de la plantilla?

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

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

  • Los recursos se solicitan de forma asíncrona en algún momento después de la carga de la página inicial
  • La lógica de la aplicación supone cosas (que pueden cambiar en futuras versiones) sobre el contenido de los recursos

Una vez que su aplicación necesita servir 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. Establecer encabezados HTTP para permitir que los navegadores almacenen en caché los archivos para siempre
    • (O mejor aún, poner todo en un CDN)
  3. Actualice todo <script>y <link>etiquetas, etc. para apuntar a ese archivo en uno de los directorios versionados

Ese último paso parece 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 puede 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 en aras de permitir que se completen las operaciones en progreso, aún puede ser más fácil admitir al menos dos versiones en paralelo: v-current y v-previous.

Michael Kropat
fuente
Michael: tu comentario es muy relevante. Aquí acabo de tratar 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 de blog y una respuesta a esta pregunta (incluido el código). Gracias por los consejos
statler
Gran comentario No puedo entender mientras la gente sigue hablando sobre el almacenamiento en caché y el almacenamiento en caché HTTP como la solución real a los problemas de almacenamiento en caché de los sitios web sin mencionar los nuevos problemas de las SPA, como si este fuera un caso marginal.
David Casillas
1
¡Excelente respuesta y estrategia absolutamente ideal! ¡Y puntos de bonificación por mencionar la baseetiqueta! En cuanto a admitir código antiguo: esto no siempre es una posibilidad, ni tampoco es una buena idea. Las nuevas versiones de código pueden admitir cambios de última hora en otras partes de una aplicación o pueden implicar soluciones de emergencia, parches de vulnerabilidad, etc. Todavía tengo que implementar esta estrategia yo mismo, pero siempre he pensado 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 desautorizar forzosamente todas las sesiones a través de WebSockets )
Jonny Asmar
Es bueno ver una respuesta bien pensada con respecto a las aplicaciones de una sola página.
Nate I
Eso es "implementación azul-verde" si desea buscar más información.
Fil
15

¡No use foo.css? Version = 1! Se supone que los navegadores no almacenan en caché las URL con variables GET. Según 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 las reglas de reescritura para eliminar el número de versión.

airrob
fuente
1
En primer lugar, los navegadores no almacenan en caché, esa 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 con una cadena de consulta?
AnthonyWJones
13
Un navegador web que incluye la funcionalidad de almacenamiento en caché de objetos (verifique el directorio de caché de su navegador). HTTP es un protocolo que incluye directivas de servidores a clientes (proxies, navegadores, arañas, etc.) que sugieren el control de caché.
tzot
13

En Laravel (PHP) podemos hacerlo de la siguiente manera clara y elegante (usando la marca de tiempo de modificación de 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'))}}">

Ejemplo de salida html ( filemtimetiempo de retorno como una marca de tiempo Unix )

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
revs Kamil Kiełczewski
fuente
¿Cuál es el resultado 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
Gediminas
filemtime : "Esta función devuelve la hora en que se escribieron los bloques de datos de un archivo, es decir, la hora en que se cambió el contenido del archivo". src: php.net/manual/en/function.filemtime.php
Kamil Kiełczewski
11

RewriteRule necesita una pequeña actualización para los archivos js 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, así que .number. es ignorado

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Nick Johnson
fuente
2
¡Gracias por el aporte! Desde que escribí esta publicación también me he 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 del 9/9/2001 al 20/11/2286.) He actualizado mi respuesta para incluir esta expresión regular:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
Kip
Entiendo regex, pero no entiendo con qué problema estás resolviendo [^.]aquí. Además, no hay ningún beneficio en escribir \ddentro de una clase de caracteres: \d+hará lo mismo. Según lo publicado, 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 de archivo. No hay coincidencia para su entrada de muestra: regex101.com/r/RPGC62/1
mickmackusa
10

Para ASP.NET 4.5 y superior, puede usar la agrupación de scripts .

La solicitud http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81es para el paquete AllMyScripts y contiene un par de cadenas de consulta v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. La cadena de consulta v tiene un token de valor que es un identificador único utilizado para el almacenamiento en caché. Mientras el paquete no cambie, la aplicación ASP.NET solicitará el paquete AllMyScripts usando este token. Si algún archivo en el paquete cambia, el marco de optimización de ASP.NET generará un nuevo token, garantizando que las solicitudes del navegador para el paquete obtendrán el último paquete.

El paquete incluye otros beneficios, incluido un mayor rendimiento en la primera carga de páginas con minificación.

usuario3738893
fuente
Por favor, ayúdenme. No estoy haciendo ningún cambio en bundle.config solo cambiando en archivos css o js, ​​¿cómo puedo resolver el problema de almacenamiento en caché?
vedankita kumbhar
10

Aquí hay una solución de JavaScript puro

(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 lanzar un nuevo código, se usa location.reload(true)para forzar la actualización de la página del servidor.

Por lo general, tengo esto como el primer script dentro del, <head>así que se evalúa antes de que se cargue cualquier otro contenido. Si se necesita 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 mezcla si desea admitir versiones anteriores de IE.

Lloyd Banks
fuente
Intenté algo como esto, esto solo funcionará en la página recargada, pero si el sitio tiene varias páginas que comparten las mismas CSS / imágenes, otras páginas seguirán utilizando recursos antiguos.
DeepBlue
9

Publicación interesante Después de leer todas las respuestas aquí combinadas con el hecho de que nunca he tenido ningún problema con las cadenas de consulta "falsas" (que no estoy seguro de por qué todos son tan reacios a usar esto), creo 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 resultaría en 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?

Michiel
fuente
8

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

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

Con las herramientas de desarrollador abiertas, simplemente haga clic en el botón Actualizar y suéltelo una vez que pase el cursor sobre "Vaciar caché y recarga dura".

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

Frank Bryce
fuente
Y si está utilizando Chrome como su entorno de desarrollo, otra solución no invasiva es deshabilitar el caché: en la configuración, puede invalidar el caché del disco seleccionando 'Deshabilitar caché' (nota: DevTools debe estar visible / abierto para que esto funcione).
Velojet
7

¡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.

Espero que ayude a alguien más también.

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

}

Saludos y gracias.

lony
fuente
7

No he encontrado el enfoque DOM del lado del cliente que crea 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>
GreQ
fuente
6

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

revs ajithes111
fuente
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 enterrada en algún lugar de las herramientas de desarrollo (se me olvida la ubicación) para recargar con fuerza en cada recarga.
Jonny Asmar
5

Puede forzar un "almacenamiento en caché de toda la sesión" si agrega el ID de sesión como un parámetro espurio del archivo js / 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 un código para imprimir la fecha del archivo o similar. Si está utilizando 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>
helios
fuente
5

Digamos 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 información de versión, por ejemplo:

/v/1234/styles/screen.css

En mi humilde opinión, el segundo método es mejor para archivos CSS porque pueden referirse a imágenes usando URL relativas, lo que significa que si especifica un me background-imagegusta así:

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 Subversion / CVS / etc. revisión esto significa que se notarán cambios en las imágenes a las que se hace referencia en los archivos CSS. Eso no está garantizado con el primer esquema, es decir, la URL images/happy.gifrelativa /styles/screen.css?v=1235es la /styles/images/happy.gifque no contiene ninguna información de versión.

He implementado una solución de almacenamiento en caché utilizando esta técnica con servlets de Java y simplemente manejo las solicitudes /v/*con un servlet que delega en el 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 Configuré encabezados que dicen "caché para siempre".

Walter Rumsby
fuente
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
1
Usando el segundo método, un cambio a un CSS invalidará las copias en caché de todas las imágenes a las que se hace referencia con URL relativas, lo que puede o no ser deseable.
TomG
5

Simplemente puede agregar un número aleatorio con la URL de CSS / JS como

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

Para ASP.NET, supongo que la próxima solución con opciones avanzadas (modo de depuración / liberación, versiones):

Archivos Js o Css incluidos 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 calcula 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();
    }
    ...
}
Ivan Kochurkin
fuente
4

Recientemente resolví esto usando Python. Aquí 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 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) 

Este código básicamente 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 html, tocar el archivo CSS activará automáticamente una invalidación de caché. Funciona muy bien y los gastos generales no se notan.

Pi.
fuente
¿podría os.stat () crear un cuello de botella?
hoju
@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.
4

Si está utilizando git + PHP, puede volver a cargar el script desde la memoria caché cada vez que haya un cambio en el repositorio de git, utilizando 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;
readikus
fuente
4

Si usted es un desarrollador que busca evitar el almacenamiento en caché, la pestaña de red de Chrome tiene la opción de deshabilitar la memoria caché. De lo contrario, puede hacerlo sin un marco de representación del servidor utilizando dos etiquetas 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>
3 revoluciones
fuente
4

Esta pregunta es muy antigua y aparece a primera vista cuando alguien busca en Google este problema. Esta no es una respuesta a la pregunta de la forma en que op lo quiere, sino una respuesta a los desarrolladores con este problema durante el desarrollo y las pruebas. Y no puedo publicar una nueva pregunta sobre este tema, ya que se marcará como duplicado.

Como muchos otros, solo quería eliminar el almacenamiento en caché brevemente.

"keep caching consistent with the file" ... es demasiado complicado ...

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

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

Como tal, si usa Chrome Dev Tools , 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 los archivos en caché

Y si tiene problemas de 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 el desarrollo, también necesita un mecanismo para forzar la recarga para la 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 encima.

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

Esperemos que esta respuesta sea muy clara y ahora no sea necesario.

AIon
fuente
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 lo anterior para deshabilitar el caché, etc.
Jitendra Pancholi
3

Parece que todas las respuestas aquí sugieren algún tipo de versión en el esquema de nombres, que tiene sus desventajas.

Los navegadores deben saber qué almacenar en caché y qué no almacenar en caché al leer la respuesta de los servidores web, en particular los encabezados http, ¿por cuánto tiempo es válido este recurso? ¿Se actualizó este recurso desde la última vez que lo recuperé? etcétera

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

Una explicación más detallada de cómo funciona eso está aquí https://www.mnot.net/cache_docs/#WORK

Pico común
fuente
3

Simplemente agregue este código, donde desea realizar una recarga dura (obligar al navegador a volver a cargar archivos CSS / JS en caché). Haga esto dentro de .load para que no se actualice como un bucle

 $( window ).load(function() {
   location.reload(true);
});
Sandeep Ranjan
fuente
No funciona en Chrome. Todavía cargando activos desde el caché del disco
Jason Kim
3

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/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>    

Esto se puede simplificar para:

<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;
    }
}
mike
fuente
2

Sugiero implementar el siguiente proceso:

  • versione sus archivos css / js cada vez que implemente, algo como: screen.1233.css (el número puede ser su revisión SVN si usa un sistema de versiones)

  • minimízalos para optimizar los tiempos de carga

Dan Burzo
fuente