Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也能够在网络可用时做为浏览器和网络间的代理。它们旨在(除其余以外)使得可以建立有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采起适当的动做。他们还容许访问推送通知和后台同步API。(引用自:连接)html
简单的来讲,ServiceWorker(后文简称sw)运行在页面后台,使用了sw的页面能够利用sw来拦截页面发出的请求,同时配合CacheAPI能够将请求缓存到客户本地linux
所以咱们能够:webpack
可是也存在着一些问题ios
IE全面扑街,pc上兼容性不太好,移动端安卓支持良好,ios要12+。但考虑到sw并不会影响的页面的正常运行,因此项目上仍是能投入生产的。git
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sw demo</title> </head> <body> </body> <script src="index.js"></script> <script> if(navigator.serviceWorker){ navigator.serviceWorker.register('sw.js').then(function(reg){ if(reg.installing){ console.log('client-installing'); }else if(reg.active){ console.log('client-active') } }) } </script> </html>
document.body.innerHTML="hello world!";
var cacheStorageKey = 'one'; var cacheList = [ "index.html", "index.js" ] self.addEventListener('install', function (e) { console.log('sw-install'); self.skipWaiting(); }) self.addEventListener('activate', function (e) { console.log('sw-activate'); caches.open(cacheStorageKey).then(function (cache) { cache.addAll(cacheList) }) var cacheDeletePromises = caches.keys().then(cacheNames => { return Promise.all(cacheNames.map(name => { if (name !== cacheStorageKey) { // if delete cache,we should post a message to client which can trigger a callback function console.log('caches.delete', caches.delete); var deletePromise = caches.delete(name); send_message_to_all_clients({ onUpdate: true }) return deletePromise; } else { return Promise.resolve(); } })); }); e.waitUntil( Promise.all([cacheDeletePromises] ).then(() => { return self.clients.claim() }) ) }) self.addEventListener('fetch', function (e) { e.respondWith( caches.match(e.request).then(function (response) { if (response != null) { console.log(`fetch:${e.request.url} from cache`); return response } else { console.log(`fetch:${e.request.url} from http`); return fetch(e.request.url) } }) ) })
这样就完成了一个简单的sw页面了,如今经过服务器访问页面html、js资源将直接从客户端本地读取,实现页面的快速打开和离线访问github
sw应用的生命周期我简单抽象为三种web
名称 | installing | active |
---|---|---|
安装 | 触发 | 不触发 |
活动 | 不触发 | 触发 |
更新 | 不触发 | 触发 |
名称 | install | activate | fetch |
---|---|---|---|
安装 | 触发 | 触发 | 不触发 |
活动 | 不触发 | 不触发 | 触发 |
更新 | 触发 | 触发 | 不触发 |
总结一下:npm
通讯方面我以前有翻译过文章,连接地址,你们感兴趣能够看看。这里我直接展现把封装好的通讯接口接口浏览器
有了通讯接口,咱们就能够优化不少事情,比方说在 cacheStorageKey发生变化的时候通知页面给予客户必定的响应缓存
function send_message_to_sw(msg){ return new Promise(function(resolve, reject){ // Create a Message Channel var msg_chan = new MessageChannel(); // Handler for recieving message reply from service worker msg_chan.port1.onmessage = function(event){ if(event.data.error){ reject(event.data.error); }else{ resolve(event.data); } }; // Send message to service worker along with port for reply navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2]); }); }
function send_message_to_client(client, msg){ return new Promise(function(resolve, reject){ var msg_chan = new MessageChannel(); msg_chan.port1.onmessage = function(event){ if(event.data.error){ reject(event.data.error); }else{ resolve(event.data); } }; client.postMessage(msg, [msg_chan.port2]); }); } function send_message_to_all_clients(msg){ clients.matchAll().then(clients => { clients.forEach(client => { send_message_to_client(client, msg).then(m => console.log("SW Received Message: "+m)); }) }) }
上述的作法须要事先写好cacheList,有必定的维护量,如今介绍一种不须要维护cacheList的作法:
self.addEventListener('fetch', function (e) { e.respondWith( caches.match(e.request).then(res => { return res || fetch(e.request) .then(res => { const resClone = res.clone(); caches.open(cacheStorageKey).then(cache => { cache.put(e.request, resClone); }) return res; }) }) ) });
这样作的话缓存资源的操做将从activate转移到fetch事件内,fetch事件先判断有没有缓存,没有缓存的话将发出对应的请求并进行缓存
这样的作法的缺点是没法在首次加载页面的时候就完成静态化,由于sw的安装声明周期是不会触发sw的fetch事件的。
针对一些页面渲染结果与url参数有关的状况,上述的架构没法完成对应的本地化需求。以前的作法是在cacheList加入了入口页面的地址,没法适应带动态参数url的状况。
具体作法在动态缓存资源文件章节有描述,再也不重复描述。
navigator.serviceWorker.register(file).then(function (reg) { if (reg.installing) { //send_message_to_sw({pageUrl:location.href}) } if (reg.active) { send_message_to_sw({pageUrl:location.href}) } return reg; })
self.addEventListener('message',function(e){ var data=e.data; if(data.pageUrl){ addPage(data.pageUrl) } }) function addPage(page){ caches.open(cacheStorageKey).then(function(cache){ cache.add(page); }) }
在客户端的active发消息给sw,sw就可以获取到对应的页面url进行缓存。
注:客户端的installing事件无法使用消息接口,你们能够在sw的activate事件向客户端发出一个消息请求获取当前页面url
sw文件至少要与入口页面文件在同一目录下,如:
笔者在这里踩了好久的坑...
介绍一个笔者写的webpack的sw插件,在弄sw页面的时候很方便,github地址
npm install --save-dev webpack-sw-plugin
const WebpackSWPlugin = require('webpack-sw-plugin'); module.exports = { // entry // output plugins:[ new WebpackSWPlugin() ] }
import worker from 'webpack-sw-plugin/lib/worker'; worker.register({ onUpdate:()=>{ console.log('client has a new version. page will refresh in 5s....'); setTimeout(function(){ window.location.reload(); },5000) } });