wake-up-neo.com

So verhindern Sie den Browser-Cache auf der Angular 2-Site

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

Vor allem die html/css-Dateien für die js-Dateien scheinen ordnungsgemäß aktualisiert zu werden, ohne große Probleme zu verursachen.

52
Rikku121

Um einen Weg zu finden, fügen Sie einfach eine Querzeichenfolge hinzu, um Ihre Komponenten zu laden.

@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 des Templates anstelle des .... des Browsers zu laden. Wenn Sie möchten, dass der Browser erst nach einer bestimmten Zeit aktualisiert wird, können Sie stattdessen diesen ISOString verwenden:

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

Und einige Zeichenketten so, dass sie sich beispielsweise erst nach einer Stunde ändern:

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

Hoffe das hilft

28
Rikku121

angle-cli löst dieses Problem brillant auf, indem ein --output-hashing-Flag für den Befehl build bereitgestellt wird. Verwendungsbeispiel:

ng build --aot --output-hashing=all

Bundling & Tree-Shaking liefert einige Details und Kontext. Läuft 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 angle-cli gilt, funktioniert es hervorragend und erfordert keine Codeänderungen oder zusätzliche Tools.

86
Jack

In jeder HTML-Vorlage füge ich oben 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 frei verfügbar, daher erbt sie keine Meta-Caching-Regeln in der Datei index.html. 

13
Rossco

Ich hatte ein ähnliches Problem mit der index.html, die vom Browser zwischengespeichert wurde, oder von mittleren cdn/proxies schwieriger (F5 hilft Ihnen nicht).

Ich habe nach einer Lösung gesucht, die 100% bestätigt, dass der Client die neueste Version von index.html hat. 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 bei einer neueren Version erneut lädt.

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

  • verwenden Sie die Tatsache, dass ng cli -- prod Hash-Dateien mit einer davon namens main. [Hash] .js erzeugt
  • erstellen Sie eine version.json-Datei, die diesen Hash enthält
  • erstellen Sie einen Winkel-Service 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, 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 Lösung von Henrik Peinar für Winkel 4 war, gab es kleinere Änderungen. Ich setze auch die festen Skripte hier ein:

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 post-build.js ausmacht:

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

platzieren Sie das Skript einfach im (neuen) build Ordner. Führen Sie das Skript mit node ./build/post-build.js aus, nachdem Sie den dist-Ordner mit ng build --prod erstellt haben.

0
Aviko

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

Sie können die Direktiven für diese Header festlegen, um genau zu steuern, wie und wann der Cache aktiviert werden soll:

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

0
ranieribt