Angular 2 sitesinde Tarayıcı önbelleği nasıl engellenir?


108

Şu anda müşterilerimizden biri tarafından günlük olarak kullanılan ve düzenli güncellemelerle yeni bir proje üzerinde çalışıyoruz. Bu proje açısal 2 kullanılarak geliştiriliyor ve önbellek sorunlarıyla karşı karşıyayız, yani müşterilerimiz makinelerinde en son değişiklikleri görmüyor.

Esas olarak js dosyalarının html / css dosyaları, fazla sorun çıkarmadan düzgün bir şekilde güncelleniyor gibi görünüyor.


2
Çok güzel soru. Bende de aynı sorun var. Bu sorunu çözmenin en iyi yolu nedir? Bu, Angular 2 uygulamasını yayınlamak için yudum veya benzeri bir araçla mümkün mü?
jump4791

2
@ jump4791 En iyi yol, web paketini kullanmak ve projeyi üretim ayarlarını kullanarak derlemektir. Şu anda bu depoyu
Rikku121

Bende de aynı sorun var.
Ziggler

3
Bunun eski bir soru olduğunu biliyorum ama bunun üzerine olan herkes için bulduğum çözümü eklemek istedim. İle derleme yaparken ng build, -prodetiketin eklenmesi , oluşturulan dosya adlarına bir karma ekler. Bu, her şeyin yeniden yüklenmesini zorlar index.html. Bu github gönderisinde , onu yeniden yüklemek için bazı ipuçları vardı.
Tiz

2
index.html temel nedendir. Karma kodu olmadığı için, önbelleğe alındığında, diğer her şey önbellekten kullanılır.
Fiona

Yanıtlar:


183

angular-cli bunu --output-hashing, build komutu için bir bayrak sağlayarak çözer (6/7 sürümleri, sonraki sürümler için buraya bakın ). Örnek kullanım:

ng build --output-hashing=all

Paketleme ve Ağaç Sallama , bazı ayrıntılar ve bağlam sağlar. Çalışıyor ng help build, bayrağı belgeler:

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

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

Bu yalnızca angular-cli kullanıcıları için geçerli olsa da , zekice çalışır ve herhangi bir kod değişikliği veya ek araç gerektirmez.

Güncelleme

Bir dizi yorum yararlı ve doğru bir şekilde bu cevabın .jsdosyalara bir karma eklediğini ancak bunun için hiçbir şey yapmadığını belirtti index.html. Bu nedenle, önbellek dosyaları bozduktan index.htmlsonra ng buildönbelleğe alınmış kalması tamamen mümkündür .js.

Bu noktada , tüm tarayıcılarda web sayfası önbelleğe almayı nasıl kontrol ederiz?


14
Bunu yapmanın doğru yolu budur ve seçilen cevap olmalıdır!
jonesy827

1
Bu, uygulamamız için işe yaramadı. Sorgu dizesi parametresine sahip templateUrl'nin CLI ile çalışmaması çok kötü
DDiVita

8
Bu, index.html'niz tarayıcı tarafından önbelleğe alınmışsa çalışmaz, dolayısıyla javascript kaynaklarınız için yeni karma adlar görmez. Bence bu ve @ Rossco'nun verdiği cevabın bir kombinasyonu mantıklı olacaktır. Bunun gönderilen HTTP başlıkları ile tutarlı olması da mantıklıdır.
stryba

2
@stryba Bu nedenle html önbelleğe alma işlemi farklı şekilde ele alınmalıdır. Önbelleğe alma işleminin gerçekleşmemesi için Cache-Control, Pragma ve Expires yanıt başlıklarını belirtmelisiniz. Bir arka uç çerçevesi kullanıyorsanız bu kolaydır, ancak bunu Apache için .htaccess dosyalarında da halledebileceğinize inanıyorum (nginx'te nasıl çalıştığını idk).
OzzyTheGiant

3
Bu cevap js dosyalarına bir karma ekler, bu harika. Ancak stryba'nın dediği gibi, index.html'nin önbelleğe alınmadığından da emin olmanız gerekir. Bunu html meta etiketleriyle değil, yanıt başlığı önbellek kontrolü ile yapmalısınız: önbelleksiz (veya daha süslü önbellekleme stratejileri için diğer başlıklar).
Noppey

35

Bunu yapmanın bir yolunu buldum, bileşenlerinizi yüklemek için bir sorgu dizesi eklemeniz yeterli, örneğin:

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

Bu, istemciyi tarayıcının yerine sunucunun şablon kopyasını yüklemeye zorlamalıdır. Yalnızca belirli bir süre sonra yenilenmesini istiyorsanız, bunun yerine bu ISOString'i kullanabilirsiniz:

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

Ve yalnızca bir saat sonra değişecek şekilde bazı karakterleri alt dizeler, örneğin:

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

Bu yardımcı olur umarım


3
Yani uygulamam aslında işe yaramadı. önbelleğe alma garip bir konudur. bazen işe yarıyor bazen yaramıyor. oh aralıklı sorunların güzelliği. Ben de cevabınızı şu şekilde uyarladım:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

TemplateUrls'ım için 404 alıyorum. Örneğin: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (Bulunamadı) Neden olduğuna dair bir fikriniz var mı?
Shenbo

@ Rikku121 Hayır değil. Aslında url'de / olmadan. Yorumu
yayınlarken

15
Kod değişikliği olmasa bile her seferinde önbelleği bozduğunuzda önbelleğe almanın amacı nedir?
Apurv Kamalapuri

1
ng build --aot --build-optimizer = true --base-href = / <url> / hata veriyor --- resource ./login.component.html?v=${new Date () çözümlenemedi. getTime ()}
Pranjal Successena

24

Her html şablonunda sadece aşağıdaki meta etiketleri en üste ekliyorum:

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

Anladığım kadarıyla her şablon bağımsızdır, bu nedenle index.html dosyasında meta hiçbir önbelleğe alma kuralı kurulumunu devralmaz.


5
Bir süredir web paketine geçtik ve açısal uygulamalarımızı önbelleği bozmakla ilgileniyor. Çözümünüzün işe yaradığını bilmek güzel. Teşekkürler
Rikku121

Benim için de yaptı
iniravpatel

5

@ Jack'in cevabı ile @ ranierbit'in cevabının bir kombinasyonu işe yaramalı.

--Output-hashing için ng yapı bayrağını ayarlayın, böylece:

ng build --output-hashing=all

Ardından bu sınıfı bir hizmete veya app.module

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

Ardından bunu sağlayıcılarınıza ekleyin app.module:

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

Bu, istemci makineler için canlı sitelerde önbelleğe alma sorunlarını önlemelidir


3

Tarayıcı tarafından önbelleğe alınan index.html ile benzer bir sorun yaşadım veya orta cdn / proxy'ler tarafından daha karmaşık hale getirildim (F5 size yardımcı olmayacak).

Müşterinin en son index.html sürümüne sahip olduğunu% 100 doğrulayan bir çözüm aradım, neyse ki bu çözümü Henrik Peinar tarafından buldum:

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

Çözüm ayrıca, istemcinin tarayıcıyı günlerce açık tutması, istemcinin aralıklarla güncellemeleri kontrol etmesi ve daha yeni bir sürüm dağıtılırsa yeniden yüklenmesi durumunu da çözer.

Çözüm biraz zor ama bir cazibe gibi çalışıyor:

  • ng cli -- prodbunlardan biri main. [hash] .js olarak adlandırılan karma dosyalar üreten gerçeğini kullanın
  • bu karmayı içeren bir version.json dosyası oluşturun
  • version.json dosyasını kontrol eden ve gerekirse yeniden yükleyen bir açısal servis VersionCheckService oluşturun.
  • Dağıtımdan sonra çalıştırılan bir js betiğinin sizin için hem version.json oluşturduğunu hem de açısal hizmette hash'i değiştirdiğini unutmayın, bu nedenle manuel çalışma gerekmez, ancak post-build.js çalıştırılır.

Henrik Peinar çözümü açısal 4 için olduğu için küçük değişiklikler oldu, düzeltilen komut dosyalarını da buraya yerleştirdim:

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

ana AppComponent'e değiştirin:

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

}

Büyüleyici, post-build.js oluşturan derleme sonrası komut dosyası:

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

komut dosyasını (yeni) derleme klasörüne yerleştirin, komut dosyasını kullanarak node ./build/post-build.jsdist klasörünü oluşturduktan sonrang build --prod


1

İstemci önbelleğini HTTP üstbilgileriyle kontrol edebilirsiniz. Bu, herhangi bir web çerçevesinde çalışır.

Önbelleği nasıl ve ne zaman etkinleştireceğiniz | devre dışı bırakacağınız konusunda ayrıntılı denetime sahip olmak için bu başlıkları direktifleri ayarlayabilirsiniz:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (çok iyi)
  • Pragma (eski tarayıcıları desteklemek istiyorsanız)

İyi önbelleğe alma, tüm bilgisayar sistemlerinde iyidir, ancak çok karmaşıktır . Daha fazla bilgi için https://helmetjs.github.io/docs/nocache/#the-headers adresine bir göz atın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.