Bagaimana cara mencegah cache Browser di situs Angular 2?

104

Kami sedang mengerjakan proyek baru dengan pembaruan rutin yang digunakan setiap hari oleh salah satu klien kami. Proyek ini dikembangkan menggunakan angular 2 dan kami menghadapi masalah cache, yaitu klien kami tidak melihat perubahan terbaru pada mesin mereka.

Terutama file html / css untuk file js tampaknya diperbarui dengan benar tanpa menimbulkan banyak masalah.

Rikku121
sumber
2
Pertanyaan yang sangat bagus. Saya memiliki masalah yang sama. Apa cara terbaik untuk mengatasi masalah ini? Apakah ini mungkin dengan gulp atau alat serupa untuk menerbitkan aplikasi Angular 2?
jump4791
2
@ jump4791 Cara terbaik adalah dengan menggunakan webpack dan mengkompilasi proyek menggunakan pengaturan produksi. Saat ini saya menggunakan repo ini, cukup ikuti langkah-langkahnya dan Anda akan baik-baik saja: github.com/AngularClass/angular2-webpack-starter
Rikku121
Saya juga memiliki masalah yang sama.
Ziggler
3
Saya tahu ini adalah pertanyaan lama tetapi saya ingin menambahkan solusi yang saya temukan, untuk siapa saja yang mengalami masalah ini. Saat membuat dengan ng build, menambahkan -prodtag menambahkan hash ke nama file yang dihasilkan. Ini memaksa memuat ulang segalanya kecuali index.html. Posting github ini memiliki beberapa petunjuk untuk membuatnya dimuat ulang.
Tiz
2
index.html adalah akar penyebabnya. Karena tidak memiliki kode hash, ketika di-cache, semua yang lain digunakan dari cache.
Fiona

Jawaban:

179

angular-cli menyelesaikan ini dengan memberikan --output-hashingtanda untuk perintah build (versi 6/7, untuk versi yang lebih baru lihat di sini ). Contoh penggunaan:

ng build --output-hashing=all

Bundling & Tree-Shaking memberikan beberapa detail dan konteks. Berjalan ng help build, mendokumentasikan bendera:

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

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

Meskipun ini hanya berlaku untuk pengguna angular-cli , ini bekerja dengan sangat baik dan tidak memerlukan perubahan kode atau perkakas tambahan.

Memperbarui

Sejumlah komentar telah membantu dan dengan benar menunjukkan bahwa jawaban ini menambahkan hash ke .jsfile tetapi tidak melakukan apa-apa index.html. Oleh karena itu, sangat mungkin bahwa index.htmltetap di-cache setelah ng buildcache merusak .jsfile.

Pada tahap ini, saya akan mengacu pada Bagaimana kami mengontrol cache halaman web, di semua browser?

Mendongkrak
sumber
14
Ini adalah cara yang tepat untuk melakukan ini dan harus menjadi jawaban yang dipilih!
jonesy827
1
Ini tidak berfungsi untuk aplikasi kami. Sayang sekali templateUrl dengan parameter string kueri tidak berfungsi dengan CLI
DDiVita
8
Ini tidak akan berfungsi jika index.html Anda di-cache oleh browser, karenanya tidak akan melihat nama hash baru untuk sumber daya javascript Anda. Saya pikir ini kombinasi dari ini dan jawaban yang diberikan @Rossco akan masuk akal. Masuk akal juga untuk membuatnya konsisten dengan header HTTP yang dikirim.
stryba
2
@stryba Inilah alasan penyimpanan cache html harus ditangani secara berbeda. Anda harus menentukan header respons Cache-Control, Pragma, dan Expires sehingga tidak ada cache yang harus dilakukan. Ini mudah jika Anda menggunakan kerangka kerja backend, tetapi saya yakin Anda juga dapat menangani ini dalam file .htaccess untuk Apache (idk cara kerjanya di nginx).
OzzyTheGiant
3
Jawaban ini menambahkan hash ke file js, yang bagus. Tapi seperti yang dikatakan stryba, Anda juga perlu memastikan index.html tidak di-cache. Anda tidak boleh melakukan ini dengan tag meta html, tetapi dengan header respons cache-control: no-cache (atau header lain untuk strategi cache yang lebih mewah).
Noppey
34

Menemukan cara untuk melakukan ini, cukup tambahkan querystring untuk memuat komponen Anda, seperti:

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

Ini akan memaksa klien untuk memuat salinan template server, bukan milik browser. Jika Anda ingin me-refresh hanya setelah jangka waktu tertentu Anda dapat menggunakan ISOString ini sebagai gantinya:

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

Dan substring beberapa karakter sehingga hanya akan berubah setelah satu jam misalnya:

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

Semoga ini membantu

Rikku121
sumber
3
Jadi implementasi saya sebenarnya tidak berhasil. caching adalah masalah yang aneh. terkadang berhasil dan terkadang tidak. oh keindahan masalah yang terputus-putus. Jadi saya menyesuaikan jawaban Anda menjadi seperti ini:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco
Saya mendapatkan 404 untuk templateUrls saya. Misalnya: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (Not Found) Ada ide mengapa?
Shenbo
@ Rikku1 Tidak, tidak. Ini sebenarnya tanpa / di url. Saya mungkin tidak sengaja menambahkannya ketika saya memposting komentar
Shenbo
15
Apa gunanya caching ketika Anda merusak cache setiap kali bahkan ketika tidak ada perubahan kode?
Apurv Kamalapuri
1
ng build --aot --build-optimizer = true --base-href = / <url> / memberikan kesalahan --- Tidak dapat menyelesaikan sumber daya ./login.component.html?v=${new Date (). getTime ()}
Pranjal Successena
23

Di setiap template html saya hanya menambahkan tag meta berikut di bagian atas:

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

Dalam pemahaman saya setiap template berdiri bebas oleh karena itu tidak mewarisi meta tidak ada pengaturan aturan caching di file index.html.

Rossco
sumber
4
Kami telah beralih ke webpack untuk beberapa waktu sekarang dan itu menangani cache yang merusak aplikasi sudut kami. Ada baiknya mengetahui solusi Anda berfungsi. Terima kasih
Rikku121
Itu juga untuk saya
iniravpatel
4

Kombinasi jawaban @ Jack dan jawaban @ ranierbit akan berhasil.

Setel flag ng build untuk --output-hashing jadi:

ng build --output-hashing=all

Kemudian tambahkan kelas ini di layanan atau di app.moudle Anda

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

Kemudian tambahkan ini ke penyedia Anda di app.module Anda:

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

Ini akan mencegah masalah cache di situs langsung untuk mesin klien

NiallMitch14
sumber
3

Saya memiliki masalah serupa dengan index.html yang di-cache oleh browser atau lebih rumit oleh cdn / proxy tengah (F5 tidak akan membantu Anda).

Saya mencari solusi yang memverifikasi 100% bahwa klien memiliki versi index.html terbaru, untungnya saya menemukan solusi ini oleh Henrik Peinar:

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

Solusi ini juga mengatasi kasus di mana klien tetap dengan browser terbuka selama berhari-hari, klien memeriksa pembaruan pada interval dan memuat ulang jika versi yang lebih baru diterapkan.

Solusinya agak rumit tetapi berfungsi seperti pesona:

  • gunakan fakta itu ng cli -- prod menghasilkan file hash dengan salah satunya disebut main. [hash] .js
  • buat file version.json yang berisi hash tersebut
  • buat VersionCheckService layanan sudut yang memeriksa version.json dan memuat ulang jika perlu.
  • Perhatikan bahwa skrip js yang berjalan setelah penerapan dibuat untuk Anda baik version.json dan mengganti hash di layanan angular, jadi tidak diperlukan pekerjaan manual, tetapi menjalankan post-build.js

Karena solusi Henrik Peinar adalah untuk sudut 4, ada perubahan kecil, saya juga menempatkan skrip tetap di sini:

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

ubah ke AppComponent utama:

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

}

Skrip post-build yang membuat keajaiban, 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);
  });

cukup tempatkan skrip di (baru) folder build jalankan skrip menggunakan node ./build/post-build.jssetelah membangun folder dist menggunakanng build --prod

Aviko
sumber
1

Anda dapat mengontrol cache klien dengan header HTTP. Ini berfungsi dalam kerangka web apa pun.

Anda dapat menyetel arahan dari header ini agar memiliki kontrol yang sangat baik tentang bagaimana dan kapan harus mengaktifkan | nonaktifkan cache:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (sangat bagus)
  • Pragma (jika Anda ingin mendukung browser lama)

Caching yang baik itu bagus, tetapi sangat kompleks, di semua sistem komputer . Lihat https://helmetjs.github.io/docs/nocache/#the-headers untuk informasi lebih lanjut.

ranieribt
sumber