Impedisci a RequireJS di memorizzare nella cache gli script richiesti


302

RequireJS sembra fare qualcosa internamente che memorizza nella cache i file javascript richiesti. Se apporto una modifica a uno dei file richiesti, devo rinominare il file per poter applicare le modifiche.

Il trucco comune di aggiungere un numero di versione come parametro querystring alla fine del nome file non funziona con requirejs <script src="jsfile.js?v2"></script>

Quello che sto cercando è un modo per prevenire questo cache interno degli script richiesti da RequireJS senza dover rinominare i miei file di script ogni volta che vengono aggiornati.

Soluzione multipiattaforma:

Ora sto usando urlArgs: "bust=" + (new Date()).getTime()per il busting automatico della cache durante lo sviluppo e urlArgs: "bust=v2"per la produzione in cui incremento il numero di versione hardcoded dopo aver implementato uno script richiesto aggiornato.

Nota:

@Dustin Getz ha menzionato in una recente risposta che Chrome Developer Tools eliminerà i punti di interruzione durante il debug quando i file Javascript vengono costantemente aggiornati in questo modo. Una soluzione alternativa è scrivere debugger;nel codice per attivare un breakpoint nella maggior parte dei debugger Javascript.

Soluzioni specifiche per server:

Per soluzioni specifiche che potrebbero funzionare meglio per l'ambiente server come Node o Apache, vedere alcune delle risposte di seguito.


Sei sicuro che questo non sia il server o il client che sta eseguendo la memorizzazione nella cache? (usano js richiesti da alcuni mesi e non ho notato nulla di simile) IE è stato catturato memorizzando nella cache i risultati dell'azione MVC, Chrome stava memorizzando nella cache i nostri modelli html ma i file js sembrano aggiornarsi quando la cache del browser è stata ripristinata. Suppongo che stavi cercando di utilizzare la memorizzazione nella cache ma non puoi fare il solito perché le richieste da js richieste stavano rimuovendo la stringa di query che potrebbe causare il problema?
PJUK,

Non sono sicuro che RequireJS rimuova i numeri di versione aggiunti in questo modo. Potrebbe essere stato il mio server. Interessante come RequireJS abbia un'impostazione cache-buster, quindi potresti aver ragione rimuovendo i miei numeri di versione aggiunti sui file richiesti.
BumbleB2na,

ho aggiornato la mia risposta con una potenziale soluzione di memorizzazione nella cache
Dustin Getz,

Ora posso aggiungere quanto segue alla litania che ho pubblicato nel mio post sul blog questa mattina: codrspace.com/dexygen/… E cioè, non solo devo aggiungere il busting della cache, ma request.js ignora un aggiornamento intenso.
Dexygen,

Sono confuso sul caso d'uso di questo .... È per il caricamento a caldo dei moduli AMD nel front-end o cosa?
Alexander Mills,

Risposte:


457

RequireJS può essere configurato per aggiungere un valore a ciascuno degli URL di script per il busting della cache.

Dalla documentazione di RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : argomenti di stringa di query extra aggiunti agli URL utilizzati da RequireJS per recuperare le risorse. Utile soprattutto per il busting della cache quando il browser o il server non sono configurati correttamente.

Esempio, aggiungendo "v2" a tutti gli script:

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

Ai fini dello sviluppo, è possibile forzare RequireJS a bypassare la cache aggiungendo un timestamp:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});

46
Molto utile, grazie. Sto usando urlArgs: "bust=" + (new Date()).getTime()per il busting automatico della cache durante lo sviluppo e urlArgs: "bust=v2"per la produzione in cui incremento il numero di versione hardcoded dopo aver implementato uno script richiesto aggiornato.
BumbleB2na,

9
... anche come ottimizzatore delle prestazioni puoi usare Math.random () invece di (new Date ()). getTime (). È più bella, non crea oggetti e un po 'più veloce jsperf.com/speedcomparison .
Vlad Tsepelev,

2
Puoi ottenerlo un po 'più piccolo:urlArgs: "bust=" + (+new Date)
mrzmyr,

11
Penso che rompere la cache ogni volta sia un'idea terribile. Purtroppo RequireJS non offre un'altra alternativa. Usiamo urlArgs ma non usiamo un timestamp o casuale per questo. Invece usiamo il nostro attuale Git SHA, in questo modo cambia solo quando implementiamo un nuovo codice.
Ivan Torres,

5
Come può funzionare questa variante "v2" in produzione, se il file che fornisce la stringa "v2" stessa è memorizzato nella cache? Se rilascerò una nuova applicazione in produzione, l'aggiunta di "v3" non farà nulla, poiché l'applicazione continua a funzionare felicemente con i file v2 memorizzati nella cache, inclusa la vecchia configurazione con v2 urlArgs.
Benny Bottema,

54

Non usare urlArgs per questo!

I carichi di script richiesti devono essere rispettati nelle intestazioni di cache HTTP. (Gli script vengono caricati con un inserimento dinamico<script> , il che significa che la richiesta assomiglia a qualsiasi risorsa precedente che viene caricata.)

Servi le tue risorse javascript con le intestazioni HTTP appropriate per disabilitare la memorizzazione nella cache durante lo sviluppo.

L'uso di urlArgs di request significa che tutti i punti di interruzione impostati non verranno conservati durante gli aggiornamenti; finisci per dover inserire debuggerdichiarazioni ovunque nel tuo codice. Male. Io usourlArgs le risorse di busting della cache durante gli aggiornamenti di produzione con git sha; quindi posso impostare le mie risorse per essere memorizzate nella cache per sempre e avere la certezza di non avere risorse obsolete.

In fase di sviluppo, prendo in giro tutte le richieste Ajax con una complessa configurazione Mockjax , quindi posso servire la mia app in modalità solo javascript con un server http Python a 10 righe con tutta la cache disattivata . Questo è diventato per me un'applicazione "enterprise" piuttosto grande con centinaia di endpoint di servizi Web riposanti. Abbiamo anche un designer a contratto che può lavorare con la nostra vera base di codice di produzione senza dargli accesso al nostro codice di backend.


8
@ JamesP. Bene, perché (almeno in Chrome) quando imposti un breakpoint per qualcosa che accade al caricamento della pagina, quindi fai clic su aggiorna, il breakpoint non viene raggiunto perché l'URL è cambiato e Chrome ha lasciato cadere il breakpoint. Mi piacerebbe conoscere una soluzione solo per il cliente a questo.
Drew Noakes,

1
Grazie Dustin. Se trovi un modo per aggirare questo, ti preghiamo di pubblicare. Nel frattempo puoi usare il debugger;tuo codice ovunque tu voglia mantenere un breakpoint.
BumbleB2na,

2
Per chiunque utilizzi http-server sul nodo (npm installa http-server). Puoi anche disabilitare la memorizzazione nella cache con -c-1 (ovvero http-server -c-1).
Yourpalal,

5
È possibile disabilitare la memorizzazione nella cache in Chrome per aggirare il problema di debug durante lo sviluppo: stackoverflow.com/questions/5690269/…
Deepak Joy

5
Da +1 a !!! NON UTILIZZARE gli URL nella PRODUZIONE !!! . Immagina il caso in cui il tuo sito web abbia 1000 file JS (sì, possibile!) E il loro carico è controllato da richiestoJS. Ora rilasci v2 o il tuo sito in cui vengono modificati solo pochi file JS! ma aggiungendo urlArgs = v2, si forza a ricaricare tutti i 1000 file JS! pagherai molto traffico! solo i file modificati devono essere ricaricati, a tutti gli altri dovrebbe essere risposto con lo stato 304 (Non modificato).
walv,

24

La soluzione urlArgs presenta problemi. Sfortunatamente non puoi controllare tutti i server proxy che potrebbero essere tra te e il browser web dell'utente. Purtroppo alcuni di questi server proxy possono essere configurati per ignorare i parametri URL durante la memorizzazione nella cache dei file. In tal caso, verrà consegnata all'utente una versione errata del file JS.

Alla fine ho rinunciato e implementato la mia correzione direttamente in request.js. Se sei disposto a modificare la tua versione della libreria requestjs, questa soluzione potrebbe funzionare per te.

Puoi vedere la patch qui:

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

Una volta aggiunto, puoi fare qualcosa del genere nella tua configurazione richiesta:

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

Usa il tuo sistema di build o l'ambiente server per sostituirlo buildNumbercon un ID revisione / versione software / colore preferito.

Utilizzando richiedono in questo modo:

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

Richiederà la richiesta di questo file:

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

Nel nostro ambiente server, utilizziamo le regole di riscrittura dell'URL per eliminare il buildNumber e servire il file JS corretto. In questo modo non dobbiamo preoccuparci di rinominare tutti i nostri file JS.

La patch ignorerà qualsiasi script che specifica un protocollo e non influenzerà alcun file non JS.

Questo funziona bene per il mio ambiente, ma mi rendo conto che alcuni utenti preferirebbero un prefisso piuttosto che un suffisso, dovrebbe essere facile modificare il mio commit in base alle proprie esigenze.

Aggiornare:

Nella discussione sulla richiesta pull, l'autore requirejs suggerisce che potrebbe funzionare come soluzione per aggiungere il prefisso al numero di revisione:

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

Non ho provato questo, ma l'implicazione è che questo richiederebbe il seguente URL:

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

Che potrebbe funzionare molto bene per molte persone che possono usare un prefisso.

Ecco alcune possibili domande duplicate:

Richiede JS e cache del proxy

require.js - Come posso impostare una versione sui moduli richiesti come parte dell'URL?


1
Mi piacerebbe davvero vedere il tuo aggiornamento farsi strada verso la build ufficiale del requestjs. Anche l'autore principale di requestjs potrebbe essere interessato (vedi la risposta di @ Louis sopra).
BumbleB2na,

@ BumbleB2na - non esitare a commentare il PullRequest ( github.com/jrburke/requirejs/pull/1017 ), jrburke non sembrava interessato. Suggerisce una soluzione usando un prefisso di nome file, aggiornerò la mia risposta per includerla.
JBCP

1
Bel aggiornamento. Penso che mi piace questo suggerimento da parte dell'autore, ma questo è solo perché ho usato questa convenzione di denominazione di recente: /scripts/myLib/v1.1/. Ho provato ad aggiungere postfix (o prefisso) ai miei nomi di file, probabilmente perché è quello che fa jquery, ma dopo un po 'sono diventato pigro e ho iniziato ad incrementare un numero di versione nella cartella principale. Penso che mi abbia semplificato la manutenzione su un grande sito Web, ma ora mi preoccupi degli incubi di riscrittura degli URL.
BumbleB2na,

1
I moderni sistemi front-end riscrivono semplicemente i file JS e con una somma MD5 nel nome del file, quindi riscrivono i file HTML per utilizzare i nuovi nomi di file durante la creazione, ma ciò diventa complicato con i sistemi legacy in cui il codice front-end è servito dal lato server.
JBCP,

funziona quando ho bisogno di js all'interno del file jspx ?, in questo modo<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

Ispirati dalla scadenza della cache su data-main request.js, abbiamo aggiornato il nostro script di distribuzione con il seguente task 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>

Dove appare l'inizio di main.js:

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

11

In produzione

urlArgs può causare problemi!

L'autore principale di requirejs preferisce non usareurlArgs :

Per le risorse distribuite, preferisco inserire la versione o l'hash per l'intera build come directory di build, quindi modificare semplicemente la baseUrlconfigurazione utilizzata per il progetto per utilizzare quella directory con versione come baseUrl. Quindi nessun altro file cambia e ciò aiuta ad evitare alcuni problemi del proxy in cui potrebbero non memorizzare nella cache un URL con una stringa di query su di esso.

[Styling mine.]

Seguo questo consiglio.

In sviluppo

Preferisco usare un server che memorizza nella cache in modo intelligente file che possono cambiare frequentemente: un server che emette Last-Modifiede risponde If-Modified-Sincecon 304 quando appropriato. Anche un server basato sul set espresso di Node per servire file statici lo fa subito. Non richiede di fare nulla per il mio browser e non rovina i punti di interruzione.


Aspetti positivi, ma la tua risposta è specifica per il tuo ambiente server. Forse una buona alternativa per chiunque si imbatta in questo è la recente raccomandazione per l'aggiunta di un numero di versione al nome del file invece di un parametro querystring. Ecco maggiori informazioni sull'argomento: stevesouders.com/blog/2008/08/23/…
BumbleB2na

Stiamo affrontando questo problema specifico in un sistema di produzione. Raccomando di modificare i nomi dei file anziché utilizzare un parametro.
JBCP

7

Ho preso questo frammento da AskApache e l' ho inserito in un file .conf separato del mio server web Apache locale (nel mio 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>

Per lo sviluppo questo funziona benissimo senza la necessità di modificare il codice. Per quanto riguarda la produzione, potrei usare l'approccio di @ dvtoever.


6

Soluzione rapida per lo sviluppo

Per lo sviluppo, puoi semplicemente disabilitare la cache in Chrome Dev Tools ( Disabilitare la cache di Chrome per lo sviluppo di siti Web ). La disabilitazione della cache si verifica solo se la finestra di dialogo degli strumenti di sviluppo è aperta, quindi non devi preoccuparti di attivare questa opzione ogni volta che navighi regolarmente.

Nota: l'utilizzo di ' urlArgs ' è la soluzione corretta in produzione in modo che gli utenti ottengano il codice più recente. Ma rende difficile il debug perché Chrome invalida i punti di interruzione ad ogni aggiornamento (perché ogni volta viene offerto un "nuovo" file).


3

Non consiglio di usare ' urlArgs ' per lo scoppio della cache con RequireJS. Poiché questo non risolve completamente il problema. L'aggiornamento di una versione no comporterà il download di tutte le risorse, anche se hai appena modificato una singola risorsa.

Per gestire questo problema, consiglio di utilizzare i moduli Grunt come 'filerev' per creare la revisione n. Inoltre, ho scritto un'attività personalizzata in Gruntfile per aggiornare la revisione non dove richiesto.

Se necessario, posso condividere lo snippet di codice per questa attività.


Uso una combinazione di grunt-filerev e grunt-cache-buster per riscrivere i file Javascript.
Ian Jamieson,

2

Ecco come lo faccio in Django / Flask (può essere facilmente adattato ad altre lingue / sistemi VCS):

Nel tuo config.py(lo uso in python3, quindi potrebbe essere necessario modificare la codifica in python2)

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

Quindi nel tuo modello:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • Non richiede il processo di compilazione manuale
  • Viene eseguito solo git rev-parse HEADuna volta all'avvio dell'app e la memorizza confignell'oggetto

0

Soluzione dinamica (senza urlArgs)

Esiste una soluzione semplice per questo problema, in modo da poter caricare un numero di revisione univoco per ogni modulo.

Puoi salvare la funzione requestjs.load originale, sovrascriverla con la tua funzione e analizzare nuovamente l'URL modificato con il requestjs.load originale:

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

Nel nostro processo di costruzione ho usato "gulp-rev" per creare un file manifest con tutte le revisioni di tutti i moduli che vengono utilizzati. Versione semplificata del mio compito gulp:

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

questo genererà un modulo AMD con numeri di revisione in moduleNames, che è incluso come 'oRevision' in main.js, dove si sovrascrive la funzione requestjs.load come mostrato prima.


-1

Questo è in aggiunta alla risposta accettata di @phil mccull.

Uso il suo metodo ma automatizzo anche il processo creando un modello T4 da eseguire pre-build.

Comandi pre-build:

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"

inserisci qui la descrizione dell'immagine

Modello T4:

inserisci qui la descrizione dell'immagine

File generato: inserisci qui la descrizione dell'immagine

Memorizza in variabile prima del caricamento di request.config.js: inserisci qui la descrizione dell'immagine

Riferimento in require.config.js:

inserisci qui la descrizione dell'immagine


2
Probabilmente perché la tua soluzione a un problema JavaScript è un sacco di codice C #. Questo è anche un sacco di lavoro extra e codice per fare qualcosa che alla fine viene fatto esattamente come la risposta accettata.
mAAdhaTTah

@mAAdhaTTah Potresti farlo con qualsiasi lingua lato server ... non deve essere c #. Inoltre, questo automatizza il processo, aggiornando il busto della cache quando si crea una nuova versione del progetto garantendo che il cliente memorizzi sempre nella cache l'ultima versione degli script. Non credo che meriti un ribasso negativo. Sta solo estendendo la risposta offrendo un approccio automatizzato alla soluzione.
Zach Painter,

Fondamentalmente ho creato questo perché quando ho gestito un'applicazione che utilizza request.js, ho trovato abbastanza fastidioso dover commentare manualmente "(new Date ()). GetTime ()) e rimuovere il commento dal cachebuster statico ogni volta che ho aggiornato l'applicazione Facile da dimenticare. All'improvviso il cliente verifica le modifiche e vede gli script memorizzati nella cache, quindi pensa che non sia cambiato nulla. Tutto perché hai semplicemente dimenticato di cambiare il cachebuster. Questo piccolo codice aggiuntivo cancella la possibilità che ciò accada.
Zach Painter,

2
Non l'ho annotato, non so cosa sia realmente successo. Sto solo suggerendo che il codice che non solo non è js, ma un linguaggio di template C #, non sarà di grande aiuto per gli sviluppatori JS che cercano di ottenere una risposta al loro problema.
mAAdhaTTah

-2

Nel mio caso, volevo caricare lo stesso modulo ogni volta che faccio clic, non volevo che le modifiche apportate al file rimanessero. Potrebbe non essere rilevante per questo post esattamente, ma questa potrebbe essere una potenziale soluzione sul lato client senza impostare la configurazione per request. Invece di inviare direttamente il contenuto, è possibile effettuare una copia del file richiesto e mantenere intatto il file effettivo.

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.