"serviceWorker" in navigator && window.addEventListener("load", function () { var e = location.pathname.match(/\/news\/[a-z]{1,}\//)[0] + "article-sw.js?v=08494f887a520e6455fa"; navigator.serviceWorker.register(e).then(function (n) { n.onupdatefound = function () { var e = n.installing; e.onstatechange = function () { switch (e.state) { case "installed": navigator.serviceWorker.controller ? console.log("New or updated content is available.") : console.log("Content is now available offline!"); break; case "redundant": console.error("The installing service worker became redundant.") } } } }). catch(function (e) { console.error("Error during service worker registration:", e) }) })
var addDirectoryIndex = function (originalUrl, index) { var url = new URL(originalUrl); if (url.pathname.slice(-1) === '/') { url.pathname += index; } return url.toString(); }; var cleanResponse = function (originalResponse) { // If this is not a redirected response, then we don't have to do anything. if (!originalResponse.redirected) { return Promise.resolve(originalResponse); } // Firefox 50 and below doesn't support the Response.body stream, so we may // need to read the entire body to memory as a Blob. var bodyPromise = 'body' in originalResponse ? Promise.resolve(originalResponse.body) : originalResponse.blob(); return bodyPromise.then(function (body) { // new Response() is happy when passed either a stream or a Blob. return new Response(body, { headers: originalResponse.headers, status: originalResponse.status, statusText: originalResponse.statusText }); }); }; var createCacheKey = function (originalUrl, paramName, paramValue, dontCacheBustUrlsMatching) { // Create a new URL object to avoid modifying originalUrl. var url = new URL(originalUrl); // If dontCacheBustUrlsMatching is not set, or if we don't have a match, // then add in the extra cache-busting URL parameter. if (!dontCacheBustUrlsMatching || !(url.pathname.match(dontCacheBustUrlsMatching))) { url.search += (url.search ? '&' : '') + encodeURIComponent(paramName) + '=' + encodeURIComponent(paramValue); } return url.toString(); }; var isPathWhitelisted = function (whitelist, absoluteUrlString) { // If the whitelist is empty, then consider all URLs to be whitelisted. if (whitelist.length === 0) { return true; } // Otherwise compare each path regex to the path of the URL passed in. var path = (new URL(absoluteUrlString)).pathname; return whitelist.some(function (whitelistedPathRegex) { return path.match(whitelistedPathRegex); }); }; var stripIgnoredUrlParameters = function (originalUrl, ignoreUrlParametersMatching) { var url = new URL(originalUrl); // Remove the hash; see https://github.com/GoogleChrome/sw-precache/issues/290 url.hash = ''; url.search = url.search.slice(1) // Exclude initial '?' .split('&') // Split into an array of 'key=value' strings .map(function (kv) { return kv.split('='); // Split each 'key=value' string into a [key, value] array }) .filter(function (kv) { return ignoreUrlParametersMatching.every(function (ignoredRegex) { return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes. }); }) .map(function (kv) { return kv.join('='); // Join each [key, value] array into a 'key=value' string }) .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each return url.toString(); };
var hashParamName = '_sw-precache'; //定义需要缓存的url列表 var urlsToCacheKeys = new Map( precacheConfig.map(function (item) { var relativeUrl = item[0]; var hash = item[1]; var absoluteUrl = new URL(relativeUrl, self.location); var cacheKey = createCacheKey(absoluteUrl, hashParamName, hash, false); return [absoluteUrl.toString(), cacheKey]; }) ); //把cache中的url提取出来,进行去重操作 function setOfCachedUrls(cache) { return cache.keys().then(function (requests) { //提取url return requests.map(function (request) { return request.url; }); }).then(function (urls) { //去重 return new Set(urls); }); } //sw安装阶段 self.addEventListener('install', function (event) { event.waitUntil( //首先尝试取出存在客户端cache中的数据 caches.open(cacheName).then(function (cache) { return setOfCachedUrls(cache).then(function (cachedUrls) { return Promise.all( Array.from(urlsToCacheKeys.values()).map(function (cacheKey) { //如果需要缓存的url不在当前cache中,则添加到cache if (!cachedUrls.has(cacheKey)) { //设置same-origin是为了兼容旧版本safari中其默认值不为same-origin, //只有当URL与响应脚本同源才发送 cookies、 HTTP Basic authentication 等验证信息 var request = new Request(cacheKey, { credentials: 'same-origin' }); return fetch(request).then(function (response) { //通过fetch api请求资源 if (!response.ok) { throw new Error('Request for ' + cacheKey + ' returned a ' + 'response with status ' + response.status); } return cleanResponse(response).then(function (responseToCache) { //并设置到当前cache中 return cache.put(cacheKey, responseToCache); }); }); } }) ); }); }).then(function () { //强制跳过等待阶段,进入激活阶段 return self.skipWaiting(); }) ); }); self.addEventListener('activate', function (event) { //清除cache中原来老的一批相同key的数据 var setOfExpectedUrls = new Set(urlsToCacheKeys.values()); event.waitUntil( caches.open(cacheName).then(function (cache) { return cache.keys().then(function (existingRequests) { return Promise.all( existingRequests.map(function (existingRequest) { if (!setOfExpectedUrls.has(existingRequest.url)) { //cache中删除指定对象 return cache.delete(existingRequest); } }) ); }); }).then(function () { //self相当于webworker线程的当前作用域 //当一个 service worker 被初始注册时,页面在下次加载之前不会使用它。claim() 方法会立即控制这些页面 //从而更新客户端上的serviceworker return self.clients.claim(); }) ); });
self.addEventListener('fetch', function (event) { if (event.request.method === 'GET') { // 标识位,用来判断是否需要缓存 var shouldRespond; // 对url进行一些处理,移除一些不必要的参数 var url = stripIgnoredUrlParameters(event.request.url, ignoreUrlParametersMatching); // 如果该url不是我们想要缓存的url,置为false shouldRespond = urlsToCacheKeys.has(url); // 如果shouldRespond未false,再次验证 var directoryIndex = 'index.html'; if (!shouldRespond && directoryIndex) { url = addDirectoryIndex(url, directoryIndex); shouldRespond = urlsToCacheKeys.has(url); } // 再次验证,判断其是否是一个navigation类型的请求 var navigateFallback = ''; if (!shouldRespond && navigateFallback && (event.request.mode === 'navigate') && isPathWhitelisted([], event.request.url)) { url = new URL(navigateFallback, self.location).toString(); shouldRespond = urlsToCacheKeys.has(url); } // 如果标识位为true if (shouldRespond) { event.respondWith( caches.open(cacheName).then(function (cache) { //去缓存cache中找对应的url的值 return cache.match(urlsToCacheKeys.get(url)).then(function (response) { //如果找到了,就返回value if (response) { return response; } throw Error('The cached response that was expected is missing.'); }); }).catch(function (e) { // 如果没找到则请求该资源 console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e); return fetch(event.request); }) ); } } });
这种策略的意思是当请求的路由有对应的Cache缓存结果就直接返回,在返回Cache缓存结果的同时会在后台发起网络请求拿到请求结果并更新Cache缓存,如果本来就没有Cache缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略,能保证用户最快速的拿到请求的结果。但是也有一定的缺点,就是还是会有网络请求占用了用户的网络带宽。可以像如下的方式使用State While Revalidate策略:
1 2 3 4 5 6 7 8 9 10 11 12 13
workbox.routing.registerRoute( new RegExp('https://edu-cms\.nosdn\.127\.net/topics/'), workbox.strategies.staleWhileRevalidate({ //cache名称 cacheName: 'lf-sw:static', plugins: [ new workbox.expiration.Plugin({ //cache最大数量 maxEntries: 30 }) ] }) );