Evite que RequireJS almacene en caché los scripts necesarios

302

RequireJS parece hacer algo internamente que almacena en caché los archivos javascript necesarios. Si hago un cambio en uno de los archivos requeridos, tengo que cambiar el nombre del archivo para que se apliquen los cambios.

El truco común de agregar un número de versión como un parámetro de cadena de consulta al final del nombre de archivo no funciona con requirejs <script src="jsfile.js?v2"></script>

Lo que estoy buscando es una forma de evitar este almacenamiento en caché interno de los scripts requeridos RequireJS sin tener que cambiar el nombre de mis archivos de script cada vez que se actualizan.

Solución multiplataforma:

Ahora estoy usando urlArgs: "bust=" + (new Date()).getTime()para la eliminación automática de caché durante el desarrollo y urlArgs: "bust=v2"para la producción donde incremente el número de versión codificada después de implementar un script requerido actualizado.

Nota:

@Dustin Getz mencionó en una respuesta reciente que las Herramientas para desarrolladores de Chrome eliminarán los puntos de interrupción durante la depuración cuando los archivos Javascript se actualicen continuamente de esta manera. Una solución alternativa es escribir debugger;código para activar un punto de interrupción en la mayoría de los depuradores de Javascript.

Soluciones específicas del servidor:

Para obtener soluciones específicas que pueden funcionar mejor para el entorno de su servidor, como Node o Apache, consulte algunas de las respuestas a continuación.

BumbleB2na
fuente
¿Está seguro de que este no es el servidor o el cliente que está haciendo el almacenamiento en caché? (he estado usando js necesarios durante algunos meses y no he notado nada similar) IE fue sorprendido almacenando en caché los resultados de la acción MVC, Chrome estaba almacenando en caché nuestras plantillas html pero todos los archivos js parecen actualizarse cuando se reinicia el caché del navegador. Supongo que si estaba buscando hacer uso del almacenamiento en caché pero no puede hacer lo habitual porque las solicitudes de js requeridas estaban eliminando la cadena de consulta que podría causar el problema.
PJUK
No estoy seguro de si RequireJS elimina los números de versión adjuntos como ese. Puede haber sido mi servidor. Sin embargo, es interesante saber que RequireJS tiene una configuración de eliminación de caché, por lo que puede tener razón al eliminar mis números de versión adjuntos en los archivos requeridos.
BumbleB2na
actualicé mi respuesta con una posible solución de almacenamiento en caché
Dustin Getz el
Ahora puedo agregar lo siguiente a la letanía que publiqué en mi blog esta mañana: codrspace.com/dexygen/… Y es decir, no solo tengo que agregar el almacenamiento en caché, sino que require.js ignora una actualización completa.
Dexygen
Estoy confundido sobre el caso de uso de esto ... ¿Es esto para recargar en caliente los módulos AMD en el front-end o qué?
Alexander Mills

Respuestas:

457

RequireJS se puede configurar para agregar un valor a cada una de las URL de script para el almacenamiento en caché.

De la documentación de RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : argumentos de cadena de consulta adicionales agregados a las URL que RequireJS utiliza para obtener recursos. Lo más útil para el almacenamiento en caché cuando el navegador o el servidor no está configurado correctamente.

Ejemplo, agregando "v2" a todos los scripts:

require.config({
    urlArgs: "bust=v2"
});

Para fines de desarrollo, puede forzar a RequireJS a omitir el caché agregando una marca de tiempo:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});
Phil McCullick
fuente
46
Muy útil, gracias. Estoy usando urlArgs: "bust=" + (new Date()).getTime()para la eliminación automática de caché durante el desarrollo y urlArgs: "bust=v2"para la producción donde incremente el número de versión codificada después de implementar un script requerido actualizado.
BumbleB2na
99
... también como optimizador de rendimiento puede usar Math.random () en lugar de (new Date ()). getTime (). Es más bello, no crea objetos y es un poco más rápido jsperf.com/speedcomparison .
Vlad Tsepelev
2
Puede obtener un poco más pequeño:urlArgs: "bust=" + (+new Date)
mrzmyr
11
Creo que romper el caché cada vez es una idea terrible. Lamentablemente, RequireJS no ofrece otra alternativa. Usamos urlArgs pero no utilizamos una marca de tiempo o aleatoria para esto. En cambio, utilizamos nuestro Git SHA actual, de esa manera solo cambia cuando implementamos un nuevo código.
Ivan Torres
55
¿Cómo puede esta variante "v2" funcionar en producción, si el archivo que proporciona la cadena "v2" está en caché? Si lanzo una nueva aplicación en producción, agregar "v3" no va a hacer nada, ya que la aplicación sigue trabajando con los archivos v2 en caché, incluida la configuración anterior con v2 urlArgs.
Benny Bottema
54

¡No use urlArgs para esto!

Requerir cargas de script respeta los encabezados de almacenamiento en caché http. (Las secuencias de comandos se cargan con una inserción dinámica <script>, lo que significa que la solicitud se parece a cualquier activo antiguo que se está cargando).

Sirva sus activos de JavaScript con los encabezados HTTP adecuados para deshabilitar el almacenamiento en caché durante el desarrollo.

El uso de urlArgs de require significa que los puntos de interrupción que establezca no se conservarán en las actualizaciones; terminas necesitando poner debuggerdeclaraciones en todas partes en tu código. Malo. Lo uso urlArgspara activos que destruyen la memoria caché durante las actualizaciones de producción con el git sha; entonces puedo configurar mis activos para que se almacenen en caché para siempre y garantizar que nunca tengan activos obsoletos.

En el desarrollo, me burlo de todas las solicitudes de ajax con una configuración compleja de mockjax , luego puedo servir mi aplicación en modo solo javascript con un servidor http python de 10 líneas con todo el almacenamiento en caché desactivado . Esto se ha ampliado para mí a una aplicación "empresarial" bastante grande con cientos de puntos finales de servicio web relajantes. Incluso tenemos un diseñador contratado que puede trabajar con nuestra base de código de producción real sin darle acceso a nuestro código de back-end.

Dustin Getz
fuente
8
@ JamesP.Wright, porque (al menos en Chrome) cuando establece un punto de interrupción para algo que sucede en la carga de la página, luego hace clic en actualizar, el punto de interrupción no se alcanza porque la URL ha cambiado y Chrome ha eliminado el punto de interrupción. Me encantaría conocer una solución solo para el cliente para esto.
Drew Noakes el
1
Gracias Dustin Si encuentra una forma de evitar esto, publique. Mientras tanto, puede usar debugger;en su código donde quiera que persista un punto de interrupción.
BumbleB2na
2
Para cualquiera que use el servidor http en el nodo (npm instale el servidor http). También puede deshabilitar el almacenamiento en caché con -c-1 (es decir, http-server -c-1).
Yourpalal
55
Puede deshabilitar el almacenamiento en caché en Chrome para solucionar el problema de depuración durante el desarrollo: stackoverflow.com/questions/5690269/…
Deepak Joy
55
¡¡¡+1 a !!! NO UTILICE urlArgs EN LA PRODUCCIÓN !!! . Imagine el caso de que su sitio web tiene 1000 archivos JS (¡sí, es posible!) Y su carga es controlada por requiredJS. ¡Ahora lanza v2 o su sitio donde solo se cambian algunos archivos JS! pero al agregar urlArgs = v2, ¡obligas a recargar los 1000 archivos JS! ¡Pagarás mucho tráfico! solo los archivos modificados deben volver a cargarse, todos los demás deben responder con el estado 304 (No modificado).
walv
24

La solución urlArgs tiene problemas. Lamentablemente, no puede controlar todos los servidores proxy que puedan estar entre usted y el navegador web de su usuario. Lamentablemente, algunos de estos servidores proxy se pueden configurar para ignorar los parámetros de URL al almacenar en caché los archivos. Si esto sucede, se le entregará a su usuario una versión incorrecta de su archivo JS.

Finalmente me di por vencido e implementé mi propia solución directamente en require.js. Si está dispuesto a modificar su versión de la biblioteca requirejs, esta solución podría funcionar para usted.

Puedes ver el parche aquí:

https://github.com/jbcpollak/requirejs/commit/589ee0cdfe6f719cd761eee631ce68eee09a5a67

Una vez agregado, puede hacer algo como esto en su configuración requerida:

var require = {
    baseUrl: "/scripts/",
    cacheSuffix: ".buildNumber"
}

Utilice su sistema de compilación o entorno de servidor para reemplazarlo buildNumbercon una identificación de revisión / versión de software / color favorito.

Usando requiere así:

require(["myModule"], function() {
    // no-op;
});

Será necesario solicitar este archivo:

http://yourserver.com/scripts/myModule.buildNumber.js

En nuestro entorno de servidor, usamos reglas de reescritura de URL para eliminar el número de compilación y servir el archivo JS correcto. De esta manera, no tenemos que preocuparnos por renombrar todos nuestros archivos JS.

El parche ignorará cualquier script que especifique un protocolo, y no afectará ningún archivo que no sea JS.

Esto funciona bien para mi entorno, pero me doy cuenta de que algunos usuarios preferirían un prefijo en lugar de un sufijo, debería ser fácil modificar mi confirmación para satisfacer sus necesidades.

Actualizar:

En la discusión de solicitud de extracción, el autor requirejs sugiere que esto podría funcionar como una solución para prefijar el número de revisión:

var require = {
    baseUrl: "/scripts/buildNumber."
};

No he intentado esto, pero la implicación es que solicitaría la siguiente URL:

http://yourserver.com/scripts/buildNumber.myModule.js

Lo que podría funcionar muy bien para muchas personas que pueden usar un prefijo.

Aquí hay algunas posibles preguntas duplicadas:

RequireJS y almacenamiento en caché de proxy

require.js - ¿Cómo puedo configurar una versión en los módulos requeridos como parte de la URL?

JBCP
fuente
1
Realmente me gustaría ver que su actualización llegue a la versión oficial de requirejs. El autor principal de requirejs también podría estar interesado (ver la respuesta de @ Louis arriba).
BumbleB2na
@ BumbleB2na: siéntase libre de comentar sobre PullRequest ( github.com/jrburke/requirejs/pull/1017 ), jrburke no parecía interesado. Él sugiere una solución usando un prefijo de nombre de archivo, actualizaré mi respuesta para incluir eso.
JBCP
1
Buena actualización Creo que me gusta esta sugerencia por el autor, pero eso es sólo porque he estado usando esta convención ultimamente: /scripts/myLib/v1.1/. Intenté agregar postfix (o prefijo) a mis nombres de archivo, probablemente porque eso es lo que hace jquery, pero después de un tiempo [me volví flojo y] comencé a incrementar un número de versión en la carpeta principal. Creo que me ha facilitado el mantenimiento en un sitio web grande, pero ahora me tiene preocupado por las pesadillas de reescritura de URL.
BumbleB2na
1
Los sistemas front-end modernos simplemente reescriben los archivos JS y con una suma MD5 en el nombre del archivo y luego reescriben los archivos HTML para usar los nuevos nombres de archivo al compilar, pero eso se vuelve complicado con los sistemas heredados donde el lado del servidor sirve el código front-end.
JBCP
¿funciona esto cuando necesito algunos js dentro del archivo jspx?, así<script data-main="${pageContext.request.contextPath}/resources/scripts/main" src="${pageContext.request.contextPath}/resources/scripts/require.js"> <jsp:text/> </script> <script> require([ 'dev/module' ]); </script>
masT
19

Inspirado por Expire cache en require.js data-main , actualizamos nuestro script de implementación con la siguiente tarea ant:

<target name="deployWebsite">
    <untar src="${temp.dir}/website.tar.gz" dest="${website.dir}" compression="gzip" />       
    <!-- fetch latest buildNumber from build agent -->
    <replace file="${website.dir}/js/main.js" token="@Revision@" value="${buildNumber}" />
</target>

Donde se ve el comienzo de main.js:

require.config({
    baseUrl: '/js',
    urlArgs: 'bust=@Revision@',
    ...
});
dvtoever
fuente
11

En producción

urlArgs puede causar problemas!

El autor principal de requirejs prefiere no usarurlArgs :

Para los activos desplegados, prefiero poner la versión o hash para toda la compilación como un directorio de compilación, luego simplemente modifique la baseUrlconfiguración utilizada para el proyecto para usar ese directorio versionado como baseUrl. Entonces, ningún otro archivo cambia, y ayuda a evitar algunos problemas de proxy en los que no pueden almacenar en caché una URL con una cadena de consulta.

[Peinando el mío.]

Sigo este consejo.

En desarrollo

Prefiero utilizar un servidor que almacena en caché de manera inteligente los archivos que pueden cambiar con frecuencia: un servidor que emite Last-Modifiedy responde If-Modified-Sincecon 304 cuando corresponde. Incluso un servidor basado en el conjunto rápido de Node para servir archivos estáticos lo hace de inmediato. No requiere hacer nada a mi navegador y no arruina los puntos de interrupción.

Louis
fuente
Buenos puntos, pero su respuesta es específica para su entorno de servidor. Quizás una buena alternativa para cualquier otra persona que se encuentre con esto es la reciente recomendación de agregar un número de versión al nombre de archivo en lugar de un parámetro de cadena de consulta. Aquí hay más información sobre ese tema: stevesouders.com/blog/2008/08/23/…
BumbleB2na
Nos encontramos con este problema específico en un sistema de producción. Recomiendo guardar sus nombres de archivo en lugar de usar un parámetro.
JBCP
7

Tomé este fragmento de AskApache y lo puse en un archivo .conf separado de mi servidor web Apache local (en mi caso /etc/apache2/others/preventcaching.conf):

<FilesMatch "\.(html|htm|js|css)$">
FileETag None
<ifModule mod_headers.c>
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
</ifModule>
</FilesMatch>

Para el desarrollo, esto funciona bien sin necesidad de cambiar el código. En cuanto a la producción, podría usar el enfoque de @ dvtoever.

mirra
fuente
6

Solución rápida para el desarrollo

Para el desarrollo, puede deshabilitar el caché en Chrome Dev Tools ( deshabilitar el caché de Chrome para el desarrollo del sitio web ). La desactivación de la memoria caché ocurre solo si el cuadro de diálogo de herramientas de desarrollo está abierto, por lo que no debe preocuparse por alternar esta opción cada vez que navega regularmente.

Nota: El uso de ' urlArgs ' es la solución adecuada en producción para que los usuarios obtengan el código más reciente. Pero dificulta la depuración porque Chrome invalida los puntos de interrupción con cada actualización (porque es un archivo 'nuevo' que se sirve cada vez).

Deepak Joy
fuente
3

No recomiendo usar ' urlArgs ' para la explosión de caché con RequireJS. Como esto no resuelve el problema por completo. La actualización de una versión no dará como resultado la descarga de todos los recursos, aunque solo haya cambiado un solo recurso.

Para manejar este problema, recomiendo usar módulos Grunt como 'filerev' para crear la revisión no. Además de esto, he escrito una tarea personalizada en Gruntfile para actualizar la revisión no donde sea necesario.

Si es necesario, puedo compartir el fragmento de código para esta tarea.

Amit Sagar
fuente
Utilizo una combinación de grunt-filerev y grunt-cache-buster para reescribir archivos Javascript.
Ian Jamieson
2

Así es como lo hago en Django / Flask (se puede adaptar fácilmente a otros idiomas / sistemas VCS):

En su config.py(uso esto en python3, por lo que es posible que deba modificar la codificación en python2)

import subprocess
GIT_HASH = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8')

Luego en su plantilla:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • No requiere proceso de construcción manual
  • Solo se ejecuta git rev-parse HEADuna vez cuando se inicia la aplicación y la almacena en el configobjeto
Stephen Fuhry
fuente
0

Solución dinámica (sin urlArgs)

Hay una solución simple para este problema, para que pueda cargar un número de revisión único para cada módulo.

Puede guardar la función requirejs.load original, sobrescribirla con su propia función y analizar nuevamente su url modificada a la requirejs.load original:

var load = requirejs.load;
requirejs.load = function (context, moduleId, url) {
    url += "?v=" + oRevision[moduleId];
    load(context, moduleId, url);
};

En nuestro proceso de construcción usé "gulp-rev" para construir un archivo de manifiesto con todas las revisiones de todos los módulos que se están utilizando. Versión simplificada de mi tarea de trago:

gulp.task('gulp-revision', function() {
    var sManifestFileName = 'revision.js';

    return gulp.src(aGulpPaths)
        .pipe(rev())
        .pipe(rev.manifest(sManifestFileName, {
        transformer: {
            stringify: function(a) {
                var oAssetHashes = {};

                for(var k in a) {
                    var key = (k.substr(0, k.length - 3));

                    var sHash = a[k].substr(a[k].indexOf(".") - 10, 10);
                    oAssetHashes[key] = sHash;
                }

                return "define([], function() { return " + JSON.stringify(oAssetHashes) + "; });"
            }
        }
    }))
    .pipe(gulp.dest('./'));
});

esto generará un módulo AMD con números de revisión para moduleNames, que se incluye como 'oRevision' en main.js, donde sobrescribe la función requirejs.load como se mostró anteriormente.

estera
fuente
-1

Esto se suma a la respuesta aceptada de @phil mccull.

Utilizo su método, pero también automatizo el proceso creando una plantilla T4 para ejecutarla antes de la compilación.

Comandos precompilados:

set textTemplatingPath="%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
if %textTemplatingPath%=="\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe" set textTemplatingPath="%CommonProgramFiles%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
%textTemplatingPath% "$(ProjectDir)CacheBuster.tt"

ingrese la descripción de la imagen aquí

Plantilla T4:

ingrese la descripción de la imagen aquí

Archivo generado: ingrese la descripción de la imagen aquí

Almacenar en variable antes de cargar require.config.js: ingrese la descripción de la imagen aquí

Referencia en require.config.js:

ingrese la descripción de la imagen aquí

Pintor de Zach
fuente
2
Probablemente porque su solución a un problema de JavaScript es un montón de código C #. Esto también es un montón de trabajo extra y código para hacer algo que finalmente se hace exactamente de la misma manera que la respuesta aceptada.
mAAdhaTTah
@mAAdhaTTah Puede hacer esto con cualquier lenguaje del lado del servidor ... no tiene que ser c #. Además, esto automatiza el proceso, actualizando el busto de caché cuando crea una nueva versión del proyecto asegurando que el cliente siempre esté almacenando en caché la última versión de los scripts. No creo que merezca una rebaja negativa. Simplemente está extendiendo la respuesta al ofrecer un enfoque automatizado a la solución.
Pintor de Zach
Básicamente, creé esto porque cuando mantenía una aplicación que usa require.js, me resultaba bastante molesto tener que comentar manualmente "(new Date ()). GetTime ()) y descomentar el cachebuster estático cada vez que actualizaba la aplicación . Fácil de olvidar de repente, el cliente está verificando los cambios y ver la escritura en caché por lo que creo que nada ha cambiado .. Todo, ya que simplemente se olvidó de cambiar el cachebuster Este poco de código extra borra la posibilidad de que eso ocurra...
Pintor Zach
2
No lo marqué, no sé qué sucedió realmente. Solo estoy sugiriendo que el código que no solo no es js, sino un lenguaje de plantillas C #, no será tan útil para los desarrolladores de JS que intentan obtener una respuesta a su problema.
mAAdhaTTah
-2

En mi caso, quería cargar el mismo formulario cada vez que hago clic, no quería que se quedaran los cambios que hice en el archivo. Puede que no sea relevante para esta publicación exactamente, pero esta podría ser una solución potencial en el lado del cliente sin configurar config para require. En lugar de enviar el contenido directamente, puede hacer una copia del archivo requerido y mantener intacto el archivo real.

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
Mahib
fuente