我们目前正在开发一个具有定期更新的新项目,我们的一位客户每天都会使用该更新。这个项目是使用angular 2开发的,我们面临着缓存问题,即我们的客户没有看到他们机器上的最新变化。
主要是js文件的html / css文件似乎已正确更新而不会带来太多麻烦。
ng build
,添加-prod
标签会将哈希添加到生成的文件名中。这迫使所有东西重新加载index.html
。这篇github帖子对重新加载有一些提示。
我们目前正在开发一个具有定期更新的新项目,我们的一位客户每天都会使用该更新。这个项目是使用angular 2开发的,我们面临着缓存问题,即我们的客户没有看到他们机器上的最新变化。
主要是js文件的html / css文件似乎已正确更新而不会带来太多麻烦。
ng build
,添加-prod
标签会将哈希添加到生成的文件名中。这迫使所有东西重新加载index.html
。这篇github帖子对重新加载有一些提示。
Answers:
angular-cli通过为build 命令提供--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.html
在ng build
缓存破坏.js
文件后仍然保留缓存。
至此,我将介绍如何在所有浏览器中控制网页缓存?
找到了一种方法,只需添加一个查询字符串即可加载您的组件,如下所示:
@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
希望这可以帮助
templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
在每个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文件中设置的meta no caching规则。
@Jack的答案和@ranierbit的答案应该可以解决问题。
为--output-hashing设置ng build标志,因此:
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
]
这样可以防止在客户端计算机的活动站点上缓存问题
我在浏览器中缓存index.html时遇到了类似的问题,或者在中间的CDN /代理服务器中比较棘手(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由于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
您可以使用HTTP标头控制客户端缓存。这适用于任何Web框架。
您可以将指令设置为以下标头,以对如何以及何时启用|禁用缓存进行精细控制:
Cache-Control
Surrogate-Control
Expires
ETag
(很好)Pragma
(如果您想支持旧的浏览器)在所有计算机系统中,良好的缓存都是好的,但是非常复杂。请查看https://helmetjs.github.io/docs/nocache/#the-headers了解更多信息。