Làm cách nào để ngăn chặn bộ nhớ cache của Trình duyệt trên trang Angular 2?


104

Chúng tôi hiện đang thực hiện một dự án mới với các bản cập nhật thường xuyên được một trong những khách hàng của chúng tôi sử dụng hàng ngày. Dự án này đang được phát triển bằng cách sử dụng angle 2 và chúng tôi đang đối mặt với các vấn đề về bộ nhớ cache, đó là khách hàng của chúng tôi không thấy những thay đổi mới nhất trên máy của họ.

Chủ yếu là các tệp html / css cho các tệp js dường như được cập nhật đúng cách mà không gây ra nhiều khó khăn.


2
Câu hỏi rất hay. Tôi có cùng một vấn đề. Cách tốt nhất để giải quyết vấn đề này là gì? Điều này có khả thi với gulp hoặc bất kỳ công cụ tương tự nào để xuất bản ứng dụng Angular 2 không?
jump4791

2
@ jump4791 Cách tốt nhất là sử dụng webpack và biên dịch dự án bằng cài đặt sản xuất. Tôi hiện đang sử dụng repo này, chỉ cần làm theo các bước và bạn sẽ ổn: github.com/AngularClass/angular2-webpack-starter
Rikku121

Tôi cũng có cùng một vấn đề.
Ziggler

3
Tôi biết đây là một câu hỏi cũ nhưng tôi muốn thêm giải pháp mà tôi đã tìm thấy, cho bất kỳ ai gặp phải vấn đề này. Khi xây dựng với ng build, việc thêm -prodthẻ sẽ thêm hàm băm vào tên tệp đã tạo. Điều này buộc phải tải lại mọi thứ nhưng index.html. Bài đăng trên github này có một số gợi ý về việc tải lại.
Tiz

2
index.html là nguyên nhân gốc rễ. Bởi vì nó không có mã băm, khi nó được lưu vào bộ nhớ cache, mọi thứ khác sẽ được sử dụng từ bộ nhớ cache.
Fiona

Câu trả lời:


178

angle-cli giải quyết vấn đề này bằng cách cung cấp --output-hashingcờ cho lệnh xây dựng (phiên bản 6/7, đối với các phiên bản sau, xem tại đây ). Ví dụ sử dụng:

ng build --output-hashing=all

Bó & Rung cây cung cấp một số chi tiết và ngữ cảnh. Đang chạy ng help build, ghi lại cờ:

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

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

Mặc dù điều này chỉ áp dụng cho người dùng angle-cli , nhưng nó hoạt động tuyệt vời và không yêu cầu bất kỳ thay đổi mã hoặc công cụ bổ sung nào.

Cập nhật

Một số nhận xét đã chỉ ra một cách hữu íchchính xác rằng câu trả lời này thêm một hàm băm vào các .jstệp nhưng không có tác dụng gì index.html. Do đó, hoàn toàn có thể index.htmlvẫn được lưu trong bộ nhớ cache sau khi ng buildbộ nhớ cache xử lý các .jstệp.

Tại thời điểm này, tôi sẽ chuyển sang Làm cách nào để chúng tôi kiểm soát bộ nhớ đệm của trang web, trên tất cả các trình duyệt?


14
Đây là cách thích hợp để làm điều này và phải là câu trả lời được chọn!
jonesy827 13/12/17

1
Điều này không hoạt động cho ứng dụng của chúng tôi. Nó quá xấu templateUrl với một tham số chuỗi truy vấn không làm việc với CLI
DDiVita

8
Điều này sẽ không hoạt động nếu index.html của bạn được trình duyệt lưu vào bộ nhớ cache, do đó sẽ không thấy các tên được băm mới cho tài nguyên javascript của bạn. Tôi nghĩ rằng sự kết hợp giữa điều này và câu trả lời mà @Rossco đưa ra sẽ có ý nghĩa. Nó cũng có ý nghĩa khi làm cho điều này nhất quán với các tiêu đề HTTP được gửi.
stryba

2
@stryba Đây là lý do tại sao bộ nhớ đệm html nên được xử lý khác nhau. Bạn nên chỉ định các tiêu đề phản hồi Cache-Control, Pragma và Expires để không có bộ nhớ đệm nào diễn ra. Điều này rất dễ dàng nếu bạn đang sử dụng khung phụ trợ, nhưng tôi tin rằng bạn cũng có thể xử lý điều này trong các tệp .htaccess cho Apache (idk cách nó hoạt động trong nginx).
OzzyTheGiant

3
Câu trả lời này thêm một hàm băm vào các tệp js, điều này thật tuyệt. Nhưng như stryba đã nói, bạn cũng cần đảm bảo rằng index.html không được lưu trong bộ nhớ cache. Bạn không nên làm điều này với các thẻ meta html, nhưng với điều khiển bộ nhớ cache của tiêu đề phản hồi: no-cache (hoặc các tiêu đề khác cho các chiến lược bộ nhớ đệm ưa thích hơn).
Noppey

34

Đã tìm thấy một cách để thực hiện việc này, chỉ cần thêm một chuỗi truy vấn để tải các thành phần của bạn, như sau:

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

Điều này sẽ buộc máy khách tải bản sao của máy chủ của mẫu thay vì của trình duyệt. Nếu bạn muốn nó chỉ làm mới sau một khoảng thời gian nhất định, bạn có thể sử dụng ISOString này để thay thế:

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

Và xâu chuỗi con một số ký tự để nó chỉ thay đổi sau một giờ, ví dụ:

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

Hi vọng điêu nay co ich


3
Vì vậy, việc triển khai của tôi thực sự không hoạt động. bộ nhớ đệm là một vấn đề kỳ lạ. đôi khi hoạt động và đôi khi không. oh vẻ đẹp của các vấn đề liên tục. Vì vậy, tôi thực sự đã điều chỉnh câu trả lời của bạn thành như vậy:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

Tôi nhận được 404 cho các templateUrl của mình. Ví dụ: TẢI localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (Không tìm thấy) Bạn có biết tại sao không?
Shenbo

@ Rikku121 Không, không. Nó thực sự không có / trong url. Tôi có thể đã vô tình thêm nó vào khi tôi đăng nhận xét
Shenbo

14
Lưu ý của bộ nhớ đệm là gì khi bạn luôn sử dụng bộ nhớ cache ngay cả khi không có thay đổi mã?
Apurv Kamalapuri

1
ng build --aot --build-Optimizer = true --base-href = / <url> / cho lỗi --- Không thể giải quyết tài nguyên ./login.component.html?v=${new Date (). getTime ()}
Pranjal Successena

23

Trong mỗi mẫu html, tôi chỉ thêm các thẻ meta sau ở trên cùng:

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

Theo hiểu biết của tôi, mỗi mẫu đều ở trạng thái tự do do đó nó không kế thừa thiết lập quy tắc meta no caching trong tệp index.html.


4
Chúng tôi đã chuyển sang webpack một thời gian và nó xử lý bộ nhớ cache chặn các ứng dụng góc cạnh của chúng tôi. Thật tốt khi biết giải pháp của bạn hoạt động. Cảm ơn
Rikku121

Nó đã làm cho tôi quá
iniravpatel

4

Sự kết hợp giữa câu trả lời của @ Jack và câu trả lời của @ ranierbit sẽ là một mẹo nhỏ.

Đặt cờ xây dựng cho --output-băm sao cho:

ng build --output-hashing=all

Sau đó, thêm lớp này trong một dịch vụ hoặc trong ứng dụng của bạn.

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

Sau đó, thêm điều này vào các nhà cung cấp của bạn trong app.module của bạn:

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

Điều này sẽ ngăn chặn sự cố bộ nhớ đệm trên các trang web trực tiếp cho máy khách


3

Tôi đã gặp sự cố tương tự với index.html được trình duyệt lưu vào bộ nhớ cache hoặc phức tạp hơn bởi cdn / proxy trung gian (F5 sẽ không giúp bạn).

Tôi đã tìm kiếm một giải pháp xác minh 100% rằng máy khách có phiên bản index.html mới nhất, may mắn là tôi đã tìm thấy giải pháp này bởi Henrik Peinar:

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

Giải pháp cũng giải quyết được trường hợp khách hàng vẫn mở trình duyệt trong nhiều ngày, khách hàng kiểm tra các bản cập nhật theo khoảng thời gian và tải lại nếu phiên bản mới hơn sẽ được triển khai.

Giải pháp là một chút phức tạp nhưng hoạt động như một sự quyến rũ:

  • sử dụng thực tế rằng ng cli -- prod tạo ra các tệp được băm với một trong số chúng được gọi là main. [hash] .js
  • tạo một tệp version.json có chứa hàm băm đó
  • tạo một dịch vụ góc cạnh VersionCheckService để kiểm tra version.json và tải lại nếu cần.
  • Lưu ý rằng tập lệnh js chạy sau khi triển khai sẽ tạo cho bạn cả phiên bản.json và thay thế hàm băm trong dịch vụ góc, vì vậy không cần thao tác thủ công mà chạy post-build.js

Vì giải pháp Henrik Peinar dành cho góc 4 nên đã có những thay đổi nhỏ, tôi cũng đặt các tập lệnh cố định ở đây:

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

thay đổi thành AppComponent chính:

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

}

Tập lệnh hậu xây dựng tạo nên điều kỳ diệu, 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);
  });

chỉ cần đặt tập lệnh vào thư mục xây dựng (mới), chạy tập lệnh bằng cách sử dụng node ./build/post-build.jssau khi xây dựng thư mục dist bằngng build --prod


1

Bạn có thể kiểm soát bộ đệm ẩn của máy khách bằng tiêu đề HTTP. Điều này hoạt động trong bất kỳ khuôn khổ web nào.

Bạn có thể đặt các chỉ thị cho các tiêu đề này để có quyền kiểm soát chi tiết về cách thức và thời điểm bật | tắt bộ nhớ cache:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (rất tốt)
  • Pragma (nếu bạn muốn hỗ trợ các trình duyệt cũ)

Bộ nhớ đệm tốt là tốt, nhưng rất phức tạp, trong tất cả các hệ thống máy tính . Hãy xem https://helmetjs.github.io/docs/nocache/#the-headers để biết thêm thông tin.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.