/** sw created by xiaogang on 2018/1/12 功能描述:简单的实现缓存 功能! */ /** * sw更新 方案汇总 * * 一、更新 cacheName 等待浏览器重启以后 新的sw 文件接管 (须要配合 activate 事件) * 二、install event 中 执行 self.skipWaiting(); 只须要sw文件有任何的更新 既可实现更新 (其实触发了install事件,从新拉取文件实现更新缓存!cache.addAll(cacheFiles) 不会被fetch拦截代理) * * cacheFiles 更新方案汇总 * 一、永不更新 :除非sw 触发更新了 * 二、实时更新 :增长动态时间戳 永不命中 * 三、定时更新 :timeout 参数控制 (两种方案实现,业务层 或者 sw层.具体看ajax.js) * 四、下次更新 :优先取缓存,同时异步取更新缓存 * */ const dataUrl = new RegExp('\.s?json'); //异步实时更新的数据 规则 const cacheName = 'sw_cache_update'; /** * 须要缓存的 文件列表 * @type {string[]} */ const cacheFiles = [ './script/ajax.js', './script/page.js', './style/page.css' ]; const cacheWhitelist = ['index.html']; /** * sw install 事件(生命周期) * 推荐作缓存 工做!(也能够什么都不作) * * caches.open(cacheName).then() * 缓存相关操做 彻底能够在页面加载等任什么时候候处理。(具体参考 官方文档 或者 cache 目录demos) * 只是单纯的缓存起来,不配合sw.fetch 无法 作拦截代理。因此推荐在install成功以后 再缓存 * */ self.addEventListener('install', InstallEvent => { //sw 更新以后当即 接管替换旧的 sw (无需 activate 事件) self.skipWaiting(); // console.log(InstallEvent); //waitUntil : InstallEvent.waitUntil( //连接 对应的cache ,并进行下载 & 缓存 数据! caches.open(cacheName).then(cache => { cacheKey(cache, 'cacheFiles added before'); return cache.addAll(cacheFiles).then(data => { console.log(data);//undefined cacheKey(cache, 'cacheFiles added after'); }); }) ); }); /** * fetch 事件(生命周期) * 实现 缓存的关键。能够拦截和代理 页面的请求! * * 问题: * 怎么更新!!!! * * */ self.addEventListener('fetch', FetchEvent => { // console.log(FetchEvent); if (dataUrl.test(FetchEvent.request.url)) { //先取缓存 后当即更新缓存 FetchEvent.respondWith( caches.match(FetchEvent.request).then(response => { if (response) { fetchRequest(FetchEvent.request).then(data => { console.log(data); }); return responseTimeout(response, FetchEvent.request) } else { return fetchRequest(FetchEvent.request); } }) ); } else { //优先缓存 FetchEvent.respondWith( caches.match(FetchEvent.request).then(response => { //已经缓存 直接返回 。没有则从新fetch 同时更新到缓存中! return responseTimeout(response, FetchEvent.request) || fetchRequest(FetchEvent.request) }) ); } }); /** * activate 事件(生命周期) * 缓存更新的关键! (缓存管理控制!) * * 一、更新您的服务工做线程 JavaScript 文件。用户导航至您的站点时,浏览器会尝试在后台从新下载定义服务工做线程的脚本文件。若是服务工做线程文件与其当前所用文件存在字节差别,则将其视为“新服务工做线程”。 * 二、新服务工做线程将会启动,且将会触发 install 事件。 * 三、此时,旧服务工做线程仍控制着当前页面,所以新服务工做线程将进入 waiting 状态。 * 四、当网站上当前打开的页面关闭时,旧服务工做线程将会被终止,新服务工做线程将会取得控制权。 * 五、新服务工做线程取得控制权后,将会触发其 activate 事件。 * * //install event中 使用self.skipWaiting(); 可使 sw 更新以后当即 接管替换旧的 sw (无需 activate 事件) */ self.addEventListener('activate', ActivateEvent => { console.log(ActivateEvent); // ActivateEvent.waitUntil( updateCacheName() ); /* * Fixes a corner case in which the app wasn't returning the latest data. * You can reproduce the corner case by commenting out the line below and * then doing the following steps: 1) load app for first time so that the * initial New York City data is shown 2) press the refresh button on the * app 3) go offline 4) reload the app. You expect to see the newer NYC * data, but you actually see the initial data. This happens because the * service worker is not yet activated. The code below essentially lets * you activate the service worker faster. */ return self.clients.claim(); }); /** * 经过 更新cacheName 来 实现更新(即 :删除旧的 caches[cacheName]) * @returns {Promise<any[]>} */ function updateCacheName() { //caches.keys(): 返回 一个 promise return caches.keys().then(cacheNameList => { console.log(cacheNameList); return Promise.all( cacheNameList.map(_cacheName => { console.log(_cacheName); if (_cacheName !== cacheName) { return caches.delete(_cacheName); } }) ); }) } /** * * @param request * @param response */ function updateCache(request, response) { caches.open(cacheName) .then(function (cache) { cache.put(request, response); }); } /** * * @param request * @returns {Promise<Response>} */ function fetchRequest(request) { /** * 在 fetch 请求中添加对 .then() 的回调。 * 得到响应后,执行如下检查: * 确保响应有效。 * 检查并确保响应的状态为 200。 * 确保响应类型为 basic,亦即由自身发起的请求。 这意味着,对第三方资产的请求不会添加到缓存。 * 若是经过检查,则克隆响应。这样作的缘由在于,该响应是 Stream,所以主体只能使用一次。因为咱们想要返回能被浏览器使用的响应,并将其传递到缓存以供使用,所以须要克隆一份副本。咱们将一份发送给浏览器,另外一份则保留在缓存。 * */ return fetch(request).then(response => { // Check if we received a valid response if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // if (cacheWhitelist.indexOf('') >= 0) { } // IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have two streams. updateCache(request, response.clone()); //todo updateCache() 也能够放到 调用以后 自行处理! // fetchRequest(FetchEvent.request).then(data => { // console.log(data); // updateCache(); // }); return responseTimeout(response, request);//response }); } //response 只读 (无法把 timeout 参数写入对应的response中去,只能定义一个全局的变量:有点坑!) let _requestTimeout = {}; /** * 定时 timeout 内不重复调用! * sw 层统一封装 * @param response * @param request * @returns {*} */ function responseTimeout(response, request) { if (!response) { //response 不存在直接 返回获取 服务端最新的 return response } let _url2Obj = url2Obj(request.url); let _timeout = _url2Obj.timeout || 0; let _resTimeout = _requestTimeout[response.url]; let _now = (new Date()).getTime() / 1000; //单位秒 console.log(response.timeout); if (_timeout && _resTimeout) { console.log(request.url); console.log(_now - _resTimeout); // 请求设置 _timeout & response 有更新 timeout (即 不是第一次) if (_now - _resTimeout > _timeout) { // 过时 删除 缓存 caches.open(cacheName) .then(function (cache) { cache.delete(request).then(data => { if (data) { delete _requestTimeout[response.url]; } }); }); } //删除以后当即获取最新的数据 (或者 下次获取 最新的数据) // return null; } else { console.log(response); // 请求没有设置 _timeout 或者 response 没有 timeout (即 第一次请求) // response.timeout = _now; // response.__proto__.timeout = _now; _requestTimeout[response.url] = _now } console.log('------------_requestTimeout--------------'); console.log(_requestTimeout) return response; } /** * 没啥 实际做用。 * @param cache * @param tips */ function cacheKey(cache, tips) { cache.keys().then(cacheKeyList => { console.log(`-------caches[${cacheName}]------${tips}--------------`); console.log(cacheKeyList);//request 对象 }); } function url2Obj(str) { if (!str) { //单页面 hash 模式下 search =''; str = location.search || location.hash || location.href; } var query = {}; str.replace(/([^?&=]*)=([^?&=]*)/g, function (m, a, d) { if (typeof query[a] !== 'undefined') { query[a] += ',' + decodeURIComponent(d); } else { query[a] = decodeURIComponent(d); } }); return query; }
实现pwa只需添加 manifest.json 配置文件(记得是域名根目录下)css