Comment empêcher le cache du navigateur sur le site Angular 2?

104

Nous travaillons actuellement sur un nouveau projet avec des mises à jour régulières qui est utilisé quotidiennement par l'un de nos clients. Ce projet est développé en utilisant angular 2 et nous sommes confrontés à des problèmes de cache, c'est-à-dire que nos clients ne voient pas les derniers changements sur leurs machines.

Principalement, les fichiers html / css pour les fichiers js semblent être mis à jour correctement sans trop de problèmes.

Rikku121
la source
2
Très bonne question. J'ai le même problème. Quelle est la meilleure façon de résoudre ce problème? Est-ce possible avec gulp ou tout autre outil similaire pour publier une application Angular 2?
jump4791
2
@ jump4791 Le meilleur moyen est d'utiliser webpack et de compiler le projet en utilisant les paramètres de production. J'utilise actuellement ce dépôt, suivez simplement les étapes et vous devriez être bon: github.com/AngularClass/angular2-webpack-starter
Rikku121
J'ai aussi le même problème.
Ziggler
3
Je sais que c'est une vieille question, mais je voulais ajouter la solution que j'ai trouvée, pour tous ceux qui surviennent à ce sujet. Lors de la création avec ng build, l'ajout de la -prodbalise ajoute un hachage aux noms de fichiers générés. Cela force le rechargement de tout sauf index.html. Ce post github contenait quelques astuces pour le recharger.
Tiz le
2
index.html est la cause première. Parce qu'il n'a pas de hashcode, quand il est mis en cache, tout le reste est utilisé à partir du cache.
Fiona

Réponses:

178

angular-cli résout ce problème en fournissant un --output-hashingindicateur pour la commande build (versions 6/7, pour les versions ultérieures, voir ici ). Exemple d'utilisation:

ng build --output-hashing=all

Bundling & Tree-Shaking fournit des détails et un contexte. En cours d'exécution ng help build, documente le drapeau:

--output-hashing=none|all|media|bundles (String)

Define the output filename cache-busting hashing mode.
aliases: -oh <value>, --outputHashing <value>

Bien que cela ne s'applique qu'aux utilisateurs d' angular-cli , il fonctionne à merveille et ne nécessite aucun changement de code ou d'outillage supplémentaire.

Mettre à jour

Un certain nombre de commentaires ont souligné utilement et correctement que cette réponse ajoute un hachage aux .jsfichiers mais ne fait rien pour index.html. Il est donc tout à fait possible que le index.htmlcache reste en cache après que le ng buildcache ait détruit les .jsfichiers.

À ce stade, je m'en remettrai à Comment contrôlons-nous la mise en cache des pages Web, sur tous les navigateurs?

Jack
la source
14
C'est la bonne façon de le faire et devrait être la réponse choisie!
jonesy827
1
Cela n'a pas fonctionné pour notre application. C'est dommage que templateUrl avec un paramètre de chaîne de requête ne fonctionne pas avec la CLI
DDiVita
8
Cela ne fonctionnera pas si votre index.html est mis en cache par le navigateur et ne verra donc pas les nouveaux noms hachés pour vos ressources javascript. Je pense que cette combinaison de cela et de la réponse donnée par @Rossco aurait du sens. Il est également logique de rendre cela cohérent avec les en-têtes HTTP envoyés.
stryba
2
@stryba C'est pourquoi la mise en cache html doit être gérée différemment. Vous devez spécifier les en-têtes de réponse Cache-Control, Pragma et Expires afin qu'aucune mise en cache ne puisse avoir lieu. C'est facile si vous utilisez un framework backend, mais je pense que vous pouvez également gérer cela dans des fichiers .htaccess pour Apache (mais comment cela fonctionne dans nginx).
OzzyTheGiant
3
Cette réponse ajoute un hachage aux fichiers js, ce qui est excellent. Mais comme l'a dit stryba, vous devez également vous assurer que index.html n'est pas mis en cache. Vous ne devriez pas faire cela avec des balises meta html, mais avec des en-têtes de réponse cache-control: no-cache (ou d'autres en-têtes pour des stratégies de mise en cache plus sophistiquées).
Noppey
34

Vous avez trouvé un moyen de le faire, ajoutez simplement une chaîne de requête pour charger vos composants, comme ceci:

@Component({
  selector: 'some-component',
  templateUrl: `./app/component/stuff/component.html?v=${new Date().getTime()}`,
  styleUrls: [`./app/component/stuff/component.css?v=${new Date().getTime()}`]
})

Cela devrait forcer le client à charger la copie du modèle du serveur au lieu de celle du navigateur. Si vous souhaitez qu'il ne s'actualise qu'après un certain temps, vous pouvez utiliser cet ISOString à la place:

new Date().toISOString() //2016-09-24T00:43:21.584Z

Et sous-chaîne de certains caractères pour qu'elle ne change qu'au bout d'une heure par exemple:

new Date().toISOString().substr(0,13) //2016-09-24T00

J'espère que cela t'aides

Rikku121
la source
3
Donc, ma mise en œuvre n'a finalement pas fonctionné. la mise en cache est un problème étrange. fonctionne parfois et parfois non. oh la beauté des problèmes intermittents. J'ai donc adapté votre réponse en tant que telle:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco
J'obtiens 404 pour mes templateUrls. Par exemple: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (Not Found) Une idée pourquoi?
Shenbo
@ Rikku121 Non, ce n'est pas le cas. C'est en fait sans le / dans l'url. Je l'aurais peut-être accidentellement ajouté lorsque j'ai
posté
14
Quel est l'intérêt de la mise en cache lorsque vous cassez le cache à chaque fois, même en l'absence de changement de code?
Apurv Kamalapuri
1
ng build --aot --build-optimizer = true --base-href = / <url> / donne une erreur --- Impossible de résoudre la ressource ./login.component.html?v=${new Date (). getTime ()}
Pranjal Successena
23

Dans chaque modèle html, j'ajoute simplement les balises meta suivantes en haut:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

Dans ma compréhension, chaque modèle est autonome, il n'hérite donc pas de la configuration des règles de mise en cache meta no dans le fichier index.html.

Rossco
la source
4
Nous sommes passés à Webpack depuis un certain temps maintenant et il se charge de contourner le cache de nos applications angulaires. Il est cependant bon de savoir que votre solution fonctionne. Thanks
Rikku121
Ça l'a fait aussi pour moi
iniravpatel
4

Une combinaison de la réponse de @ Jack et de la réponse de @ ranierbit devrait faire l'affaire.

Définissez l'indicateur de construction ng pour --output-hashing ainsi:

ng build --output-hashing=all

Ajoutez ensuite cette classe soit dans un service, soit dans votre application.

@Injectable()
export class NoCacheHeadersInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const authReq = req.clone({
            setHeaders: {
                'Cache-Control': 'no-cache',
                 Pragma: 'no-cache'
            }
        });
        return next.handle(authReq);    
    }
}

Ajoutez ensuite ceci à vos fournisseurs dans votre app.module:

providers: [
  ... // other providers
  {
    provide: HTTP_INTERCEPTORS,
    useClass: NoCacheHeadersInterceptor,
    multi: true
  },
  ... // other providers
]

Cela devrait éviter les problèmes de mise en cache sur les sites en direct pour les ordinateurs clients

NiallMitch14
la source
3

J'ai eu un problème similaire avec l'index.html mis en cache par le navigateur ou plus délicat par le cdn / proxies du milieu (F5 ne vous aidera pas).

J'ai cherché une solution qui vérifie à 100% que le client a la dernière version d'index.html, heureusement j'ai trouvé cette solution par Henrik Peinar:

https://blog.nodeswat.com/automagic-reload-for-clients-after-deploy-with-angular-4-8440c9fdd96c

La solution résout également le cas où le client reste avec le navigateur ouvert pendant des jours, le client vérifie les mises à jour à intervalles réguliers et recharge si une version plus récente est déployée.

La solution est un peu délicate mais fonctionne comme un charme:

  • utiliser le fait qui ng cli -- prodproduit des fichiers hachés avec l'un d'eux appelé main. [hash] .js
  • créer un fichier version.json contenant ce hachage
  • créer un service angulaire VersionCheckService qui vérifie version.json et recharger si nécessaire.
  • Notez qu'un script js exécuté après le déploiement crée pour vous à la fois version.json et remplace le hachage dans le service angulaire, donc aucun travail manuel n'est nécessaire, mais exécutant post-build.js

Puisque la solution Henrik Peinar était pour angular 4, il y a eu des changements mineurs, je place également les scripts fixes ici:

VersionCheckService:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class VersionCheckService {
    // this will be replaced by actual hash post-build.js
    private currentHash = '{{POST_BUILD_ENTERS_HASH_HERE}}';

    constructor(private http: HttpClient) {}

    /**
     * Checks in every set frequency the version of frontend application
     * @param url
     * @param {number} frequency - in milliseconds, defaults to 30 minutes
     */
    public initVersionCheck(url, frequency = 1000 * 60 * 30) {
        //check for first time
        this.checkVersion(url); 

        setInterval(() => {
            this.checkVersion(url);
        }, frequency);
    }

    /**
     * Will do the call and check if the hash has changed or not
     * @param url
     */
    private checkVersion(url) {
        // timestamp these requests to invalidate caches
        this.http.get(url + '?t=' + new Date().getTime())
            .subscribe(
                (response: any) => {
                    const hash = response.hash;
                    const hashChanged = this.hasHashChanged(this.currentHash, hash);

                    // If new version, do something
                    if (hashChanged) {
                        // ENTER YOUR CODE TO DO SOMETHING UPON VERSION CHANGE
                        // for an example: location.reload();
                        // or to ensure cdn miss: window.location.replace(window.location.href + '?rand=' + Math.random());
                    }
                    // store the new hash so we wouldn't trigger versionChange again
                    // only necessary in case you did not force refresh
                    this.currentHash = hash;
                },
                (err) => {
                    console.error(err, 'Could not get version');
                }
            );
    }

    /**
     * Checks if hash has changed.
     * This file has the JS hash, if it is a different one than in the version.json
     * we are dealing with version change
     * @param currentHash
     * @param newHash
     * @returns {boolean}
     */
    private hasHashChanged(currentHash, newHash) {
        if (!currentHash || currentHash === '{{POST_BUILD_ENTERS_HASH_HERE}}') {
            return false;
        }

        return currentHash !== newHash;
    }
}

passer à AppComponent principal:

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor(private versionCheckService: VersionCheckService) {

    }

    ngOnInit() {
        console.log('AppComponent.ngOnInit() environment.versionCheckUrl=' + environment.versionCheckUrl);
        if (environment.versionCheckUrl) {
            this.versionCheckService.initVersionCheck(environment.versionCheckUrl);
        }
    }

}

Le script post-build qui rend la magie, post-build.js:

const path = require('path');
const fs = require('fs');
const util = require('util');

// get application version from package.json
const appVersion = require('../package.json').version;

// promisify core API's
const readDir = util.promisify(fs.readdir);
const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);

console.log('\nRunning post-build tasks');

// our version.json will be in the dist folder
const versionFilePath = path.join(__dirname + '/../dist/version.json');

let mainHash = '';
let mainBundleFile = '';

// RegExp to find main.bundle.js, even if it doesn't include a hash in it's name (dev build)
let mainBundleRegexp = /^main.?([a-z0-9]*)?.js$/;

// read the dist folder files and find the one we're looking for
readDir(path.join(__dirname, '../dist/'))
  .then(files => {
    mainBundleFile = files.find(f => mainBundleRegexp.test(f));

    if (mainBundleFile) {
      let matchHash = mainBundleFile.match(mainBundleRegexp);

      // if it has a hash in it's name, mark it down
      if (matchHash.length > 1 && !!matchHash[1]) {
        mainHash = matchHash[1];
      }
    }

    console.log(`Writing version and hash to ${versionFilePath}`);

    // write current version and hash into the version.json file
    const src = `{"version": "${appVersion}", "hash": "${mainHash}"}`;
    return writeFile(versionFilePath, src);
  }).then(() => {
    // main bundle file not found, dev build?
    if (!mainBundleFile) {
      return;
    }

    console.log(`Replacing hash in the ${mainBundleFile}`);

    // replace hash placeholder in our main.js file so the code knows it's current hash
    const mainFilepath = path.join(__dirname, '../dist/', mainBundleFile);
    return readFile(mainFilepath, 'utf8')
      .then(mainFileData => {
        const replacedFile = mainFileData.replace('{{POST_BUILD_ENTERS_HASH_HERE}}', mainHash);
        return writeFile(mainFilepath, replacedFile);
      });
  }).catch(err => {
    console.log('Error with post build:', err);
  });

placez simplement le script dans le (nouveau) dossier de construction exécutez le script en utilisant node ./build/post-build.jsaprès la construction du dossier dist en utilisantng build --prod

Aviko
la source
1

Vous pouvez contrôler le cache client avec des en-têtes HTTP. Cela fonctionne dans n'importe quel framework Web.

Vous pouvez définir les directives de ces en-têtes pour avoir un contrôle précis sur comment et quand activer | désactiver le cache:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (très bon)
  • Pragma (si vous souhaitez prendre en charge les anciens navigateurs)

Une bonne mise en cache est bonne, mais très complexe, dans tous les systèmes informatiques . Jetez un coup d'œil à https://helmetjs.github.io/docs/nocache/#the-headers pour plus d'informations.

ranieribt
la source