web-dev-qa-db-fra.com

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

Nous travaillons actuellement sur un nouveau projet avec des mises à jour régulières, 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. Nos clients ne voient pas les dernières modifications apportées à leurs machines.

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

52
Rikku121

Si 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 obliger le client à charger la copie du modèle du serveur au lieu de la version ..__ du navigateur. Si vous souhaitez l'actualiser uniquement 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 caractères pour que cela ne change qu'après une heure, par exemple:

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

J'espère que cela t'aides

28
Rikku121

angular-cli résout cela brillamment en fournissant un drapeau --output-hashing pour la commande build . Exemple d'utilisation:

ng build --aot --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 soit applicable qu'aux utilisateurs de angular-cli , cela fonctionne brillamment et ne nécessite aucune modification du code ni outillage supplémentaire.

86
Jack

Dans chaque modèle html, je viens d'ajouter les balises méta 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">

À ma connaissance, chaque modèle est autonome et n’hérite donc pas des règles de mise en cache Meta No dans le fichier index.html. 

13
Rossco

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

J'ai cherché une solution qui vérifie à 100% que le client dispose de la dernière version de index.html. Heureusement, j'ai trouvé cette solution de 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 et recharge si la nouvelle version est déployée.

La solution est un peu délicate mais fonctionne à merveille:

  • utilisez le fait que ng cli -- prod produit 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 le recharger si nécessaire. 
  • Notez qu'un script js qui s'exécute après le déploiement crée pour vous à la fois version.json et remplace le hachage dans le service angular. Aucun travail manuel n'est donc nécessaire, mais l'exécution de post-build.js

Comme la solution de Henrik Peinar était pour angular 4, il y avait 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 fait 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 build et exécutez-le à l'aide de node ./build/post-build.js après avoir créé le dossier dist à l'aide de ng build --prod

0
Aviko

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

Vous pouvez définir les directives pour que ces en-têtes contrôlent avec précision quand et comment activer | désactiver le cache:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (très bon)
  • Pragma (si vous voulez supporter 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.

0
ranieribt