Come impedire la cache del browser sul sito Angular 2?


104

Attualmente stiamo lavorando a un nuovo progetto con aggiornamenti regolari che viene utilizzato quotidianamente da uno dei nostri clienti. Questo progetto è stato sviluppato utilizzando angular 2 e stiamo affrontando problemi di cache, ovvero i nostri clienti non vedono le ultime modifiche sulle loro macchine.

Principalmente i file html / css per i file js sembrano essere aggiornati correttamente senza dare troppi problemi.


2
Domanda molto buona. Ho lo stesso problema. Qual è il modo migliore per risolvere questo problema? È possibile con gulp o qualsiasi strumento simile per pubblicare l'applicazione Angular 2?
jump4791

2
@ jump4791 Il modo migliore è utilizzare webpack e compilare il progetto utilizzando le impostazioni di produzione. Attualmente sto utilizzando questo repository, segui i passaggi e dovresti essere bravo: github.com/AngularClass/angular2-webpack-starter
Rikku121

Ho anche lo stesso problema.
Ziggler

3
So che questa è una vecchia domanda, ma volevo aggiungere la soluzione che ho trovato, per chiunque capiti a questo proposito. Quando si crea con ng build, l'aggiunta del -prodtag aggiunge un hash ai nomi dei file generati. Questo costringe la ricarica di tutto ma index.html. Questo post su GitHub aveva alcuni suggerimenti su come ricaricarlo.
Tiz

2
index.html è la causa principale. Poiché non ha codice hash, quando è memorizzato nella cache, tutto il resto viene utilizzato dalla cache.
Fiona

Risposte:


178

angular-cli risolve questo problema fornendo un --output-hashingflag per il comando build (versioni 6/7, per le versioni successive vedi qui ). Utilizzo di esempio:

ng build --output-hashing=all

Bundling & Tree-Shaking fornisce alcuni dettagli e contesto. Correndo ng help build, documenta la bandiera:

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

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

Sebbene questo sia applicabile solo agli utenti di angular-cli , funziona in modo brillante e non richiede modifiche al codice o strumenti aggiuntivi.

Aggiornare

Numerosi commenti hanno utilmente e correttamente sottolineato che questa risposta aggiunge un hash ai .jsfile ma non fa nulla per index.html. È quindi del tutto possibile che index.htmlrimanga memorizzato nella ng buildcache dopo che la cache ha eseguito il busting dei .jsfile.

A questo punto rimando a Come controlliamo la memorizzazione nella cache delle pagine Web, su tutti i browser?


14
Questo è il modo corretto per farlo e dovrebbe essere la risposta selezionata!
jonesy827

1
Questo non ha funzionato per la nostra app. È un
peccato che

8
Questo non funzionerà se il tuo index.html è memorizzato nella cache dal browser, quindi non vedrai nuovi nomi con hash per le tue risorse javascript. Penso che questa sia una combinazione di questo e la risposta data da @Rossco avrebbe senso. Ha anche senso renderlo coerente con le intestazioni HTTP inviate.
stryba

2
@stryba Questo è il motivo per cui la cache HTML dovrebbe essere gestita in modo diverso. È necessario specificare le intestazioni di risposta Cache-Control, Pragma e Expires in modo che non venga eseguita alcuna memorizzazione nella cache. Questo è facile se stai usando un framework di backend, ma credo che tu possa gestirlo anche nei file .htaccess per Apache (idk come funziona in nginx però).
OzzyTheGiant

3
Questa risposta aggiunge un hash ai file js, il che è fantastico. Ma come ha detto stryba, devi anche assicurarti che index.html non sia memorizzato nella cache. Non dovresti farlo con i meta tag html, ma con il response header cache-control: no-cache (o altri header per strategie di caching più fantasiose).
Noppey

34

Trovato un modo per farlo, aggiungi semplicemente una stringa di query per caricare i tuoi componenti, in questo modo:

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

Questo dovrebbe costringere il client a caricare la copia del server del modello invece di quella del browser. Se desideri che si aggiorni solo dopo un certo periodo di tempo, puoi utilizzare invece questa ISOString:

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

E sottostringa alcuni caratteri in modo che cambierà solo dopo un'ora, ad esempio:

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

Spero che questo ti aiuti


3
Quindi la mia implementazione in realtà non ha funzionato. la memorizzazione nella cache è un problema strano. a volte funziona ea volte no. oh la bellezza dei problemi intermittenti. Quindi ho effettivamente adattato la tua risposta come tale:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

Ricevo 404 per il mio templateUrls. Ad esempio: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (non trovato) Qualche idea del perché?
Shenbo

@ Rikku121 No, non lo fa. In realtà è senza / nell'URL. Potrei averlo aggiunto accidentalmente quando pubblico il commento
Shenbo

14
Qual è lo scopo del caching quando si esegue il busting della cache ogni volta anche quando non viene apportata alcuna modifica al codice?
Apurv Kamalapuri

1
ng build --aot --build-optimizer = true --base-href = / <url> / dà errore --- Impossibile risolvere la risorsa ./login.component.html?v=${new Date (). getTime ()}
Pranjal Successena

23

In ogni modello html aggiungo semplicemente i seguenti meta tag in alto:

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

A quanto mi risulta, ogni modello è indipendente, quindi non eredita l'impostazione delle regole di non memorizzazione nella cache nel file index.html.


4
Siamo passati a webpack da un po 'di tempo e si occupa del busting della cache delle nostre app angolari. È bene sapere che la tua soluzione funziona però. Grazie
Rikku121

Lo ha fatto anche per me
iniravpatel

4

Una combinazione della risposta di @ Jack e della risposta di @ ranierbit dovrebbe fare il trucco.

Imposta il flag ng build per --output-hashing così:

ng build --output-hashing=all

Quindi aggiungi questa classe in un servizio o nell'app.moudle

@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);    
    }
}

Quindi aggiungilo ai tuoi provider nel tuo app.module:

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

Ciò dovrebbe impedire problemi di memorizzazione nella cache su siti live per le macchine client


3

Ho avuto un problema simile con index.html memorizzato nella cache dal browser o più complicato da cdn / proxy centrali (F5 non ti aiuterà).

Ho cercato una soluzione che verificasse al 100% che il client avesse l'ultima versione index.html, fortunatamente ho trovato questa soluzione di Henrik Peinar:

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

La soluzione risolve anche il caso in cui il client rimane con il browser aperto per giorni, il client verifica la presenza di aggiornamenti a intervalli e ricarica se viene distribuita una versione più recente.

La soluzione è un po 'complicata ma funziona a meraviglia:

  • usa il fatto che ng cli -- prod produce file con hash con uno di essi chiamato main. [hash] .js
  • creare un file version.json che contiene quell'hash
  • creare un servizio angolare VersionCheckService che controlli version.json e ricaricare se necessario.
  • Nota che uno script js in esecuzione dopo la distribuzione crea per te sia version.json che sostituisce l'hash nel servizio angolare, quindi non è necessario alcun lavoro manuale, ma in esecuzione post-build.js

Poiché la soluzione di Henrik Peinar era per l'angolo 4, ci sono state piccole modifiche, inserisco anche gli script fissi qui:

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

passare a AppComponent principale:

@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);
        }
    }

}

Lo script post-build che crea la magia, 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);
  });

è sufficiente posizionare lo script nella (nuova) cartella build eseguire lo script utilizzando node ./build/post-build.jsdopo aver creato la cartella dist utilizzandong build --prod


1

Puoi controllare la cache del client con le intestazioni HTTP. Funziona in qualsiasi framework web.

Puoi impostare le direttive di queste intestazioni per avere un controllo dettagliato su come e quando abilitare | disabilitare la cache:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (molto buono)
  • Pragma (se vuoi supportare i vecchi browser)

Una buona memorizzazione nella cache è buona, ma molto complessa, in tutti i sistemi informatici . Dai un'occhiata a https://helmetjs.github.io/docs/nocache/#the-headers per ulteriori informazioni.

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.