Service Worker
Service Worker
本質上充當Web應用程序與瀏覽器之間的代理服務器,也能夠在網絡可用時做爲瀏覽器和網絡間的代理。它們旨在(除其餘以外)使得可以建立有效的離線體驗,攔截網絡請求並基於網絡是否可用以及更新的資源是否駐留在服務器上來採起適當的動做。他們還容許訪問推送通知和後臺同步API
。javascript
Service Worker
的本質是一個Web Worker
,它獨立於JavaScript
主線程,所以它不能直接訪問DOM
,也不能直接訪問window
對象,可是,Service Worker
能夠訪問navigator
對象,也能夠經過消息傳遞的方式(postMessage)與JavaScript
主線程進行通訊。Service Worker
是一個網絡代理,它能夠控制Web
頁面的全部網絡請求。Service Worker
具備自身的生命週期,使用好Service Worker
的關鍵是靈活控制其生命週期。Service Worker
的做用Web APP
Service Worker
兼容性Service Worker
是現代瀏覽器的一個高級特性,它依賴於fetch API
、Cache Storage
、Promise
等,其中,Cache
提供了Request / Response
對象對的存儲機制,Cache Storage
存儲多個Cache
。css
在瞭解Service Worker
的原理以前,先來看一段Service Worker
的示例:html
self.importScripts('./serviceworker-cache-polyfill.js'); var urlsToCache = [ '/', '/index.js', '/style.css', '/favicon.ico', ]; var CACHE_NAME = 'counterxing'; self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } return fetch(event.request); }) ); }); self.addEventListener('activate', function(event) { var cacheWhitelist = ['counterxing']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); });
下面開始逐段逐段地分析,揭開Service Worker
的神祕面紗:java
polyfill
首先看第一行:self.importScripts('./serviceworker-cache-polyfill.js');
,這裏引入了Cache API的一個polyfill,這個polyfill
支持使得在較低版本的瀏覽器下也可使用Cache Storage API
。想要實現Service Worker
的功能,通常都須要搭配Cache API
代理網絡請求到緩存中。webpack
在Service Worker
線程中,使用importScripts
引入polyfill
腳本,目的是對低版本瀏覽器的兼容。git
Cache Resources List
And Cache Name
以後,使用一個urlsToCache
列表來聲明須要緩存的靜態資源,再使用一個變量CACHE_NAME
來肯定當前緩存的Cache Storage Name
,這裏能夠理解成Cache Storage
是一個DB
,而CACHE_NAME
則是DB
名:github
var urlsToCache = [ '/', '/index.js', '/style.css', '/favicon.ico', ]; var CACHE_NAME = 'counterxing';
Lifecycle
Service Worker
獨立於瀏覽器JavaScript
主線程,有它本身獨立的生命週期。web
若是須要在網站上安裝Service Worker
,則須要在JavaScript
主線程中使用如下代碼引入Service Worker
。json
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { console.log('成功安裝', registration.scope); }).catch(function(err) { console.log(err); }); }
此處,必定要注意sw.js
文件的路徑,在個人示例中,處於當前域根目錄下,這意味着,Service Worker
和網站是同源的,能夠爲當前網站的全部請求作代理,若是Service Worker
被註冊到/imaging/sw.js
下,那隻能代理/imaging
下的網絡請求。瀏覽器
可使用Chrome
控制檯,查看當前頁面的Service Worker
狀況:
安裝完成後,Service Worker
會經歷如下生命週期:
download
)install
)activate
)Service Worker
控制的網站或頁面時,Service Worker
會馬上被下載。以後至少每24
小時它會被下載一次。它可能被更頻繁地下載,不過每24
小時必定會被下載一次,以免不良腳本長時間生效。Service Worker
,在安裝階段,一般須要緩存一些咱們預先聲明的靜態資源,在咱們的示例中,經過urlsToCache
預先聲明。Service Worker
腳本文件,下載成功後,會與前一次已緩存的Service Worker
腳本文件作對比,若是與前一次的Service Worker
腳本文件不一樣,證實Service Worker
已經更新,會觸發activate
事件。完成激活。如圖所示,爲Service Worker
大體的生命週期:
install
在安裝完成後,嘗試緩存一些靜態資源:
self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); });
首先,self.skipWaiting()
執行,告知瀏覽器直接跳過等待階段,淘汰過時的sw.js
的Service Worker
腳本,直接開始嘗試激活新的Service Worker
。
而後使用caches.open
打開一個Cache
,打開後,經過cache.addAll
嘗試緩存咱們預先聲明的靜態文件。
fetch
,代理網絡請求頁面的全部網絡請求,都會經過Service Worker
的fetch
事件觸發,Service Worker
經過caches.match
嘗試從Cache
中查找緩存,緩存若是命中,則直接返回緩存中的response
,不然,建立一個真實的網絡請求。
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } return fetch(event.request); }) ); });
若是咱們須要在請求過程當中,再向Cache Storage
中添加新的緩存,能夠經過cache.put
方法添加,看如下例子:
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // 緩存命中 if (response) { return response; } // 注意,這裏必須使用clone方法克隆這個請求 // 緣由是response是一個Stream,爲了讓瀏覽器跟緩存都使用這個response // 必須克隆這個response,一份到瀏覽器,一份到緩存中緩存。 // 只能被消費一次,想要再次消費,必須clone一次 var fetchRequest = event.request.clone(); return fetch(fetchRequest).then( function(response) { // 必須是有效請求,必須是同源響應,第三方的請求,由於不可控,最好不要緩存 if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // 消費過一次,又須要再克隆一次 var responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); return response; } ); }) ); });
在項目中,必定要注意控制緩存,接口請求通常是不推薦緩存的。因此在我本身的項目中,並無在這裏作動態的緩存方案。
activate
Service Worker
總有須要更新的一天,隨着版本迭代,某一天,咱們須要把新版本的功能發佈上線,此時須要淘汰掉舊的緩存,舊的Service Worker
和Cache Storage
如何淘汰呢?
self.addEventListener('activate', function(event) { var cacheWhitelist = ['counterxing']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); });
Cache
是不被淘汰的。caches.keys()
拿到全部的Cache Storage
,把不在白名單中的Cache
淘汰。caches.delete()
方法。它接收cacheName
做爲參數,刪除該cacheName
全部緩存。sw-precache-webpack-plugin是一個webpack plugin
,能夠經過配置的方式在webpack
打包時生成咱們想要的sw.js
的Service Worker
腳本。
一個最簡單的配置以下:
var path = require('path'); var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const PUBLIC_PATH = 'https://www.my-project-name.com/'; // webpack needs the trailing slash for output.publicPath module.exports = { entry: { main: path.resolve(__dirname, 'src/index'), }, output: { path: path.resolve(__dirname, 'src/bundles/'), filename: '[name]-[hash].js', publicPath: PUBLIC_PATH, }, plugins: [ new SWPrecacheWebpackPlugin( { cacheId: 'my-project-name', dontCacheBustUrlsMatching: /\.\w{8}\./, filename: 'service-worker.js', minify: true, navigateFallback: PUBLIC_PATH + 'index.html', staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], } ), ], }
在執行webpack
打包後,會生成一個名爲service-worker.js
文件,用於緩存webpack
打包後的靜態文件。
一個最簡單的示例。
Service Worker Cache
VS Http Cache
對比起Http Header
緩存,Service Worker
配合Cache Storage
也有本身的優點:
Service Worker
能夠立馬使用緩存返回,但與此同時能夠發起請求,校驗是否有新版本更新。hash
值實在是太難看了。Http
緩存容易被沖掉,也容易過時,而Cache Storage
則不容易被沖掉。也沒有過時時間的說法。Service Worker
能夠實現離線訪問應用。可是缺點是,因爲Service Worker
依賴於fetch API
、依賴於Promise
、Cache Storage
等,兼容性不太好。
本文只是簡單總結了Service Worker
的基本使用和使用Service Worker
作客戶端緩存的簡單方式,然而,Service Worker
的做用遠不止於此,例如:藉助Service Worker
作離線應用、用於作網絡應用的推送(可參考push-notifications)等。
甚至能夠藉助Service Worker
,對接口進行緩存,在我所在的項目中,其實並不會作的這麼複雜。不過作接口緩存的好處是支持離線訪問,對離線狀態下也能正常訪問咱們的Web
應用。
Cache Storage
和Service Worker
老是分不開的。Service Worker
的最佳用法其實就是配合Cache Storage
作離線緩存。藉助於Service Worker
,能夠輕鬆實現對網絡請求的控制,對於不一樣的網絡請求,採起不一樣的策略。例如對於Cache
的策略,其實也是存在多種狀況。例如能夠優先使用網絡請求,在網絡請求失敗時再使用緩存、亦能夠同時使用緩存和網絡請求,一方面檢查請求,一方面有檢查緩存,而後看兩個誰快,就用誰。