Wie kann ich den Browser-Cache auf der Angular 2-Site verhindern?

103

Wir arbeiten derzeit an einem neuen Projekt mit regelmäßigen Updates, die täglich von einem unserer Kunden verwendet werden. Dieses Projekt wird mit Angular 2 entwickelt und wir haben Probleme mit dem Cache. Das heißt, unsere Kunden sehen nicht die neuesten Änderungen auf ihren Computern.

Hauptsächlich scheinen die HTML / CSS-Dateien für die JS-Dateien ordnungsgemäß aktualisiert zu werden, ohne große Probleme zu verursachen.

Rikku121
quelle
2
Sehr gute Frage. Ich habe das gleiche Problem. Was ist der beste Weg, um dieses Problem zu lösen? Ist dies mit gulp oder einem ähnlichen Tool zum Veröffentlichen von Angular 2-Anwendungen möglich?
Sprung4791
2
@ jump4791 Am besten verwenden Sie das Webpack und kompilieren das Projekt mithilfe der Produktionseinstellungen. Ich benutze gerade dieses Repo, folge einfach den Schritten und du solltest gut sein: github.com/AngularClass/angular2-webpack-starter
Rikku121
Ich habe auch das gleiche Problem.
Ziggler
3
Ich weiß, dass dies eine alte Frage ist, aber ich wollte die Lösung, die ich gefunden habe, für jeden hinzufügen, der darüber nachdenkt. Beim Erstellen mit ng buildfügt das Hinzufügen des -prodTags den generierten Dateinamen einen Hash hinzu. Dies erzwingt das Nachladen von allem aber index.html. Dieser Github-Beitrag hatte einige Hinweise, wie man das neu laden kann.
Tiz
2
index.html ist die Hauptursache. Da es keinen Hashcode hat, wird beim Zwischenspeichern alles andere aus dem Cache verwendet.
Fiona

Antworten:

177

angle-cli behebt dieses Problem, indem ein --output-hashingFlag für den Build- Befehl bereitgestellt wird (Versionen 6/7, für spätere Versionen siehe hier ). Anwendungsbeispiel:

ng build --output-hashing=all

Bundling & Tree-Shaking bietet einige Details und Zusammenhänge. Laufen ng help build, dokumentiert die Flagge:

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

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

Obwohl dies nur für Benutzer von Angular-Cli gilt , funktioniert es hervorragend und erfordert keine Codeänderungen oder zusätzliche Werkzeuge.

Aktualisieren

In einer Reihe von Kommentaren wurde hilfreich und korrekt darauf hingewiesen, dass diese Antwort den .jsDateien einen Hash hinzufügt , aber nichts dafür tut index.html. Es ist daher durchaus möglich, dass index.htmlzwischengespeichert bleibt, nachdem der ng buildCache die .jsDateien kaputt gemacht hat .

An dieser Stelle werde ich auf Wie steuern wir das Zwischenspeichern von Webseiten in allen Browsern?

Jack
quelle
14
Dies ist der richtige Weg, dies zu tun und sollte die ausgewählte Antwort sein!
jonesy827
1
Dies hat bei unserer App nicht funktioniert. Es ist schade, dass die templateUrl mit einem
Abfragezeichenfolgenparameter
8
Dies funktioniert nicht, wenn Ihre index.html vom Browser zwischengespeichert wird und daher keine neuen Hash-Namen für Ihre Javascript-Ressourcen angezeigt werden. Ich denke, dies wäre eine Kombination aus dieser und der Antwort, die @Rossco gegeben hat, sinnvoll. Es ist auch sinnvoll, dies mit den gesendeten HTTP-Headern in Einklang zu bringen.
Stryba
2
@stryba Aus diesem Grund sollte das HTML-Caching anders gehandhabt werden. Sie sollten die Antwortheader Cache-Control, Pragma und Expires angeben, damit kein Caching stattfinden soll. Dies ist einfach, wenn Sie ein Backend-Framework verwenden, aber ich glaube, Sie können dies auch in .htaccess-Dateien für Apache behandeln (idk, wie es in Nginx funktioniert).
OzzyTheGiant
3
Diese Antwort fügt den js-Dateien einen Hash hinzu, was großartig ist. Aber wie stryba sagte, müssen Sie auch sicherstellen, dass index.html nicht zwischengespeichert wird. Sie sollten dies nicht mit HTML-Meta-Tags tun, sondern mit der Cache-Steuerung des Antwortheaders: kein Cache (oder anderen Headern für ausgefallenere Caching-Strategien).
Noppey
34

Um einen Weg zu finden, fügen Sie einfach einen Querystring hinzu, um Ihre Komponenten zu laden, wie folgt:

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

Dies sollte den Client zwingen, die Serverkopie der Vorlage anstelle der des Browsers zu laden. Wenn Sie möchten, dass es erst nach einer bestimmten Zeit aktualisiert wird, können Sie stattdessen diesen ISOString verwenden:

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

Und Teilzeichenfolge einiger Zeichen, damit sie sich erst nach einer Stunde ändert, zum Beispiel:

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

Hoffe das hilft

Rikku121
quelle
3
Meine Implementierung hat also nicht funktioniert. Caching ist ein seltsames Problem. funktioniert manchmal und manchmal nicht. Oh, die Schönheit von zeitweiligen Problemen. Also habe ich Ihre Antwort tatsächlich so angepasst:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco
Ich bekomme 404 für meine templateUrls. Beispiel: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (nicht gefunden) Irgendeine Idee warum?
Shenbo
@ Rikku121 Nein, tut es nicht. Es ist eigentlich ohne das / in der URL. Ich könnte es versehentlich hinzugefügt haben, als ich den Kommentar
poste
14
Was bringt das Zwischenspeichern, wenn Sie den Cache jedes Mal sprengen, selbst wenn keine Codeänderung erfolgt?
Apurv Kamalapuri
1
ng build --aot --build-optimizer = true --base-href = / <url> / gibt Fehler aus --- Ressource konnte nicht aufgelöst werden ./login.component.html?v=${new Date (). getTime ()}
Pranjal Successena
23

In jeder HTML-Vorlage füge ich oben einfach die folgenden Meta-Tags hinzu:

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

Nach meinem Verständnis ist jede Vorlage freistehend, daher erbt sie keine Meta-No-Caching-Regeln, die in der Datei index.html eingerichtet wurden.

Rossco
quelle
4
Wir haben seit einiger Zeit auf Webpack umgestellt und es sorgt dafür, dass der Cache unsere eckigen Apps kaputt macht. Es ist jedoch gut zu wissen, dass Ihre Lösung funktioniert. Danke
Rikku121
Es tat auch für mich
iniravpatel
4

Eine Kombination aus @ Jacks Antwort und @ ranierbits Antwort sollte den Trick machen.

Setzen Sie das ng-Build-Flag für --output-hashing wie folgt:

ng build --output-hashing=all

Fügen Sie diese Klasse dann entweder in einem Dienst oder in Ihrer app.moudle hinzu

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

Fügen Sie dies dann Ihren Anbietern in Ihrer app.module hinzu:

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

Dies sollte Caching-Probleme auf Live-Sites für Client-Computer verhindern

NiallMitch14
quelle
3

Ich hatte ein ähnliches Problem damit, dass die index.html vom Browser zwischengespeichert wurde oder schwieriger durch mittlere CDN / Proxys (F5 hilft Ihnen nicht).

Ich habe nach einer Lösung gesucht, die zu 100% bestätigt, dass der Client über die neueste index.html-Version verfügt. Zum Glück habe ich diese Lösung von Henrik Peinar gefunden:

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

Die Lösung löst auch den Fall, dass der Client tagelang bei geöffnetem Browser bleibt, der Client in Intervallen nach Updates sucht und neu lädt, wenn eine neuere Version bereitgestellt wird.

Die Lösung ist etwas knifflig, funktioniert aber wie ein Zauber:

  • Verwenden Sie die Tatsache, dass ng cli -- prodHash-Dateien mit einer von ihnen namens main. [hash] .js erstellt werden
  • Erstellen Sie eine version.json-Datei, die diesen Hash enthält
  • Erstellen Sie einen Winkeldienst VersionCheckService, der version.json überprüft und bei Bedarf neu lädt.
  • Beachten Sie, dass ein js-Skript, das nach der Bereitstellung ausgeführt wird, für Sie sowohl version.json erstellt als auch den Hash im Winkeldienst ersetzt, sodass keine manuelle Arbeit erforderlich ist, sondern post-build.js ausgeführt wird

Da die Henrik Peinar-Lösung für Winkel 4 war, gab es geringfügige Änderungen. Ich platziere hier auch die festen Skripte:

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

Wechseln Sie zur Haupt-AppComponent:

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

}

Das Post-Build-Skript, das die Magie erzeugt, 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);
  });

Legen Sie das Skript einfach in den (neuen) Build- Ordner. Führen Sie das Skript aus, node ./build/post-build.jsnachdem Sie den dist-Ordner mit erstellt habenng build --prod

Aviko
quelle
1

Sie können den Client-Cache mit HTTP-Headern steuern. Dies funktioniert in jedem Webframework.

Sie können die Anweisungen für diese Header so einstellen, dass sie genau steuern, wie und wann der Cache aktiviert oder deaktiviert wird:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (sehr gut)
  • Pragma (wenn Sie alte Browser unterstützen möchten)

Gutes Caching ist in allen Computersystemen gut, aber sehr komplex . Weitere Informationen finden Sie unter https://helmetjs.github.io/docs/nocache/#the-headers .

ranieribt
quelle