Empêcher RequireJS de mettre en cache les scripts requis

302

RequireJS semble faire quelque chose en interne qui met en cache les fichiers javascript requis. Si j'apporte une modification à l'un des fichiers requis, je dois renommer le fichier pour que les modifications soient appliquées.

L'astuce courante consistant à ajouter un numéro de version en tant que paramètre de chaîne de requête à la fin du nom de fichier ne fonctionne pas avec requirejs <script src="jsfile.js?v2"></script>

Ce que je recherche, c'est un moyen d'empêcher ce cache interne des scripts requis par RequireJS sans avoir à renommer mes fichiers de script chaque fois qu'ils sont mis à jour.

Solution multiplateforme:

J'utilise maintenant urlArgs: "bust=" + (new Date()).getTime()pour la suppression automatique du cache pendant le développement et urlArgs: "bust=v2"pour la production où j'incrémente le numéro de version codé en dur après avoir déployé un script requis mis à jour.

Remarque:

@Dustin Getz a mentionné dans une réponse récente que Chrome Developer Tools supprimera les points d'arrêt pendant le débogage lorsque les fichiers Javascript sont continuellement actualisés comme ceci. Une solution de contournement consiste à écrire debugger;du code pour déclencher un point d'arrêt dans la plupart des débogueurs Javascript.

Solutions spécifiques au serveur:

Pour des solutions spécifiques qui peuvent mieux fonctionner pour votre environnement de serveur telles que Node ou Apache, consultez certaines des réponses ci-dessous.

BumbleB2na
la source
Vous êtes sûr que ce n'est pas le serveur ou le client qui fait la mise en cache? (utilise les js requis depuis quelques mois maintenant et n'a rien remarqué de similaire) IE a été surpris en train de mettre en cache les résultats de l'action MVC, chrome mettait en cache nos modèles html mais les fichiers js semblent tous s'actualiser lorsque le cache du navigateur a été réinitialisé. Je suppose que si vous cherchiez à utiliser la mise en cache mais que vous ne pouvez pas faire l'habituel parce que les demandes des js requis supprimaient la chaîne de requête qui pourrait causer le problème?
PJUK
Je ne sais pas si RequireJS supprime les numéros de version ajoutés comme ça. C'était peut-être mon serveur. Il est intéressant de noter que RequireJS possède un paramètre de cache-buster, donc vous avez peut-être raison de supprimer mes numéros de version ajoutés sur les fichiers requis.
BumbleB2na
j'ai mis à jour ma réponse avec une solution de mise en cache potentielle
Dustin Getz
Maintenant, je peux ajouter ce qui suit à la litanie que j'ai mise en avant dans mon article de blog ce matin: codrspace.com/dexygen/… Et c'est-à-dire, non seulement je dois ajouter le contournement du cache, mais require.js ignore une actualisation matérielle.
Dexygen
Je suis confus au sujet du cas d'utilisation pour cela .... Est-ce pour le rechargement à chaud des modules AMD dans le front-end ou quoi?
Alexander Mills

Réponses:

457

RequireJS peut être configuré pour ajouter une valeur à chacune des URL de script pour le contournement du cache.

Dans la documentation RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : arguments de chaîne de requête supplémentaires ajoutés aux URL que RequireJS utilise pour récupérer des ressources. Plus utile pour mettre en cache le buste lorsque le navigateur ou le serveur n'est pas configuré correctement.

Exemple, en ajoutant "v2" à tous les scripts:

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

À des fins de développement, vous pouvez forcer RequireJS à contourner le cache en ajoutant un horodatage:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});
Phil McCullick
la source
46
Très utile, merci. J'utilise urlArgs: "bust=" + (new Date()).getTime()pour la suppression automatique du cache pendant le développement et urlArgs: "bust=v2"pour la production où j'incrémente le numéro de version codé en dur après avoir déployé un script requis mis à jour.
BumbleB2na
9
... également en tant qu'optimiseur de performances, vous pouvez utiliser Math.random () au lieu de (new Date ()). getTime (). C'est plus de beauté, pas de création d'objet et un peu plus rapide jsperf.com/speedcomparison .
Vlad Tsepelev
2
Vous pouvez l'obtenir un peu plus petit:urlArgs: "bust=" + (+new Date)
mrzmyr
11
Je pense que casser le cache à chaque fois est une idée terrible. Malheureusement, RequireJS ne propose pas d'autre alternative. Nous utilisons urlArgs, mais n'utilisons pas d'aléatoire ou d'horodatage pour cela. Au lieu de cela, nous utilisons notre Git SHA actuel, de cette façon ne change que lorsque nous déployons un nouveau code.
Ivan Torres
5
Comment cette variante "v2" peut-elle fonctionner en production, si le fichier qui fournit la chaîne "v2" elle-même est mis en cache? Si je lance une nouvelle application en production, l'ajout de "v3" ne fera rien, car l'application continue de travailler avec bonheur avec les fichiers v2 mis en cache, y compris l'ancienne configuration avec v2 urlArgs.
Benny Bottema
54

N'utilisez pas urlArgs pour cela!

Les chargements de script requis respectent les en-têtes de mise en cache http. (Les scripts sont chargés avec un élément inséré dynamiquement <script>, ce qui signifie que la demande ressemble à tout ancien élément en cours de chargement.)

Servez vos ressources javascript avec les en-têtes HTTP appropriés pour désactiver la mise en cache pendant le développement.

L'utilisation des urlArgs de require signifie que les points d'arrêt que vous définissez ne seront pas conservés lors des actualisations; vous finissez par avoir besoin de mettre des debuggerinstructions partout dans votre code. Mauvais. j'utiliseurlArgs pour les actifs de contournement du cache lors des mises à niveau de production avec le git sha; alors je peux définir mes actifs pour qu'ils soient mis en cache pour toujours et être assuré de ne jamais avoir d'actifs périmés.

En développement, je moque toutes les demandes ajax avec une configuration mockjax complexe , puis je peux servir mon application en mode javascript uniquement avec un serveur http en python de 10 lignes avec toute la mise en cache désactivée . Cela s'est étendu pour moi à une application "entreprise" assez grande avec des centaines de points de terminaison de service Web reposants. Nous avons même un concepteur sous contrat qui peut travailler avec notre véritable base de code de production sans lui donner accès à notre code backend.

Dustin Getz
la source
8
@ JamesP.Wright, car (dans Chrome au moins) lorsque vous définissez un point d'arrêt pour quelque chose qui se produit au chargement de la page, puis cliquez sur Actualiser, le point d'arrêt n'est pas atteint car l'URL a changé et Chrome a supprimé le point d'arrêt. J'aimerais connaître une solution de contournement réservée aux clients.
Drew Noakes
1
Merci, Dustin. Si vous trouvez un moyen de contourner ce problème, veuillez poster. En attendant, vous pouvez utiliser debugger;dans votre code où vous voulez qu'un point d'arrêt persiste.
BumbleB2na
2
Pour toute personne utilisant le serveur http sur le nœud (npm installe le serveur http). Vous pouvez également désactiver la mise en cache avec -c-1 (c'est-à-dire http-server -c-1).
Yourpalal
5
Vous pouvez désactiver la mise en cache dans Chrome pour contourner le problème de débogage pendant le développement: stackoverflow.com/questions/5690269/…
Deepak Joy
5
+1 à !!! NE PAS UTILISER d'URLARGS EN PRODUCTION !!! . Imaginez le cas où votre site Web a 1000 fichiers JS (oui, c'est possible!) Et leur charge est contrôlée par requiredJS. Maintenant, vous publiez v2 ou votre site où seuls quelques fichiers JS sont modifiés! mais en ajoutant urlArgs = v2, vous forcez à recharger tous les 1000 fichiers JS! vous paierez beaucoup de trafic! seuls les fichiers modifiés doivent être rechargés, tous les autres doivent recevoir le statut 304 (Non modifié).
walv
24

La solution urlArgs a des problèmes. Malheureusement, vous ne pouvez pas contrôler tous les serveurs proxy qui pourraient se trouver entre vous et le navigateur Web de votre utilisateur. Certains de ces serveurs proxy peuvent malheureusement être configurés pour ignorer les paramètres d'URL lors de la mise en cache des fichiers. Si cela se produit, la mauvaise version de votre fichier JS sera remise à votre utilisateur.

J'ai finalement abandonné et mis en place mon propre correctif directement dans require.js. Si vous souhaitez modifier votre version de la bibliothèque requirejs, cette solution peut fonctionner pour vous.

Vous pouvez voir le patch ici:

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

Une fois ajouté, vous pouvez faire quelque chose comme ceci dans votre configuration requise:

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

Utilisez votre système de build ou votre environnement de serveur pour remplacer buildNumber par un ID de révision / version du logiciel / couleur préférée.

Utilisation de require comme ceci:

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

Faudra obliger à demander ce fichier:

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

Sur notre environnement de serveur, nous utilisons des règles de réécriture d'URL pour supprimer le buildNumber et servir le fichier JS correct. De cette façon, nous n'avons pas vraiment à nous soucier de renommer tous nos fichiers JS.

Le correctif ignorera tout script spécifiant un protocole et n'affectera aucun fichier non JS.

Cela fonctionne bien pour mon environnement, mais je me rends compte que certains utilisateurs préfèrent un préfixe plutôt qu'un suffixe, il devrait être facile de modifier mon commit en fonction de vos besoins.

Mettre à jour:

Dans la discussion sur la demande d'extraction, l'auteur de requirejs suggère que cela pourrait fonctionner comme une solution pour préfixer le numéro de révision:

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

Je n'ai pas essayé cela, mais l'implication est que cela demanderait l'URL suivante:

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

Ce qui pourrait très bien fonctionner pour de nombreuses personnes qui peuvent utiliser un préfixe.

Voici quelques questions en double possibles:

RequireJS et la mise en cache du proxy

require.js - Comment puis-je définir une version sur les modules requis dans le cadre de l'URL?

JBCP
la source
1
J'aimerais vraiment que votre mise à jour fasse son chemin dans la version officielle de requirejs. L'auteur principal de requirejs pourrait également être intéressé (voir la réponse de @ Louis ci-dessus).
BumbleB2na
@ BumbleB2na - n'hésitez pas à commenter la PullRequest ( github.com/jrburke/requirejs/pull/1017 ), jrburke ne semblait pas intéressé. Il suggère une solution en utilisant un préfixe de nom de fichier, je mettrai à jour ma réponse pour l'inclure.
JBCP
1
Belle mise à jour. Je pense que je aime cette suggestion par l'auteur, mais c'est seulement parce que je l' ai utilisé récemment cette convention de nommage: /scripts/myLib/v1.1/. J'ai essayé d'ajouter le suffixe (ou le préfixe) à mes noms de fichiers, probablement parce que c'est ce que fait jquery, mais après un certain temps, je suis devenu paresseux et j'ai commencé à incrémenter un numéro de version dans le dossier parent. Je pense que cela a rendu la maintenance plus facile pour moi sur un grand site Web, mais maintenant vous m'inquiétez des cauchemars de réécriture d'URL.
BumbleB2na
1
Les systèmes frontaux modernes réécrivent simplement les fichiers JS et avec une somme MD5 dans le nom de fichier, puis réécrivent les fichiers HTML pour utiliser les nouveaux noms de fichiers lors de la construction, mais cela devient difficile avec les systèmes hérités où le code frontal est servi par le côté serveur.
JBCP
est-ce que cela fonctionne quand j'ai besoin de js dans le fichier jspx?, comme ceci<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

Inspiré par Expire cache on require.js data-main, nous avons mis à jour notre script de déploiement avec la tâche ant suivante:

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

À quoi ressemble le début de main.js:

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

En production

urlArgs peut causer des problèmes!

L'auteur principal de requirejs préfère ne pas utiliserurlArgs :

Pour les ressources déployées, je préfère mettre la version ou le hachage pour la build entière en tant que répertoire de build, puis modifier simplement la baseUrlconfiguration utilisée pour le projet pour utiliser ce répertoire versionné en tant que baseUrl. Ensuite, aucun autre fichier ne change et cela permet d'éviter certains problèmes de proxy où ils ne peuvent pas mettre en cache une URL avec une chaîne de requête dessus.

[Styling mine.]

Je suis ce conseil.

En développement

Je préfère utiliser un serveur qui cache intelligemment les fichiers qui peuvent changer fréquemment: un serveur qui émet Last-Modifiedet répond If-Modified-Sinceavec 304 le cas échéant. Même un serveur basé sur l' ensemble express de Node pour servir des fichiers statiques le fait tout de suite. Il ne nécessite rien de faire sur mon navigateur et ne gâche pas les points d'arrêt.

Louis
la source
Bon point, mais votre réponse est spécifique à votre environnement de serveur. Peut-être qu'une bonne alternative pour toute autre personne tombant sur ce problème est la récente recommandation d'ajouter un numéro de version au nom de fichier au lieu d'un paramètre de chaîne de requête. Voici plus d'informations à ce sujet: stevesouders.com/blog/2008/08/23/…
BumbleB2na
Nous rencontrons ce problème spécifique dans un système de production. Je recommande de réviser vos noms de fichiers plutôt que d'utiliser un paramètre.
JBCP
7

J'ai pris cet extrait de AskApache et l' ai mis dans un fichier .conf séparé de mon serveur web Apache local (dans mon cas /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>

Pour le développement, cela fonctionne très bien sans avoir besoin de changer le code. Quant à la production, je pourrais utiliser l'approche de @ dvtoever.

myrho
la source
6

Solution rapide pour le développement

Pour le développement, vous pouvez simplement désactiver le cache dans Chrome Dev Tools ( Désactiver le cache Chrome pour le développement de sites Web ). La désactivation du cache ne se produit que si la boîte de dialogue des outils de développement est ouverte, vous n'avez donc pas à vous soucier de basculer cette option à chaque fois que vous effectuez une navigation régulière.

Remarque: L'utilisation de « urlArgs » est la solution appropriée en production afin que les utilisateurs obtiennent le dernier code. Mais cela rend le débogage difficile car Chrome invalide les points d'arrêt à chaque actualisation (car c'est un «nouveau» fichier servi à chaque fois).

Deepak Joy
la source
3

Je ne recommande pas d'utiliser ' urlArgs ' pour l'éclatement du cache avec RequireJS. Comme cela ne résout pas complètement le problème. La mise à jour d'une version no entraînera le téléchargement de toutes les ressources, même si vous venez de modifier une seule ressource.

Pour gérer ce problème, je recommande d'utiliser des modules Grunt comme «filerev» pour créer la révision no. En plus de cela, j'ai écrit une tâche personnalisée dans Gruntfile pour mettre à jour la révision non partout où cela est nécessaire.

Si nécessaire, je peux partager l'extrait de code pour cette tâche.

Amit Sagar
la source
J'utilise une combinaison de grunt-filerev et grunt-cache-buster pour réécrire les fichiers Javascript.
Ian Jamieson
2

Voici comment je le fais dans Django / Flask (peut être facilement adapté à d'autres langages / systèmes VCS):

Dans votre config.py(j'utilise ceci en python3, vous devrez donc peut-être modifier l'encodage en python2)

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

Puis dans votre modèle:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • Ne nécessite pas de processus de construction manuel
  • S'exécute git rev-parse HEADune seule fois au démarrage de l'application et la stocke dans l' configobjet
Stephen Fuhry
la source
0

Solution dynamique (sans urlArgs)

Il existe une solution simple à ce problème, afin que vous puissiez charger un numéro de révision unique pour chaque module.

Vous pouvez enregistrer la fonction originale requirejs.load, la remplacer par votre propre fonction et analyser à nouveau votre URL modifiée dans l'original requirejs.load:

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

Dans notre processus de construction, j'ai utilisé "gulp-rev" pour construire un fichier manifeste avec toutes les révisions de tous les modules utilisés. Version simplifiée de ma tâche de gorgée:

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

cela générera un module AMD avec des numéros de révision pour moduleNames, qui est inclus en tant que «oRevision» dans le fichier main.js, où vous écrasez la fonction requirejs.load comme indiqué précédemment.

tapis
la source
-1

Ceci s'ajoute à la réponse acceptée de @phil mccull.

J'utilise sa méthode mais j'automatise également le processus en créant un modèle T4 à exécuter pré-build.

Commandes de pré-génération:

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"

entrez la description de l'image ici

Modèle T4:

entrez la description de l'image ici

Fichier généré: entrez la description de l'image ici

Stocker dans une variable avant le chargement de require.config.js: entrez la description de l'image ici

Référence dans require.config.js:

entrez la description de l'image ici

Peintre Zach
la source
2
Probablement parce que votre solution à un problème JavaScript est un tas de code C #. C'est aussi un tas de travail supplémentaire et de code pour faire quelque chose qui est finalement fait exactement de la même manière que la réponse acceptée.
mAAdhaTTah
@mAAdhaTTah Vous pouvez le faire avec n'importe quelle langue côté serveur ... ne doit pas nécessairement être c #. Cela automatise également le processus, mettant à jour le buste de cache lorsque vous créez une nouvelle version du projet, garantissant que le client met toujours en cache la dernière version des scripts. Je ne pense pas que cela mérite une démarque négative. Il étend simplement la réponse en proposant une approche automatisée de la solution.
Zach Painter
J'ai essentiellement créé cela parce que lors de la maintenance d'une application qui utilisait require.js, je trouvais assez ennuyeux de devoir commenter manuellement le "(new Date ()). GetTime ()) et de ne pas commenter le cachebuster statique à chaque fois que je mettais à jour l'application . Facile à oublier. Tout à coup, le client vérifie les modifications et voit le script mis en cache afin qu'il pense que rien n'a changé .. Tout cela parce que vous avez simplement oublié de changer le cachebuster. Ce petit bout de code supplémentaire efface les chances que cela se produise.
Zach Painter
2
Je ne l'ai pas noté, je ne sais pas ce qui s'est réellement passé. Je suggère simplement que le code qui n'est pas seulement js, mais un langage de modèle C #, ne sera pas très utile aux développeurs JS qui essaient d'obtenir une réponse à leur problème.
mAAdhaTTah
-2

Dans mon cas, je voulais charger le même formulaire chaque fois que je cliquais, je ne voulais pas que les modifications que j'avais apportées au fichier restent. Il n'est peut-être pas pertinent pour cet article, mais cela pourrait être une solution potentielle du côté client sans définir la configuration pour require. Au lieu d'envoyer directement le contenu, vous pouvez faire une copie du fichier requis et conserver le fichier réel intact.

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