จะป้องกัน Browser cache บนเว็บไซต์ Angular 2 ได้อย่างไร?


108

ขณะนี้เรากำลังดำเนินการโครงการใหม่ที่มีการอัปเดตเป็นประจำซึ่งลูกค้ารายหนึ่งของเราจะใช้งานทุกวัน โครงการนี้กำลังพัฒนาโดยใช้ angular 2 และเรากำลังประสบปัญหาแคชนั่นคือลูกค้าของเราไม่เห็นการเปลี่ยนแปลงล่าสุดในเครื่องของพวกเขา

โดยส่วนใหญ่ไฟล์ html / css สำหรับไฟล์ js ดูเหมือนจะได้รับการอัปเดตอย่างถูกต้องโดยไม่ก่อให้เกิดปัญหามากนัก


2
คำถามที่ดีมาก ผมมีปัญหาเดียวกัน. วิธีที่ดีที่สุดในการแก้ปัญหานี้คืออะไร? เป็นไปได้ด้วยอึกหรือเครื่องมือที่คล้ายกันสำหรับเผยแพร่แอปพลิเคชัน Angular 2?
jump4791

2
@ jump4791 วิธีที่ดีที่สุดคือใช้ webpack และรวบรวมโปรเจ็กต์โดยใช้การตั้งค่าการผลิต ฉันกำลังใช้ repo นี้อยู่เพียงทำตามขั้นตอนและคุณควรจะดี: github.com/AngularClass/angular2-webpack-starter
Rikku121

ฉันก็มีปัญหาเดียวกัน
Ziggler

3
ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันต้องการเพิ่มวิธีแก้ปัญหาที่ฉันพบสำหรับใครก็ตามที่เกิดเหตุการณ์นี้ขึ้น เมื่อสร้างด้วยng buildการเพิ่ม-prodแท็กจะเพิ่มแฮชให้กับชื่อไฟล์ที่สร้างขึ้น สิ่งนี้บังคับให้รีโหลดทุกอย่างยกเว้นindex.html. โพสต์ Github นี้มีคำแนะนำบางอย่างในการโหลดซ้ำ
Tiz

2
index.html คือสาเหตุที่แท้จริง เนื่องจากไม่มีแฮชโค้ดเมื่อแคชแล้วทุกอย่างจะถูกใช้จากแคช
Fiona

คำตอบ:


183

angular-cliแก้ไขปัญหานี้โดยระบุ--output-hashingแฟล็กสำหรับ คำสั่งbuild (เวอร์ชัน 6/7 สำหรับเวอร์ชันที่ใหม่กว่าโปรดดูที่นี่ ) ตัวอย่างการใช้งาน:

ng build --output-hashing=all

Bundling & Tree-Shakingให้รายละเอียดและบริบท ทำงาน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.htmlจะยังคงถูกแคชหลังจากที่ng buildแคชหยุด.jsไฟล์

ณ จุดนี้ฉันจะเลื่อนไปที่เราจะควบคุมการแคชหน้าเว็บในเบราว์เซอร์ทั้งหมดได้อย่างไร


14
นี่คือวิธีที่เหมาะสมและควรเป็นคำตอบที่เลือก!
jonesy827

1
สิ่งนี้ใช้ไม่ได้กับแอปของเรา มันแย่เกินไป templateUrl ที่มีพารามิเตอร์สตริงการค้นหาใช้ไม่ได้กับ CLI
DDiVita

8
การดำเนินการนี้จะใช้ไม่ได้หากเบราว์เซอร์แคช index.html ดังนั้นจะไม่เห็นชื่อที่แฮชใหม่สำหรับแหล่งข้อมูลจาวาสคริปต์ของคุณ ฉันคิดว่าการรวมกันของสิ่งนี้และคำตอบที่ @Rossco ให้ไว้จะสมเหตุสมผล นอกจากนี้ยังเหมาะสมที่จะทำให้สอดคล้องกับส่วนหัว HTTP ที่ส่ง
stryba

2
@stryba นี่คือเหตุผลที่การแคช html ควรได้รับการจัดการที่แตกต่างกัน คุณควรระบุส่วนหัวการตอบสนอง Cache-Control, Pragma และ Expires เพื่อไม่ให้เกิดการแคช นี่เป็นเรื่องง่ายถ้าคุณใช้แบ็กเอนด์เฟรมเวิร์ก แต่ฉันเชื่อว่าคุณสามารถจัดการสิ่งนี้ได้ในไฟล์. htaccess สำหรับ Apache (idk ว่ามันทำงานอย่างไรใน nginx)
OzzyTheGiant

3
คำตอบนี้เพิ่มแฮชให้กับไฟล์ js ซึ่งดีมาก แต่อย่างที่คุณ stryba กล่าวคุณต้องตรวจสอบให้แน่ใจว่าไม่ได้แคช index.html คุณไม่ควรทำสิ่งนี้กับเมตาแท็ก html แต่ด้วยการควบคุมแคชของส่วนหัวการตอบสนอง: ไม่มีแคช (หรือส่วนหัวอื่น ๆ สำหรับกลยุทธ์การแคชแบบแฟนซีเพิ่มเติม)
Noppey

35

พบวิธีในการทำเช่นนี้เพียงแค่เพิ่มสตริงการสืบค้นเพื่อโหลดส่วนประกอบของคุณดังนี้:

@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

และสตริงย่อยอักขระบางตัวเพื่อที่จะเปลี่ยนหลังจากผ่านไปหนึ่งชั่วโมงเท่านั้นเช่น:

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

หวังว่านี่จะช่วยได้


3
ดังนั้นการใช้งานของฉันจึงไม่ได้ผล การแคชเป็นปัญหาที่แปลก บางครั้งใช้งานได้และบางครั้งก็ไม่ได้ โอ้ความงดงามของปัญหาที่ไม่ต่อเนื่อง ดังนั้นฉันจึงปรับคำตอบของคุณให้เป็นดังนี้:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

ฉันได้รับ 404 สำหรับ templateUrls ของฉัน ตัวอย่างเช่น GET localhost: 8080 / app.component.html /? v = 0.0.1-alpha 404 (ไม่พบ) คิดว่าทำไม?
Shenbo

@ Rikku121 ไม่ค่ะ จริงๆแล้วมันไม่มี / ใน url ฉันอาจเพิ่มโดยไม่ได้ตั้งใจเมื่อฉันโพสต์ความคิดเห็น
Shenbo

15
อะไรคือจุดสำคัญของการแคชเมื่อคุณหยุดแคชทุกครั้งแม้ว่าจะไม่มีการเปลี่ยนแปลงรหัสก็ตาม
Apurv Kamalapuri

1
ng build --aot --build-optimizer = true --base-href = / <url> / ให้ข้อผิดพลาด --- ไม่สามารถแก้ไขทรัพยากรได้ /login.component.html?v=${new Date () getTime ()}
Pranjal Successena

24

ในเทมเพลต 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


5
เราได้เปลี่ยนมาใช้ webpack มาระยะหนึ่งแล้วและดูแลการป้องกันแคชของแอปเชิงมุมของเรา เป็นเรื่องดีที่ทราบว่าโซลูชันของคุณใช้ได้ผล ขอบคุณ
Rikku121

มันก็ทำเพื่อฉันเช่นกัน
iniravpatel

5

การรวมกันของคำตอบของ @ แจ็คและคำตอบของ @ ranierbit น่าจะเป็นเคล็ดลับ

ตั้งค่าแฟล็ก ng build สำหรับ --output-hashing ดังนั้น:

ng build --output-hashing=all

จากนั้นเพิ่มคลาสนี้ในบริการหรือในไฟล์ 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);    
    }
}

จากนั้นเพิ่มสิ่งนี้ให้กับผู้ให้บริการของคุณในapp.module:

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

สิ่งนี้ควรป้องกันปัญหาการแคชบนไซต์ที่ใช้งานจริงสำหรับเครื่องไคลเอนต์


3

ฉันมีปัญหาคล้ายกันกับ index.html ที่เบราว์เซอร์แคชหรือยุ่งยากมากขึ้นโดย cdn / proxies กลาง (F5 จะไม่ช่วยคุณ)

ฉันมองหาโซลูชันที่ตรวจสอบได้ 100% ว่าลูกค้ามี index.html เวอร์ชันล่าสุด แต่โชคดีที่พบโซลูชันนี้โดย Henrik Peinar:

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

โซลูชันยังแก้ปัญหาในกรณีที่ไคลเอนต์อยู่กับเบราว์เซอร์ที่เปิดเป็นเวลาหลายวันไคลเอ็นต์จะตรวจสอบการอัปเดตตามช่วงเวลาและโหลดซ้ำหากเวอร์ชันที่ใหม่กว่าปรับใช้

วิธีแก้ปัญหาค่อนข้างยุ่งยาก แต่ใช้งานได้เหมือนมีเสน่ห์:

  • ใช้ข้อเท็จจริงที่ng cli -- prodสร้างไฟล์ที่แฮชโดยหนึ่งในนั้นเรียกว่า main. [hash] .js
  • สร้างไฟล์ version.json ที่มีแฮชนั้น
  • สร้างบริการเชิงมุม VersionCheckService ที่ตรวจสอบ version.json และโหลดใหม่หากจำเป็น
  • โปรดทราบว่าสคริปต์ js ที่ทำงานหลังจากการปรับใช้จะสร้างให้คุณทั้ง version.json และแทนที่แฮชในบริการเชิงมุมดังนั้นจึงไม่จำเป็นต้องใช้งานด้วยตนเอง แต่การรัน post-build.js

เนื่องจากโซลูชันของ Henrik Peinar เป็นของเชิงมุม 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.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);
  });

เพียงวางสคริปต์ในโฟลเดอร์build (ใหม่) เรียกใช้สคริปต์โดยใช้node ./build/post-build.jsหลังจากสร้างโฟลเดอร์ dist โดยใช้ng build --prod


1

คุณสามารถควบคุมแคชไคลเอนต์ด้วยส่วนหัว HTTP สิ่งนี้ใช้ได้กับกรอบงานเว็บใด ๆ

คุณสามารถตั้งค่าคำสั่งส่วนหัวเหล่านี้ให้มีการควบคุมแบบละเอียดว่าจะเปิดใช้งานอย่างไรและเมื่อใด | ปิดใช้งานแคช:

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