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) } });