Як запобігти кешу браузера на сайті Angular 2?


104

В даний час ми працюємо над новим проектом, який регулярно оновлюється, щодня користується один із наших клієнтів. Цей проект розробляється з використанням angular 2, і ми стикаємось із проблемами кешу, тобто наші клієнти не бачать останніх змін на своїх машинах.

В основному файли html / css для файлів js, схоже, оновлюються належним чином, не створюючи особливих проблем.


2
Дуже гарне запитання. У мене така ж проблема. Який найкращий спосіб вирішити цю проблему? Чи можливо це за допомогою глотка або будь-якого подібного інструменту для публікації програми Angular 2?
jump4791

2
@ jump4791 Найкращий спосіб - використовувати webpack та скомпілювати проект за допомогою виробничих налаштувань. На даний момент я використовую це репо, просто виконайте кроки, і ви повинні бути добрими: github.com/AngularClass/angular2-webpack-starter
Rikku121

У мене теж є таке питання.
Ziggler

3
Я знаю, що це давнє запитання, але я хотів додати рішення, яке я знайшов, для всіх, хто трапляється над цим. При побудові за допомогою ng buildдодавання -prodтегу додається хеш до сформованих імен файлів. Це змушує перезавантажити все, крім index.html. Ця публікація у github мала кілька підказок щодо перезавантаження.
Тіз

2
index.html є основною причиною. Оскільки у нього немає хеш-коду, при кешуванні все інше використовується з кешу.
Фіона

Відповіді:


178

angular-cli вирішує це, надаючи --output-hashingпрапор для команди build (версії 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.htmlкеш залишається після того, як ng buildкеш видаляє .jsфайли.

На цьому етапі я перейду до пункту Як ми контролюємо кешування веб-сторінок у всіх браузерах?


14
Це правильний спосіб зробити це, і це має бути обрана відповідь!
jonesy827

1
Це не спрацювало для нашого додатка. Дуже погано, що templateUrl з параметром рядка запиту не працює з CLI
DDiVita

8
Це не спрацює, якщо ваш index.html кешований браузером, отже, нові хешовані імена для ваших ресурсів javascript не відображатимуться. Я думаю, що це поєднання цього та відповіді, яку дав @Rossco, мало б сенс. Також має сенс узгодити це з відправленими заголовками HTTP.
стриба

2
@stryba Ось чому кешування html має оброблятися по-різному. Ви повинні вказати заголовки відповідей Cache-Control, Pragma та Expires, щоб не було кешування. Це легко, якщо ви використовуєте серверну структуру, але я вважаю, що ви також можете впоратися з цим у файлах .htaccess для Apache (ідентифікуйте, як це працює в nginx).
OzzyTheGiant

3
Ця відповідь додає хеш до файлів js, що чудово. Але, як сказав stryba, вам також потрібно переконатися, що index.html не кешовано. Ви повинні робити це не з мета-тегами html, а з кеш-контролем заголовка відповіді: no-cache (або іншими заголовками для більш вигадливих стратегій кешування).
Ноппі

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

І підстроку деяких символів, так що він зміниться лише через годину, наприклад:

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

Сподіваюся, це допомагає


3
Тож моє впровадження насправді не закінчилось результативним. кешування - дивна проблема. іноді працює, а іноді ні. о, краса непостійних питань. Тож я насправді пристосував вашу відповідь як таку:templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Роско

Я отримую 404 для мого templateUrls. Наприклад: GET localhost: 8080 / app.component.html /? V = 0.0.1-alpha 404 (Не знайдено) Будь-яка ідея, чому?
Шенбо,

@ Rikku121 Ні, ні. Це насправді без / в url. Я міг випадково додати його, коли публікую коментар
Шеньбо,

14
Який сенс кешування, коли ви щоразу руйнуєте кеш, навіть коли код не змінюється?
Апурв Камалапурі

1
ng build --aot --build-optimizer = true --base-href = / <url> / видає помилку --- Не вдалося вирішити ресурс ./login.component.html?v=${new Date (). getTime ()}
Pranjal

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

4

Комбінація відповіді @ Джека та відповіді @ ranierbit повинна зробити трюк.

Встановіть прапор побудови ng для --output-hashing так:

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 вам не допоможе).

Я шукав рішення, яке на 100% перевіряє наявність у клієнта останньої версії index.html, на щастя, я знайшов це рішення від Генріка Пейнара:

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

Оскільки рішення Хенріка Пейнара було для кута 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);
  });

просто помістіть скрипт в (нову) папку збірки, запустіть скрипт, використовуючи node ./build/post-build.jsпісля побудови папки distng build --prod


1

Керувати кеш-пам'яттю клієнта можна за допомогою заголовків HTTP. Це працює в будь-якій веб-структурі.

Ви можете встановити директиви цих заголовків, щоб мати чіткий контроль над тим, як і коли вмикати | вимкнути кеш:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (дуже хороший)
  • Pragma (якщо ви хочете підтримувати старі браузери)

Хороший кеш - це добре, але дуже складно в усіх комп’ютерних системах . Подивіться https://helmetjs.github.io/docs/nocache/#the-headers для отримання додаткової інформації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.