如何防止浏览器缓存在角度2站点?

我们目前正在进行一个新的项目,定期更新,我们的一个客户每天都在使用。这个项目正在开发使用角2和我们面临的缓存问题,这是我们的客户没有看到他们的机器上的最新变化。

主要是 js 文件的 html/css 文件似乎得到了正确的更新,而没有带来太多麻烦。

144512 次浏览

找到了这样做的方法,只需添加一个查询字符串来加载您的组件,如下所示:

@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

希望这个能帮上忙

在每个 html 模板中,我只在顶部添加以下 meta 标签:

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

在我的理解中,每个模板都是独立的,因此它不会继承 meta,也不会在 index.html 文件中设置缓存规则。

Angle-cli 通过为 建造命令提供一个 --output-hashing标志来解决这个问题(版本6/7,后续版本参见 给你)。示例用法:

ng build --output-hashing=all

捆绑和摇树 提供了一些细节和上下文:

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


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

虽然这只适用于 角斜肌的用户,但是它工作得很出色,不需要任何代码更改或其他工具。

更新

有许多评论指出,这个答案给 .js文件添加了一个散列,但是对 index.html没有任何作用。因此,在 ng build缓存中断 .js文件之后,index.html仍然保持缓存是完全有可能的。

在这一点上,我将遵从 我们如何控制所有浏览器的网页缓存?

我在浏览器缓存 index.html 时遇到过类似的问题,在 cdn/代理中间缓存 index.html 时遇到了更棘手的问题(F5不会帮助您)。

我寻找一个解决方案,100% 验证客户端有最新的 index.html 版本,幸运的是,我找到了这个解决方案由亨里克佩纳:

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

该解决方案还解决了客户端在浏览器打开状态下停留数天的情况,客户端在间隔时间内检查更新,如果部署了新版本,则重新加载。

这个解决方案有点棘手,但很有效:

  • 使用 ng cli -- prod生成散列文件的事实,其中一个文件名为 main
  • 创建一个包含该散列的 version. json 文件
  • 创建一个角度服务 VersionCheckService,该服务检查 version. json 并在需要时重新加载。
  • 请注意,在部署之后运行的 js 脚本会为您创建 version. json 并替换分角服务中的 hash,因此不需要手工操作,只需要在构建之后运行。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;
}
}

更改为主应用程序组件:

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


}

创造奇迹的后构建脚本,后构建.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);
});

只需将脚本放入(新的) 建造文件夹中,在使用 ng build --prod构建 dist 文件夹后,使用 node ./build/post-build.js运行脚本

您可以使用 HTTP 头控制客户端缓存。这在任何 Web 框架中都可以工作。

您可以设置这些头的指令,以便对如何以及何时启用 | 禁用缓存进行细粒度控制:

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag(非常好的一个)
  • Pragma(如果你想支持旧的浏览器)

在所有的计算机系统中,好的缓存是好的,但是非常复杂。

把“ Jack 的答案”和“ ranierbit 的答案”结合起来就可以了。

将 ng build 标志设置为—— output-hash,如下所示:

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
]

这应该可以防止客户端计算机活动站点上的缓存问题

把这个添加到你的 nginx

location ~ /index.html|.*\.json$ {
expires -1;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}


location ~ .*\.css$|.*\.js$ {
add_header Cache-Control 'max-age=31449600'; # one year
}


location / {
try_files $uri $uri/ /index.html?$args;
add_header Cache-Control 'max-age=86400'; # one day
}

当您使用 ngbuild 构建应用程序时,应该使用以下标志:

--outputHashing=all

这是为了启用缓存破坏。

缓存终止 通过使用唯一的文件版本标识符告诉浏览器文件的新版本可用,从而解决了浏览器缓存问题。因此,浏览器不会从缓存中检索旧文件,而是向原始服务器请求新文件。

因此,一种方法是这样运行:

示例(旧版本)

ng build --prod --aot --output-hashing=all

下面是可以在 --output-hashing中传递的选项

No: 没有执行散列 Media: 只向通过[ url | file ]-loader 处理的文件添加哈希 Bundle: 只向输出 bundle 添加散列 All: 向媒体和捆绑包添加散列 最新情况

对于新版本的“角度”(例如“角度10”) ,该命令现在已经更新:

ng build --prod --aot --outputHashing=all

您可以在这里阅读更多关于构建选项标志的信息

如果您不希望在运行 ng build 时附加这些标志,那么您应该在 angular.json 文件的配置中设置它们。

"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
.
.
.
}
}