之前的文章介绍的缓存方案是白名单形式,只把需要缓存的内容列举出来进行缓存,这篇文章反向思考使用黑名单模式,黑名单里的不缓存,其他的都缓存,一般不缓存的都是第三方域名下的跨域的资源,也无法缓存。
延迟注册serviceworker(每个页面都要加载):
window.addEventListener('load', function() { if('serviceWorker' in navigator){ // serviceworker.js 文件要放在网站发布的根目录 navigator.serviceWorker.register('/serviceworker.js').then(function (registration) { console.log('Service Worker Registered,register script: serviceworker.js.'); }).catch(function (error) { // registration failed console.log('Registration failed with ' + error); }); } }); // 控制台显示service worker缓存占用情况 if ("storage" in navigator && "estimate" in navigator.storage) { // 检查浏览器是否支持 Web Storage 估计功能 navigator.storage .estimate() .then((estimate) => { // 转换使用量和配额到 MB const usageMB = estimate.usage / 1024 / 1024; const quotaMB = estimate.quota / 1024 / 1024; // 计算使用比例 const proportion = (estimate.usage / estimate.quota) * 100; // 打印结果 console.log( `Using ${usageMB.toFixed(2)} out of ${quotaMB.toFixed( 2 )} MB. The proportion is ${proportion.toFixed(2)}%` ); }) .catch((error) => { // 处理可能的错误 console.error("Error estimating storage:", error); }); } else { // 如果不支持,可以在这里打印一条消息 console.log("Browser does not support storage estimation."); }
这个和之前的一样没有什么大的变化。
然后是serviceworker.js一定是放在网站发布目录的根目录,保证它的作用域最大化。具体的配置发生了变化:
//serviceworker.js 'use strict'; const version = 'v20180608'; //缓存版本号,修改之后缓存会刷新 const __DEVELOPMENT__ = false; //开发模式 const __DEBUG__ = false; //调试模式 const offlineResources = [ //离线断网的时候提供友好界面 '/', '/offline.html', '/offline.svg' ]; const ignoreFetch = [ //这里是需要忽略不缓存的的第三方的域名 /chrome-extension:\/\//, // 建议加上忽略chrome插件的协议 /https?:\/\/ae.bdstatic.com\//, /https?:\/\/msite.baidu.com\//, /https?:\/\/s.bdstatic.com\//, /https?:\/\/timg01.bdimg.com\//, /https?:\/\/zz.bdstatic.com\//, /https?:\/\/jspassport.ssl.qhimg.com\//, /https?:\/\/hm.baidu.com\//, /https?:\/\/sp0.baidu.com\//, /https?:\/\/s.360.cn\//, /https?:\/\/s.ssl.qhres.com\//, /https?:\/\/www.google-analytics.com\//, /https?:\/\/www.googletagmanager.com\//, /.php$/ ]; ////////// // Install ////////// function onInstall(event) { log('install event in progress.'); event.waitUntil(updateStaticCache()); } function updateStaticCache() { return caches .open(cacheKey('offline')) .then((cache) => { return cache.addAll(offlineResources); }) .then(() => { log('installation complete!'); }); } //////// // Fetch //////// function onFetch(event) { const request = event.request; if (shouldAlwaysFetch(request)) { event.respondWith(networkedOrOffline(request)); return; } if (shouldFetchAndCache(request)) { event.respondWith(networkedOrCached(request)); return; } event.respondWith(cachedOrNetworked(request)); } function networkedOrCached(request) { return networkedAndCache(request) .catch(() => { return cachedOrOffline(request) }); } // Stash response in cache as side-effect of network request function networkedAndCache(request) { return fetch(request) .then((response) => { var copy = response.clone(); caches.open(cacheKey('resources')) .then((cache) => { cache.put(request, copy); }); log("(network: cache write)", request.method, request.url); return response; }); } function cachedOrNetworked(request) { return caches.match(request) .then((response) => { log(response ? '(cached)' : '(network: cache miss)', request.method, request.url); return response || networkedAndCache(request) .catch(() => { return offlineResponse(request) }); }); } function networkedOrOffline(request) { return fetch(request) .then((response) => { log('(network)', request.method, request.url); return response; }) .catch(() => { return offlineResponse(request); }); } function cachedOrOffline(request) { return caches .match(request) .then((response) => { return response || offlineResponse(request); }); } function offlineResponse(request) { log('(offline)', request.method, request.url); if (request.url.match(/.(jpg|png|gif|svg|jpeg)(?.*)?$/)) { return caches.match('/offline.svg'); } else { return caches.match('/offline.html'); } } /////////// // Activate /////////// function onActivate(event) { log('activate event in progress.'); event.waitUntil(removeOldCache()); } function removeOldCache() { return caches .keys() .then((keys) => { return Promise.all( // We return a promise that settles when all outdated caches are deleted. keys .filter((key) => { return !key.startsWith(version); // Filter by keys that don't start with the latest version prefix. }) .map((key) => { return caches.delete(key); // Return a promise that's fulfilled when each outdated cache is deleted. }) ); }) .then(() => { log('removeOldCache completed.'); }); } function cacheKey() { return [version, ...arguments].join(':'); } function log() { if (developmentMode()) { console.log("SW:", ...arguments); } } function shouldAlwaysFetch(request) { return __DEVELOPMENT__ || request.method !== 'GET' || ignoreFetch.some(regex => request.url.match(regex)); } function shouldFetchAndCache(request) { return ~request.headers.get('Accept').indexOf('text/html'); } function developmentMode() { return __DEVELOPMENT__ || __DEBUG__; } log("Hello from ServiceWorker land!", version); self.addEventListener('install', onInstall); self.addEventListener('fetch', onFetch); self.addEventListener("activate", onActivate);
注意到上面的配置文件里面有提到一个离线文件offline.html,这里给出一个简单的版本:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="theme-color" content="#242628"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>网络异常 | Powered by chaihongjun.me</title> <style> :root { --wrapper-width-percent: 80%; --wrapper-min-width: 600px; --wrapper-max-width: 1024px; --navbar-height: 48px; --area-gap-size: 16px; --text-gap-size: 4px; --line-height: 24px; --border-radius-size: 4px; --color-dark-1: #353739; --color-dark-2: #242628; --color-dark-3: #111517; --color-medium: #96999b; --color-light-1: #aaacaf; --color-light-2: #d6d7d9; --color-light-3: #f2f4f6; --color-orange: #f57c00; --color-blue: #2196f3; --color-green: #4caf50; --color-purple: #4a148c; --color-shadow: rgba(0, 0, 0, 0.3) } * { -webkit-tap-highlight-color: transparent; box-sizing: border-box; outline: none } @-ms-viewport { width: device-width; } ::selection { background: var(--color-orange); color: var(--color-light-3) } html, body { margin: 0; padding: 0; font-size: 16px; font-weight: 400; background: var(--color-dark-2) } body, button, input, textarea, select { border-radius: var(--border-radius-size); font-family: pingfang sc, helvetica neue, hiragino sans gb, segoe ui, microsoft yahei ui, 微软雅黑, sans-serif } button { background: var(--color-orange); box-shadow: 1px 2px 8px var(--color-shadow); border: none; color: var(--color-light-3); cursor: pointer; font-size: 16px; margin: 1rem 0; padding: calc(var(--text-gap-size) * 2); width: 15rem } button:hover { background: #ff9c20 } button:active { background: #d55c00 } :disabled { background: var(--color-light-1); cursor: no-drop } pre, pre *, code, var { font-family: source code variable, consolas, menlo, monaco, courier new, monospace !important } var, code { display: inline-block } b, strong { font-weight: 600 } a { text-decoration: none; color: var(--color-blue) } body { background: #242628; color: #d6d7d9; padding: 0 1rem; } </style> </head> <body> <h1>网络异常</h1> <p>你的网络似乎遭遇了中断,无法从 <code>chaihongjun.me</code> 获取最新的数据。</p> <p>由于 Service Worker 技术,你之前访问过的页面已被缓存,可以从当前设备上离线访问,但部分资源(图片、样式等)可能无法显示。</p> <button onclick="window.history.back();">返回上一页</button> <p>若已确认你的网络环境正常,可以点击下方的按钮或按浏览器刷新按钮,来重载当前页面。</p> <button onclick="location.reload()">立即重载</button> </body> </html>