Angular 2サイトでブラウザのキャッシュを防ぐ方法は?


104

私たちは現在、クライアントの1人が毎日使用している定期的な更新を含む新しいプロジェクトに取り組んでいます。このプロジェクトはangular 2を使用して開発されており、私たちはキャッシュの問題に直面しています。つまり、クライアントはマシンの最新の変更を見ていません。

主にjsファイルのhtml / cssファイルは、それほど問題なく適切に更新されるようです。


2
非常に良い質問です。私は同じ問題を抱えています。この問題を解決する最良の方法は何ですか?Angular 2アプリケーションを公開するためのgulpまたは類似のツールでこれは可能ですか?
jump4791

2
@ jump4791最善の方法は、webpackを使用し、プロダクション設定を使用してプロジェクトをコンパイルすることです。私は現在、このリポジトリを使用しています。次の手順に従ってください。github.com
AngularClass

私も同じ問題を抱えています。
ジグラー

3
私はこれが古い質問であることを知っていますが、私が見つけた解決策を追加したかったのです。を使用してビルドする場合ng build-prodタグを追加すると、生成されたファイル名にハッシュが追加されます。これにより、以外のすべての再ロードが強制されますindex.htmlこのgithubの投稿には、再読み込みするためのヒントがいくつかありました。
Tiz

2
index.htmlが根本的な原因です。ハッシュコードがないため、キャッシュされると、それ以外はすべてキャッシュから使用されます。
フィオナ

回答:


178

角度クリ--output-hashingビルド コマンドのフラグを提供することでこれを解決します(バージョン6/7、それ以降のバージョンについては、こちらを参照してください)。使用例:

ng build --output-hashing=all

バンドリングとツリーシェーキングは、いくつかの詳細とコンテキストを提供します。実行するng help buildと、フラグが記録されます。

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

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

これはangular-cliのユーザーにのみ適用されますが、見事に機能し、コードの変更や追加のツールを必要としません。

更新

多くのコメントは、この答えがファイルにハッシュを追加するが、何もしないことを役立つ正しく指摘し.jsましたindex.html。したがってindex.htmlng buildキャッシュがバストした後もキャッシュされたままになる可能性は完全にあります。.jsファイルをます。

この時点で、すべてのブラウザーでWebページのキャッシュどのように制御するかについては、後で説明します。


14
これはこれを行う適切な方法であり、選択された答えになるはずです!
jonesy827 2017

1
これは私たちのアプリでは機能しませんでした。クエリ文字列パラメータを含むtemplateUrlがCLIでは機能しない
DDiVita

8
これは、index.htmlがブラウザーによってキャッシュされている場合は機能しません。そのため、JavaScriptリソースの新しいハッシュされた名前は表示されません。これとこれと@Rosscoの回答の組み合わせは理にかなっていると思います。これを、送信されるHTTPヘッダーと一致させることも意味があります。
ストリバ

2
@strybaこれが、htmlキャッシングを異なる方法で処理する必要がある理由です。Cache-Control、Pragma、およびExpires応答ヘッダーを指定して、キャッシュが行われないようにする必要があります。これはバックエンドフレームワークを使用している場合は簡単ですが、Apacheの.htaccessファイルでもこれを処理できると思います(nginxでの動作方法はidkです)。
OzzyTheGiant

3
この答えはjsファイルにハッシュを追加します。これは素晴らしいことです。ただし、strybaが言ったように、index.htmlがキャッシュされないようにする必要もあります。これはhtmlメタタグではなく、レスポンスヘッダーcache-control:no-cache(または、より高度なキャッシュ戦略のための他のヘッダー)で行う必要があります。
Noppey

34

これを行う方法が見つかりました。次のように、クエリ文字列を追加してコンポーネントをロードするだけです。

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

これにより、クライアントはブラウザではなくサーバーのテンプレートのコピーをロードする必要があります。一定時間後にのみ更新したい場合は、代わりにこのISOStringを使用できます。

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

そして、いくつかの文字をサブストリング化して、たとえば1時間後にのみ変更されるようにします。

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

お役に立てれば


3
したがって、私の実装は実際には機能しませんでした。キャッシングは奇妙な問題です。時々動作し、時々動作しません。ああ、断続的な問題の美しさ。だから私は実際にあなたの答えをそのように適応させました:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

templateUrlsに404を取得しています。例:GET localhost:8080 / app.component.html /?v = 0.0.1-alpha 404(Not Found)理由は何ですか?
神保

@ Rikku121いいえ、ありません。実際には、URLに/がありません。私がコメントを投稿したときに誤って追加した可能性があります
Shenbo

14
コードに変更がない場合でも毎回キャッシュを無効にする場合のキャッシュのポイントは何ですか?
アプルフカマラプリ2018

1
ng build --aot --build-optimizer = true --base-href = / <url> /エラーが発生しました---リソース./login.component.html?v=${new Date()を解決できませんでした。 getTime()}
Pranjal Successena

23

各htmlテンプレートで、次のメタタグを上部に追加します。

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

私の理解では、各テンプレートは独立しているため、index.htmlファイルで設定されているメタノーキャッシュルールを継承しません。


4
私たちはしばらくの間webpackに切り替えており、角度付きアプリのキャッシュ破壊を処理します。ただし、ソリューションが機能することを知っておくと便利です。ありがとう
Rikku121

私にとってもそうでした
iniravpatel 2017

4

@Jackの回答と@ranierbitの回答を組み合わせるとうまくいくはずです。

--output-hashingのngビルドフラグを設定します。

ng build --output-hashing=all

次に、このクラスをサービスまたは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);    
    }
}

次に、これをapp.moduleのプロバイダーに追加します。

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

これにより、クライアントマシンのライブサイトでのキャッシュの問題が回避されます。


3

index.htmlがブラウザーによってキャッシュされる、または中間のcdn /プロキシーによってさらにトリッキーになるという同様の問題がありました(F5は役に立ちません)。

私は、クライアントが最新のindex.htmlバージョンを持っていることを100%検証するソリューションを探しました。幸いにも、Henrik Peinarがこのソリューションを見つけました。

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

このソリューションは、クライアントがブラウザーを数日間開いたままである場合も解決します。クライアントは、定期的に更新を確認し、新しいバージョンが展開されている場合は再ロードします。

解決策は少しトリッキーですが、魅力のように機能します:

  • という事実を使用して ng cli -- prodmain。[hash] .jsと呼ばれるハッシュファイルを生成ます
  • そのハッシュを含むversion.jsonファイルを作成します
  • version.jsonをチェックし、必要に応じて再読み込みする角度サービスVersionCheckServiceを作成します。
  • デプロイ後に実行されるjsスクリプトはversion.jsonを作成し、角度サービスのハッシュを置き換えるため、手動の作業は必要ありませんが、post-build.jsを実行することに注意してください

Henrik PeinarソリューションはAngular 4用だったので、小さな変更があったため、ここにも修正済みのスクリプトを配置します。

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

メインの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);
        }
    }

}

魔法になるpost-buildスクリプト、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);
  });

(新しい)ビルドフォルダーにスクリプトを置くだけで、node ./build/post-build.jsdistフォルダーを使用してスクリプトを実行します。ng build --prod


1

HTTPヘッダーを使用してクライアントキャッシュを制御できます。これはどのWebフレームワークでも機能します。

これらのヘッダーのディレクティブを設定して、キャッシュを有効/無効にする方法とタイミングをきめ細かく制御できます。

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (とても良いもの)
  • Pragma (古いブラウザをサポートしたい場合)

すべてのコンピュータシステムにおいて、優れたキャッシングは優れていますが、非常に複雑です。詳細については、https://helmetjs.github.io/docs/nocache/#the-headersをご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.