Как предотвратить кеширование браузера на сайте Angular 2?
В настоящее время мы работаем над новым проектом с регулярными обновлениями, которые ежедневно используются одним из наших клиентов. Этот проект разрабатывается с использованием angular 2, и мы сталкиваемся с проблемами кеша, то есть наши клиенты не видят последних изменений на своих машинах.
В основном файлы html/css для файлов js, похоже, обновляются правильно, не принося больших проблем.
Ответы
Ответ 1
Найден способ сделать это, просто добавьте запрос для загрузки ваших компонентов, например:
@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
Надеюсь, что это поможет
Ответ 2
angular-cli решает эту проблему блестяще, предоставляя флаг --output-hashing
для команды build. Пример использования:
ng build --output-hashing=all
Комплектация & 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, оно работает великолепно и не требует никаких изменений кода или дополнительных инструментов.
Ответ 3
В каждом 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
У меня была похожая проблема с кешированием index.html браузером или более хитрыми средними cdn/proxies (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, и заменяет хэш в angular service, поэтому ручная работа не требуется, но выполняется 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.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 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 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 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
после сборки папки dist с помощью ng build --prod
Ответ 5
Вы можете контролировать кеш клиента с помощью заголовков HTTP. Это работает в любом веб-фреймворке.
Вы можете установить директивы этих заголовков, чтобы иметь точный контроль над тем, как и когда включать | отключать кеш:
Cache-Control
Surrogate-Control
Expires
ETag
(очень хороший)
Pragma
(если вы хотите поддерживать старые браузеры)
Хорошее кэширование - это хорошо, но очень сложно во всех компьютерных системах. Посмотрите на https://helmetjs.github.io/docs/nocache/#the-headers для получения дополнительной информации.
Ответ 6
Комбинация ответа @Jack и ответа @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
]
Это должно предотвратить проблемы с кэшированием на живых сайтах для клиентских компьютеров.