React 应用的缓存清除
简而言之,对你的应用使用语义化版本控制(SemVer)meta.json ,并在每次构建时生成一个不会被浏览器缓存的文件。当版本不匹配时,使缓存失效并强制重新加载应用。
注:本文中的示例和解释均基于 React,但该策略适用于任何 Web 应用程序/框架。
缓存固然好用,但缓存失效一直是个难题。清除浏览器中加载的 Web 应用的缓存已经够难了,清除保存到主屏幕的 Web 应用的缓存就更难了。
缓存简介 —
服务器缓存: Web 服务器会在用户首次请求资源时将其缓存。从第二次请求开始,资源将从服务器缓存中直接提供。这其中还有很多细节——例如 CDN、源服务器、边缘服务器等等,但我们这里就不一一赘述了。由于我们可以控制服务器,因此清除服务器缓存非常简单,每次部署新版本时,我们都可以自动或手动清除旧缓存。
浏览器缓存:浏览器也会以自己的方式缓存资源。当用户首次在浏览器中加载网站时,浏览器会将一些资源(通常是图片、JS 和 CSS 等资源)缓存到本地。下次用户访问同一网站时,浏览器会直接从本地缓存中提供资源。由于我们无法控制用户的浏览器,因此清除浏览器缓存一直以来都比较麻烦。虽然有了缓存头以及像 webpack 这样的构建工具会在每次构建时生成唯一的代码块,缓存管理变得稍微容易了一些,但仍然存在一些问题。
以下是浏览器缓存的一些陷阱——
- 如果网站在同一标签页中刷新,浏览器有时会忽略缓存验证——如果用户固定了标签页,即使服务器缓存被清除,网站也很有可能从浏览器缓存加载。
- 如果您的应用注册了Service Worker ,那么只有当用户在新标签页中打开网站时,Service Worker缓存才会失效。如果标签页从未关闭,用户将永远被 Service Worker 缓存所困。
- 如果用户将网站添加到手机/平板电脑的主屏幕,那么只有当用户主动退出应用时,浏览器缓存才会失效——这几乎和在浏览器中一直打开同一个标签页一样。我知道有些人几个月都不退出主屏幕应用。
理想情况下,缓存有助于加快网站加载速度。禁用缓存并非解决之道,而且这种方法也不可靠,因为你无法控制用户浏览器的行为。我们希望找到一种方法,在每次将新版本的应用程序部署到服务器时,都能清除浏览器或 Service Worker 的缓存。
一种简单而有效的方法
- SemVer你的部署
- 将应用版本打包到应用中
meta.json每次构建时生成一个包含应用程序版本的文件meta.json加载时获取并比较版本- 版本不匹配时强制清除缓存并重新加载
SemVer你的部署
使用SemVer对所有部署进行版本控制。我个人使用以下三个 npm 命令,它们会自动递增包版本,并创建一个包含相应版本标签的 Git 提交。
npm version patch—仅包含错误修复的版本npm version minor—适用于包含新功能(无论是否修复错误)的版本npm version major—适用于重大版本发布或重大功能更新
记得推送你的提交时要添加--tag属性——git push origin master --tags
将应用版本打包到应用中
在 webpack 构建期间(或相关构建工具)解析软件包版本,并在应用程序中设置一个全局变量,以便您可以方便地在浏览器控制台中检查版本,并使用该变量与最新版本进行比较。
import packageJson from '{root-dir}/package.json';
global.appVersion = packageJson.version;
设置完成后,您可以通过在浏览器控制台中输入来检查应用程序版本appVersion。
meta.json每次构建时生成一个包含应用程序版本的文件
运行脚本,meta.json在public应用程序目录中生成一个文件。
添加一个prebuildnpm 脚本,该脚本将meta.json在每次执行之前生成该文件build。
/* package.json */
{
"scripts": {
"generate-build-version": "node generate-build-version",
"prebuild": "npm run generate-build-version",
// other scripts
}
}
/* generate-build-version.js */
const fs = require('fs');
const packageJson = require('./package.json');
const appVersion = packageJson.version;
const jsonData = {
version: appVersion
};
var jsonContent = JSON.stringify(jsonData);
fs.writeFile('./public/meta.json', jsonContent, 'utf8', function(err) {
if (err) {
console.log('An error occured while writing JSON Object to meta.json');
return console.log(err);
}
console.log('meta.json file has been saved with latest version number');
});
每次构建完成后,部署应用后,meta.json即可通过路径访问该/meta.json文件,并像使用 REST 端点一样获取 JSON 数据。由于浏览器不会缓存 XHR 请求,因此该文件不会被浏览器缓存。meta.json即使您的 bundle 文件被缓存,您也始终能获取到最新文件。
因此,如果appVersion您的 bundle 文件中的值小于 中的值version,meta.json那么我们就知道浏览器缓存已过期,需要使其失效。
你可以使用此脚本来比较语义版本——
// version from `meta.json` - first param
// version in bundle file - second param
const semverGreaterThan = (versionA, versionB) => {
const versionsA = versionA.split(/\./g);
const versionsB = versionB.split(/\./g);
while (versionsA.length || versionsB.length) {
const a = Number(versionsA.shift());
const b = Number(versionsB.shift());
// eslint-disable-next-line no-continue
if (a === b) continue;
// eslint-disable-next-line no-restricted-globals
return a > b || isNaN(b);
}
return false;
};
你也可以在我的GitHub 示例中找到这段代码。
meta.json加载时获取并比较版本
当App挂载完成后,获取meta.json并比较当前版本与服务器上的最新版本。
当版本不匹配时=> 强制清除缓存并重新加载;
当版本相同时 => 渲染应用程序的其余部分。
我开发了一个CacheBuster组件,可以强制清除缓存并重新加载网站。该逻辑适用于大多数网站,但也可以根据具体应用场景进行调整。
/* CacheBuster component */
import packageJson from '../package.json';
global.appVersion = packageJson.version;
const semverGreaterThan = (versionA, versionB) => {
// code from above snippet goes here
}
export default class CacheBuster extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: () => {
console.log('Clearing cache and hard reloading...')
if (caches) {
// Service worker cache should be cleared with caches.delete()
caches.keys().then(function(names) {
for (let name of names) caches.delete(name);
});
}
// delete browser cache and hard reload
window.location.reload(true);
}
};
}
componentDidMount() {
fetch('/meta.json')
.then((response) => response.json())
.then((meta) => {
const latestVersion = meta.version;
const currentVersion = global.appVersion;
const shouldForceRefresh = semverGreaterThan(latestVersion, currentVersion);
if (shouldForceRefresh) {
console.log(`We have a new version - ${latestVersion}. Should force refresh`);
this.setState({ loading: false, isLatestVersion: false });
} else {
console.log(`You already have the latest version - ${latestVersion}. No cache refresh needed.`);
this.setState({ loading: false, isLatestVersion: true });
}
});
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
}
}
我们可以使用这个组件来控制组件CacheBuster中的渲染。App
/* App component */
class App extends Component {
render() {
return (
<CacheBuster>
{({ loading, isLatestVersion, refreshCacheAndReload }) => {
if (loading) return null;
if (!loading && !isLatestVersion) {
// You can decide how and when you want to force reload
refreshCacheAndReload();
}
return (
<div className="App">
<header className="App-header">
<h1>Cache Busting - Example</h1>
<p>
Bundle version - <code>v{global.appVersion}</code>
</p>
</header>
</div>
);
}}
</CacheBuster>
);
}
}
您也可以在这里找到这两个组件的代码——
CacheBuster - CacheBuster.js
应用 - App.js
版本不匹配时强制清除缓存并重新加载
每次应用加载时,我们都会检查最新版本。根据应用版本是否过时,我们可以采用不同的方式清除缓存。
例如,
- 您可以在渲染应用程序之前进行强制重新加载。
- 您可以显示一个模态框/弹出窗口,要求用户点击按钮并触发强制重新加载。
- 应用处于空闲状态时,您可以强制重启。
- 几秒钟后你可以强制重新加载。
setTimeout()
你可以在这个仓库中找到本文中的完整代码以及一个可运行的示例——cache-busting-example
以上就是全部内容。如果您对这种方法有任何反馈(好的或不好的),请在评论区告诉我。
寻宝真有趣!🎉
文章来源:https://dev.to/flexdinesh/cache-busting-a-react-app-22lk