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
的策略,其实也是存在多种状况。例如能够优先使用网络请求,在网络请求失败时再使用缓存、亦能够同时使用缓存和网络请求,一方面检查请求,一方面有检查缓存,而后看两个谁快,就用谁。