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




no cache javascript (20)

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

Me he dado cuenta de 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 lleva a 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 volver a cargar 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 solución mejor y dejaré que sus votos decidan.

Actualizar:

Después de permitir la 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-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 para usar automáticamente la marca de tiempo como una cadena de consulta falsa fue enviada por pi ). Sin embargo, existe cierta discusión sobre si el navegador almacenará o no un archivo con una cadena de consulta. (Recuerde que queremos que el navegador almacene 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.


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

Por lo general, tengo este 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, es difícil que el usuario lo note.

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 quiere admitir versiones anteriores de IE.


El complemento mod_pagespeed de Google para apache se encargará automáticamente de la versión. Es realmente resbaladizo.

Analiza HTML en su salida del servidor web (funciona con PHP, rieles, python, HTML estático, cualquier cosa) y vuelve a escribir 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. Cuando los archivos cambian, automáticamente cambia las URL para que el navegador tenga que recuperarlos. Básicamente simplemente funciona, sin ningún cambio en su código. Incluso se reducirá su código en la salida también.


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

<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'))}}">

Gracias a Kip por su solución perfecta!

Lo extendí para usarlo como Zend_view_Helper. Como mi cliente ejecutó su página en un servidor virtual, también lo 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://.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.



Las más o menos 30 respuestas existentes son un gran consejo para un sitio web de circa 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 servidor web. expediente.

Imagina que eres un usuario que tiene la versión M de un SPA cargada en tu 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 XHR al servidor para obtener /some.template
    • (Su navegador no ha actualizado la página, por lo que aún 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 de /some.template cambió entre las versiones M y N (o se cambió el nombre del archivo o lo que sea), es probable que 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 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 asume cosas (que pueden cambiar en futuras versiones) sobre el contenido del recurso

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

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

El ú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 etiqueta <base> y cambiar la versión actual en un solo lugar.

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


No estoy seguro de por qué están teniendo tanto dolor al 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 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.


Para ASP.NET 4.5 y versiones posteriores, puede utilizar la agrupación de scripts .

La solicitud es http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 AllMyScripts. 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 cualquier archivo en el paquete cambia, el marco de optimización ASP.NET generará un nuevo token, garantizando que las solicitudes del paquete para el navegador obtendrán el paquete más reciente.

Hay otros beneficios de la agrupación que incluyen un mayor rendimiento en la primera carga de páginas con minificación.


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

Js o archivos css incluidos por tal forma:

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

Puede forzar un "almacenamiento en caché de toda la sesión" si agrega el id de sesión como un parámetro espumoso 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 algún 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>

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

example.css?randomNo=Math.random()

Actualización: 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: Incorporación de comentarios de Nick Johnson de que la expresión regular .htaccess puede causar problemas con archivos como json-1.3.js . La solución es solo volver a escribir 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 11/20/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ámbiala desde 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á 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, favicons y JavaScript. Básicamente todo lo que no se genera dinámicamente.


Técnica simple del lado del cliente

En general, el almacenamiento en caché es bueno ... Así que hay un par de técnicas, dependiendo de si está solucionando el problema por sí mismo a medida que 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 desarrolla 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 una red de Google o hi5), es menos probable que tengan 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 guarde un cambio en su versión de desarrollo. Eso sería tedioso.

Así que 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 crear una versión de un recurso, pero para un sitio web simple puede ser innecesario. Y recuerda, el almacenamiento en caché es algo bueno.

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

Lo creas o no, en realidad es tu servidor el que hace que la memoria caché del navegador sea tan persistente. Podría ajustar la configuración de su servidor y cambiar los encabezados de CADUCOS, 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, por lo general, desea establecer esa fecha en el futuro (un "Encabezado que vence en el 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 sobre 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.


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 para desarrolladores abiertas, simplemente haga clic 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 deseas!


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

Los navegadores deben saber qué almacenar en caché y qué no guardar en caché leyendo la respuesta de los servidores 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étera.

Si las cosas están configuradas 'correctamente', solo actualizar los archivos de su aplicación debería (en algún momento) actualizar los cachés de los navegadores. Por ejemplo, puede configurar su servidor web para decirle al navegador que nunca almacene 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


Estoy agregando esta respuesta como una respuesta específica de SilverStripe http://www.silverstripe.org que estaba buscando y que nunca encontré, pero he encontrado en la lectura: http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110

Esperamos que esto ayude a alguien a usar una plantilla SilverStripe y tratar de forzar la recarga de una imagen en caché en cada visita / actualización de la página. En mi caso, es una animación gif que solo se reproduce una vez y, por lo tanto, no se reproduce después de que se almacenó en caché. En mi plantilla simplemente agregué:

?$Now.Format(dmYHis)

hasta el final de la ruta del archivo para crear una marca de tiempo única y forzar al navegador a tratarla como un nuevo archivo.


Google Chrome tiene la opción Recargar duro , así como la opción Vaciar caché y Recargar duro . Puede hacer clic y mantener presionado el botón de recargar (en el modo Inspección) para seleccionar uno.


Puse un hash MD5 del contenido del archivo en su URL. De esa manera puedo establecer una fecha de vencimiento muy larga, y no tengo que preocuparme por los usuarios que tienen JS o CSS antiguos.

También calculo esto una vez por archivo en tiempo de ejecución (o en los cambios del sistema de archivos) para que no haya nada divertido que hacer en el momento del diseño o durante el proceso de compilación.

Si está utilizando ASP.NET MVC, puede consultar el código en mi otra respuesta aquí .


Recientemente resolví esto usando Python. Aquí el código (debería ser fácil de adoptar a 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 tendrá que cambiar su html nuevamente, al tocar el archivo CSS se activará automáticamente una invalidación de caché. Funciona muy bien y la sobrecarga no se nota.





auto-versioning