菜单

行使 瑟维斯 Worker 做二个 PWA 离线网页应用

2019年4月9日 - jQuery

使用 Service Worker 做一个 PWA 离线网页应用

2017/10/09 · JavaScript
· PWA, Service
Worker

初稿出处:
人人网FED博客   

在上1篇《本身是怎样让网址用上HTML伍Manifest》介绍了怎么用Manifest做三个离线网页应用,结果被广泛网上好友嘲笑说这些事物已经被deprecated,移出web标准了,以往被ServiceWorker替代了,不管什么,Manifest的部分思虑依旧得以借用的。作者又将网址升级到了ServiceWorker,借使是用Chrome等浏览器就用ServiceWorker做离线缓存,倘若是Safari浏览器就照旧用Manifest,读者能够打开那一个网址https://fed.renren.com感受一下,断网也是能不荒谬打开。

参考资料
MDN — Service Worker
API

Service Workers: an
Introduction

劳动办事线程生命周期
Service Worker 库克book(收集了瑟维斯Worker的部分实行例子)
理解 Service
Workers

1. 什么是Service Worker

Service Worker是谷歌发起的落到实处PWA(Progressive Web
App)的二个要害剧中人物,PWA是为了缓解古板Web 应用程式的弱项:

(一)未有桌面入口

(二)不能够离线使用

(3)没有Push推送

这Service Worker的具体表现是何许的呢?如下图所示:

图片 1

ServiceWorker是在后台运维的一条服务Worker线程,上航海用体育场地小编开了五个标签页,所以呈现了八个Client,然而不管开多少个页面都唯有三个Worker在负责管理。这一个Worker的工作是把部分财富缓存起来,然后拦截页面包车型地铁呼吁,先看下缓存Curry有未有,假若局地话就从缓存里取,响应200,反之没有的话就走正规的央求。具体来说,ServiceWorker结合Web App Manifest能一挥而就以下工作(那也是PWA的质量评定标准):

图片 2

包含能够离线使用、断网时回来200、能提醒用户把网址添加多少个图标到桌面上等。

自身提示

二. Service Worker的补助景况

Service Worker近来只有Chrome/Firfox/Opera补助:

图片 3

Safari和艾德ge也在准备帮忙瑟维斯 Worker,由于瑟维斯Worker是谷歌(谷歌)着力的1项专业,对于生态相比较封闭的Safari来说也是迫于时局开始准备补助了,在Safari
TP版本,能够见见:

图片 4

在试行功用(Experimental Features)里已经有ServiceWorker的菜单项了,只是尽管打开也是无法用,会提示您还尚无落实:

图片 5

但不论怎么,至少表达Safari已经准备帮助ServiceWorker了。此外仍可以够见见在当年20一柒年11月发表的Safari
1一.0.一版本已经援助WebLacrosseTC了,所以Safari照旧2个上扬的男女。

Edge也准备帮忙,所以Service Worker的前景极度美好。

  1. 动用范围
    Service Worker由于权力很高,只支持https协议恐怕localhost。
    个体觉得Github
    Pages
    是一个很优异的勤学苦练场地。
  2. 储备知识
    ServiceWorker大量使用Promise,不打听的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的施用套路是先挂号一个Worker,然后后台就会运行一条线程,可以在这条线程运维的时候去加载壹些能源缓存起来,然后监听fetch事件,在那几个事件里拦截页面包车型地铁央浼,先看下缓存里有没有,借使有平素回到,不然符合规律加载。也许是1初步不缓存,每一个能源请求后再拷贝一份缓存起来,然后下3回呼吁的时候缓存里就有了。

兼容性

(1)注册1个瑟维斯 Worker

Service Worker对象是在window.navigator里面,如下代码:

JavaScript

window.addEventListener(“load”, function() { console.log(“Will the
service worker register?”); navigator.serviceWorker.register(‘/sw-3.js’)
.then(function(reg){ console.log(“Yes, it did.”); }).catch(function(err)
{ console.log(“No it didn’t. This happened: “, err) }); });

1
2
3
4
5
6
7
8
9
window.addEventListener("load", function() {
    console.log("Will the service worker register?");
    navigator.serviceWorker.register(‘/sw-3.js’)
    .then(function(reg){
        console.log("Yes, it did.");
    }).catch(function(err) {
        console.log("No it didn’t. This happened: ", err)
    });
});

在页面load完事后注册,注册的时候传一个js文件给它,那一个js文件正是ServiceWorker的周转环境,假如无法成功注册的话就会抛至极,如Safari
TP即便有那一个目的,可是会抛极度无法选取,就足以在catch里面处理。那里有个难题是为啥需求在load事件运行呢?因为你要相当运行一个线程,运转之后您只怕还会让它去加载能源,那么些都以内需占用CPU和带宽的,大家应该保障页面能健康加载完,然后再起步我们的后台线程,无法与符合规律的页面加载产生竞争,那一个在低端移动装备意义比较大。

还有有个别亟需注意的是ServiceWorker和Cookie一样是有Path路径的定义的,如若你设定三个cookie如果叫time的path=/page/A,在/page/B那几个页面是不能得到到那么些cookie的,尽管设置cookie的path为根目录/,则装有页面都能收获到。类似地,要是注册的时候使用的js路径为/page/sw.js,那么那个ServiceWorker只好管理/page路径下的页面和能源,而不能处理/api路径下的,所以1般把ServiceWorker注册到超级目录,如上边代码的”/sw-三.js”,那样那一个ServiceWorker就能接管页面包车型地铁装有能源了。

图片 6

(二)瑟维斯 Worker安装和激活

登记完之后,ServiceWorker就会议及展览开设置,那一年会触发install事件,在install事件之中能够缓存1些财富,如下sw-3.js:

JavaScript

const CACHE_NAME = “fed-cache”; this.add伊夫ntListener(“install”,
function(event) { this.skipWaiting(); console.log(“install service
worker”); // 创立和开拓贰个缓存库 caches.open(CACHE_NAME); // 首页 let
cacheResources = [“https://fed.renren.com/?launcher=true"\];
event.waitUntil( // 请求财富并添加到缓存里面去
caches.open(CACHE_NAME).then(cache => {
cache.addAll(cacheResources); }) ); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const CACHE_NAME = "fed-cache";
this.addEventListener("install", function(event) {
    this.skipWaiting();
    console.log("install service worker");
    // 创建和打开一个缓存库
    caches.open(CACHE_NAME);
    // 首页
    let cacheResources = ["https://fed.renren.com/?launcher=true"];
    event.waitUntil(
        // 请求资源并添加到缓存里面去
        caches.open(CACHE_NAME).then(cache => {
            cache.addAll(cacheResources);
        })
    );
});

通过地点的操作,创设和添加了四个缓存库叫fed-cache,如下Chrome控制台所示:

图片 7

ServiceWorker的API基本上都是再次来到Promise对象制止堵塞,所以要用Promise的写法。下面在装置ServiceWorker的时候就把首页的伸手给缓存起来了。在ServiceWorker的周转环境之中它有四个caches的全局对象,这些是缓存的进口,还有3个常用的clients的全局对象,贰个client对应三个标签页。

在ServiceWorker里面能够应用fetch等API,它和DOM是与世隔膜的,未有windows/document对象,不可能直接操作DOM,不恐怕直接和页面交互,在ServiceWorker里面不能够得知当前页面打开了、当前页面包车型的士url是怎样,因为叁个ServiceWorker管理当前打开的多少个标签页,能够通过clients知道全体页面包车型大巴url。还有能够经过postMessage的方法和主页面相互传递音讯和数据,进而做些控制。

install完之后,就会触发Service Worker的active事件:

JavaScript

this.addEventListener(“active”, function(event) { console.log(“service
worker is active”); });

1
2
3
this.addEventListener("active", function(event) {
    console.log("service worker is active");
});

ServiceWorker激活之后就可见监听fetch事件了,大家期待每获得七个财富就把它缓存起来,就无须像上一篇涉嫌的Manifest要求先生成三个列表。

您大概会问,当自家刷新页面包车型客车时候不是又重新挂号安装和激活了二个ServiceWorker?纵然又调了一回注册,但并不会重新挂号,它发现”sw-三.js”这些已经注册了,就不会再登记了,进而不会触发install和active事件,因为脚下ServiceWorker已经是active状态了。当须要立异ServiceWorker时,如变成”sw-四.js”,或许转移sw-三.js的文件内容,就会重复登记,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的ServiceWorker就会被沟通掉,新的ServiceWorker进入active状态,假如不想等到再度开动浏览器能够像上边一样在install里面调skipWaiting:

JavaScript

this.skipWaiting();

1
this.skipWaiting();

Service Worker的包容性

(3)fetch资源后cache起来

如下代码,监听fetch事件做些处理:

JavaScript

this.addEventListener(“fetch”, function(event) { event.respondWith(
caches.match(event.request).then(response => { // cache hit if
(response) { return response; } return
util.fetchPut(event.request.clone()); }) ); });

1
2
3
4
5
6
7
8
9
10
11
12
this.addEventListener("fetch", function(event) {
    event.respondWith(
        caches.match(event.request).then(response => {
            // cache hit
            if (response) {
                return response;
            }
            return util.fetchPut(event.request.clone());
        })
    );
});

先调caches.match看一下缓存里面是还是不是有了,如若有一贯回到缓存里的response,不然的话不奇怪请求财富并把它内置cache里面。放在缓存里财富的key值是Request对象,在match的时候,须求请求的url和header都1致才是一模一样的能源,能够设定第一个参数ignoreVary:

JavaScript

caches.match(event.request, {ignoreVary: true})

1
caches.match(event.request, {ignoreVary: true})

表示1旦请求url相同就以为是同二个能源。

上边代码的util.fetchPut是那样完结的:

JavaScript

let util = { fetchPut: function (request, callback) { return
fetch(request).then(response => { // 跨域的财富直接return if
(!response || response.status !== 200 || response.type !== “basic”) {
return response; } util.putCache(request, response.clone()); typeof
callback === “function” && callback(); return response; }); }, putCache:
function (request, resource) { // 后台不要缓存,preview链接也不用缓存 if
(request.method === “GET” && request.url.indexOf(“wp-admin”) < 0 &&
request.url.indexOf(“preview_id”) < 0) {
caches.open(CACHE_NAME).then(cache => { cache.put(request,
resource); }); } } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let util = {
    fetchPut: function (request, callback) {
        return fetch(request).then(response => {
            // 跨域的资源直接return
            if (!response || response.status !== 200 || response.type !== "basic") {
                return response;
            }
            util.putCache(request, response.clone());
            typeof callback === "function" && callback();
            return response;
        });
    },
    putCache: function (request, resource) {
        // 后台不要缓存,preview链接也不要缓存
        if (request.method === "GET" && request.url.indexOf("wp-admin") < 0
              && request.url.indexOf("preview_id") < 0) {
            caches.open(CACHE_NAME).then(cache => {
                cache.put(request, resource);
            });
        }
    }
};

亟待留意的是跨域的资源不可能缓存,response.status会再次来到0,假设跨域的能源援助COLANDS,那么能够把request的mod改成cors。借使请求战败了,如40四要么是过期等等的,那么也平昔回到response让主页面处理,不然的话表明加载成功,把那个response克隆二个放到cache里面,然后再再次来到response给主页面线程。注意能舒缓存里的财富1般只好是GET,通过POST获取的是不可能缓存的,所以要做个判断(当然你也得以手动把request对象的method改成get),还有把某个个体不指望缓存的财富也做个判断。

诸如此类1旦用户打开过2遍页面,ServiceWorker就设置好了,他刷新页面或许打开首个页面包车型客车时候就可见把请求的能源1壹做缓存,包涵图形、CSS、JS等,只要缓存里有了随便用户在线也许离线都能够平常访问。那样我们本来会有二个难题,那些缓存空间到底有多大?上壹篇咱们提到Manifest也究竟地方存款和储蓄,PC端的Chrome是5Mb,其实那几个说法在新本子的Chrome已经不确切了,在Chrome
陆一版本能够看到地面存款和储蓄的空令月利用景况:

图片 8

里头Cache Storage是指ServiceWorker和Manifest占用的长台湾空中大学小和,上图能够看看总的空间尺寸是20GB,大概是unlimited,所以基本上不用操心缓存会不够用。

1、 生命周期

个体会认识为先清楚一下它的生命周期很重点!在此以前查资料的时候,很多小说壹上来就监听install事件、waiting事件、activate事件……反正本人是壹脸懵逼。

图片 9

Service Worker的生命周期

(4)cache html

地方第(3)步把图纸、js、css缓存起来了,但是1旦把页面html也缓存了,例如把首页缓存了,就会有一个狼狈的标题——ServiceWorker是在页面注册的,但是今后收获页面包车型地铁时候是从缓存取的,每回都是平等的,所以就招致力不从心革新ServiceWorker,如变成sw-5.js,然则PWA又要求大家能缓存页面html。那如何做呢?谷歌(Google)的开发者文书档案它只是提到会存在这几个题材,但并不曾证实怎么消除那么些标题。那个的难点的缓解就须要大家要有二个体制能领会html更新了,从而把缓存里的html给替换掉。

Manifest更新缓存的编写制定是去看Manifest的公文内容有没有发生变化,就算发生变化了,则会去革新缓存,ServiceWorker也是基于sw.js的文书内容有未有发生变化,我们能够借鉴那么些思量,假使请求的是html并从缓存里取出来后,再发个请求获取多少个文件看html更新时间是否产生变化,倘诺产生变化了则印证发生转移了,进而把缓存给删了。所以能够在服务端通过决定这几个文件从而去立异客户端的缓存。如下代码:

JavaScript

this.add伊芙ntListener(“fetch”, function(event) { event.respondWith(
caches.match(event.request).then(response => { // cache hit if
(response) { //借使取的是html,则看发个请求看html是还是不是更新了 if
(response.headers.get(“Content-Type”).indexOf(“text/html”) >= 0) {
console.log(“update html”); let url = new UPRADOL(event.request.url);
util.updateHtmlPage(url, event.request.clone(), event.clientId); }
return response; } return util.fetchPut(event.request.clone()); }) );
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.addEventListener("fetch", function(event) {
 
    event.respondWith(
        caches.match(event.request).then(response => {
            // cache hit
            if (response) {
                //如果取的是html,则看发个请求看html是否更新了
                if (response.headers.get("Content-Type").indexOf("text/html") >= 0) {
                    console.log("update html");
                    let url = new URL(event.request.url);
                    util.updateHtmlPage(url, event.request.clone(), event.clientId);
                }
                return response;
            }
 
            return util.fetchPut(event.request.clone());
        })
    );
});

通过响应头header的content-type是或不是为text/html,假使是的话就去发个请求获取1个文本,依照那一个文件的内容决定是还是不是必要删除缓存,那么些创新的函数util.updateHtmlPage是这么完结的:

JavaScript

let pageUpdateTime = { }; let util = { updateHtmlPage: function (url,
htmlRequest) { let pageName = util.getPageName(url); let jsonRequest =
new Request(“/html/service-worker/cache-json/” + pageName + “.sw.json”);
fetch(jsonRequest).then(response => { response.json().then(content
=> { if (pageUpdateTime[pageName] !== content.updateTime) {
console.log(“update page html”); // 倘若有创新则另行取得html
util.fetchPut(htmlRequest); pageUpdateTime[pageName] =
content.updateTime; } }); }); }, delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let pageUpdateTime = {
 
};
let util = {
    updateHtmlPage: function (url, htmlRequest) {
        let pageName = util.getPageName(url);
        let jsonRequest = new Request("/html/service-worker/cache-json/" + pageName + ".sw.json");
        fetch(jsonRequest).then(response => {
            response.json().then(content => {
                if (pageUpdateTime[pageName] !== content.updateTime) {
                    console.log("update page html");
                    // 如果有更新则重新获取html
                    util.fetchPut(htmlRequest);
                    pageUpdateTime[pageName] = content.updateTime;
                }
            });
        });
    },
    delCache: function (url) {
        caches.open(CACHE_NAME).then(cache => {
            console.log("delete cache " + url);
            cache.delete(url, {ignoreVary: true});
        });
    }
};

代码先去取得二个json文件,三个页面会对应3个json文件,那一个json的始末是如此的:

JavaScript

{“updateTime”:”10/2/2017, 3:23:57 PM”,”resources”: {img: [], css:
[]}}

1
{"updateTime":"10/2/2017, 3:23:57 PM","resources": {img: [], css: []}}

内部根本有贰个updateTime的字段,假若本地内部存款和储蓄器没有那么些页面包车型大巴update提姆e的数量或然是和流行updateTime不均等,则另行去取得
html,然后放到缓存里。接着须求公告页面线程数据产生变化了,你刷新下页面吗。那样就毫无等用户刷新页面才能奏效了。所以当刷新完页面后用postMessage文告页面:

JavaScript

let util = { postMessage: async function (msg) { const allClients =
await clients.matchAll(); allClients.forEach(client =>
client.postMessage(msg)); } }; util.fetchPut(htmlRequest, false,
function() { util.postMessage({type: 1, desc: “html found updated”, url:
url.href}); });

1
2
3
4
5
6
7
8
9
let util = {
    postMessage: async function (msg) {
        const allClients = await clients.matchAll();
        allClients.forEach(client => client.postMessage(msg));
    }
};
util.fetchPut(htmlRequest, false, function() {
    util.postMessage({type: 1, desc: "html found updated", url: url.href});
});

并分明type: 一就象征这是二个立异html的音讯,然后在页面监听message事件:

JavaScript

if(“serviceWorker” in navigator) {
navigator.serviceWorker.addEventListener(“message”, function(event) {
let msg = event.data; if (msg.type === 1 && window.location.href ===
msg.url) { console.log(“recv from service worker”, event.data);
window.location.reload(); } }); }

1
2
3
4
5
6
7
8
9
if("serviceWorker" in navigator) {
    navigator.serviceWorker.addEventListener("message", function(event) {
        let msg = event.data;
        if (msg.type === 1 && window.location.href === msg.url) {
            console.log("recv from service worker", event.data);
            window.location.reload();
        }  
    });
}

接下来当大家必要更新html的时候就创新json文件,那样用户就能来看最新的页面了。只怕是当用户重新启航浏览器的时候会造成ServiceWorker的运维内部存款和储蓄器都被清空了,即存款和储蓄页面更新时间的变量被清空了,那个时候也会重新请求页面。

亟需专注的是,要把这么些json文件的http
cache时间设置成0,那样浏览器就不会缓存了,如下nginx的配备:

JavaScript

location ~* .sw.json$ { expires 0; }

1
2
3
location ~* .sw.json$ {
    expires 0;
}

因为那么些文件是急需实时获取的,无法被缓存,firefox暗许会缓存,Chrome不会,加上http缓存时间为0,firefox也不会缓存了。

再有一种更新是用户更新的,例如用户公布了评论,必要在页面公告service
worker把html缓存删了再也赢得,那是1个扭转的音讯公告:

JavaScript

if (“serviceWorker” in navigator) {
document.querySelector(“.comment-form”).addEventListener(“submit”,
function() { navigator.serviceWorker.controller.postMessage({ type: 1,
desc: “remove html cache”, url: window.location.href} ); } }); }

1
2
3
4
5
6
7
8
9
10
if ("serviceWorker" in navigator) {
    document.querySelector(".comment-form").addEventListener("submit", function() {
            navigator.serviceWorker.controller.postMessage({
                type: 1,
                desc: "remove html cache",
                url: window.location.href}
            );
        }
    });
}

Service Worker也监听message事件:

JavaScript

const messageProcess = { // 删除html index 1: function (url) {
util.delCache(url); } }; let util = { delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const messageProcess = {
    // 删除html index
    1: function (url) {
        util.delCache(url);
    }
};
 
let util = {
    delCache: function (url) {
        caches.open(CACHE_NAME).then(cache => {
            console.log("delete cache " + url);
            cache.delete(url, {ignoreVary: true});
        });
    }
};
 
this.addEventListener("message", function(event) {
    let msg = event.data;
    console.log(msg);
    if (typeof messageProcess[msg.type] === "function") {
        messageProcess[msg.type](msg.url);
    }
});

遵照不一致的新闻类型调差异的回调函数,如若是一的话就是剔除cache。用户公布完评论后会触发刷新页面,刷新的时候缓存已经被删了就会重复去哀告了。

这么就一举成功了实时更新的难点。

1. Parsed

SW是二个JS文件,假使我们要使用三个SW(ServiceWorker),那么我们须要在大家的js代码中登记它,类似于:
navigator.serviceWorker.register('/sw-1.js')

今昔并不须求知道这些主意各类部分的详尽含义,只要知道我们今后在为我们的网页注册1个SW就足以了。

能够看出大家传入的参数是一个JS文件的路子,当浏览器执行到此地的时候,就会到对应的不二等秘书诀下载该文件,然后对该脚本举行分析,如果下载或许解析战败,那么那么些SW就会被放任。

假定条分缕析成功了,那就到了parsed状态。能够进行上面包车型客车行事了。

4. Http/Manifest/Service Worker三种cache的关系

要缓存能够运用三种手段,使用Http
Cache设置缓存时间,也足以用Manifest的Application Cache,还足以用ServiceWorker缓存,假使叁者都用上了会什么呢?

会以Service Worker为预先,因为ServiceWorker把请求拦截了,它起首做处理,倘若它缓存库里有的话一贯重返,未有的话不奇怪请求,就一定于尚未ServiceWorker了,那一年就到了Manifest层,Manifest缓存里假如有个别话就取这么些缓存,如若没有的话就也正是尚未Manifest了,于是就会从Http缓存里取了,假诺Http缓存里也不曾就会发请求去获得,服务端依照Http的etag或许Modified
提姆e可能会回去30四 Not
Modified,不然正常重临200和数据内容。那正是整2个获取的进度。

据此只要既用了Manifest又用ServiceWorker的话应该会导致同多少个财富存了五回。然而足以让援救ServiceWorker的浏览器采取瑟维斯 Worker,而不帮助的选取Manifest.

2. Installing

在installing状态中,SW 脚本中的 install
事件被实践。在力所能及支配客户端此前,install
事件让我们有空子缓存大家需求的持有内容。

譬如说,我们能够先缓存一张图片,那么当SW控制客户端之后,客户点击该链接的图样,大家就能够用SW捕获请求,直接回到该图片的缓存。

若事件中有 event.waitUntil() 方法,则 installing
事件会一贯等到该办法中的 Promise 实现今后才会马到功成;若 Promise
被拒,则设置退步,瑟维斯 Worker 直接进去丢弃(redundant)状态。

5. 行使Web App Manifest添加桌面入口

只顾那里说的是其它1个Manifest,这几个Manifest是1个json文件,用来放网址icon名称等新闻以便在桌面添加三个图标,以及成立一种打开这么些网页如同打开App1样的职能。上边一向说的Manifest是被废弃的Application
Cache的Manifest。

其1Maifest.json文件可以那样写:

JavaScript

{ “short_name”: “人人FED”, “name”: “人人网FED,专注于前者技术”,
“icons”: [ { “src”: “/html/app-manifest/logo_48.png”, “type”:
“image/png”, “sizes”: “48×48” }, { “src”:
“/html/app-manifest/logo_96.png”, “type”: “image/png”, “sizes”: “96×96”
}, { “src”: “/html/app-manifest/logo_192.png”, “type”: “image/png”,
“sizes”: “192×192” }, { “src”: “/html/app-manifest/logo_512.png”,
“type”: “image/png”, “sizes”: “512×512” } ], “start_url”:
“/?launcher=true”, “display”: “standalone”, “background_color”:
“#287fc5”, “theme_color”: “#fff” }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "short_name": "人人FED",
  "name": "人人网FED,专注于前端技术",
  "icons": [
    {
      "src": "/html/app-manifest/logo_48.png",
      "type": "image/png",
      "sizes": "48×48"
    },
    {
      "src": "/html/app-manifest/logo_96.png",
      "type": "image/png",
      "sizes": "96×96"
    },
    {
      "src": "/html/app-manifest/logo_192.png",
      "type": "image/png",
      "sizes": "192×192"
    },
    {
      "src": "/html/app-manifest/logo_512.png",
      "type": "image/png",
      "sizes": "512×512"
    }
  ],
  "start_url": "/?launcher=true",
  "display": "standalone",
  "background_color": "#287fc5",
  "theme_color": "#fff"
}

icon需求安不忘危各类规则,最大必要51二px *
51贰px的,这样Chrome会自动去采纳合适的图纸。就算把display改成standalone,从扭转的图标打开就会像打开3个App一样,未有浏览器地址栏那多少个东西了。start_url钦定打开现在的入口链接。

接下来添加四个link标签指向这么些manifest文件:

JavaScript

<link rel=”manifest” href=”/html/app-manifest/manifest.json”>

1
<link rel="manifest" href="/html/app-manifest/manifest.json">

这么组合Service Worker缓存:
图片 10把start_url指向的页面用ServiceWorker缓存起来,那样当用户用Chrome浏览器打开这些网页的时候,Chrome就会在底层弹二个提拔,询问用户是还是不是把那么些网页添加到桌面,如若点“添加”就会转移三个桌面图标,从这么些图标点进去就如打开三个App1样。感受如下:

图片 11

正如为难的是Manifest近日唯有Chrome协助,并且只可以在安卓系统上选择,IOS的浏览器不能够添加一个桌面图标,因为IOS未有开放那种API,不过自身的Safari却又是可以的。

综上,本文介绍了怎么用Service Worker结合Manifest做三个PWA离线Web
APP,首如果用ServiceWorker控制缓存,由于是写JS,比较灵敏,还足以与页面实行通讯,其余通过请求页面包车型客车换代时间来判断是还是不是须要更新html缓存。ServiceWorker的包容性不是专门好,不过前景比较光明,浏览器都在预备帮忙。现阶段可以组合offline
cache的Manifest做离线应用。

有关阅读:

  1. 何以要把网址升级到HTTPS
  2. 怎么把网址升级到http/二
  3. 自家是什么让网址用上HTML五Manifest

1 赞 1 收藏
评论

图片 12

3. Installed / Waiting

若果设置成功,Service Worker
进入installed(waiting)状态。在此情形中,它是1个得力的但未曾激活的
worker。它从未纳入 document 的主宰,确切来说是在等候着从当前 worker
接手。

高居 Waiting 状态的 SW,在以下之1的事态下,会被触发 Activating 状态。

4. Activating

高居 activating 状态之间,SW 脚本中的 activate
事件被执行。大家见惯司空在 activate 事件中,清理 cache
中的文件(清除旧Worker的缓存文件)。

SW激活失利,则一向进去屏弃(redundant)状态。

5. Activated

假定激活成功,SW
进入激活状态。在此情形中,SW开端接管理控制制客户端,并得以处理fetch(捕捉请求)、
push(音信推送)、 sync(同步事件)等作用性事件:

// sw.js

self.addEventListener('fetch', function(event) {  
  // Do stuff with fetch events
});

self.addEventListener('message', function(event) {  
  // Do stuff with postMessages received from document
}); 
......

6. Redundant 废弃

Service Worker 大概以下之一的案由而被抛弃(redundant)——

 
我们曾经了然了SW的生命周期了,那么未来就起来来做一个离线应用。

大家只兑现最简易的功能:用户每发送3个http请求,我们就用SW捕获那么些请求,然后在缓存里找是不是缓存了这么些请求对应的响应内容,假设找到了,就把缓存中的内容重返给主页面,不然再发送请求给服务器。

二、 register 注册

第一要登记贰个SW,在index.js文件中:

// index.js

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    // 注册一个service worker,这个例子中worker的路径是根目录中的,所以这个worker可以缓存这个项目中任意文件。如果目录是‘/js/sw.js‘,那么只能缓存目录'/js'下的文件
    // 参数registration存储了本次注册的一些相关信息
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      // registration.scope 返回的是这个service worker的作用域
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }).catch(function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

知识点:

1. window.navigator

归来3个Navigator对象,该对象不难的话就是同意大家取得大家用户代理(浏览器)的局地音信。比如,浏览器的官方名称,浏览器的版本,互连网连接景况,设备地点音信等等。

2. navigator.serviceWorker

回去三个
ServiceWorkerContainer对象,该指标允许大家对SW实行注册、删除、更新和通讯。

上边包车型客车代码中率先判断navigator是否有serviceWorker属性(存在的话代表浏览器帮忙SW),如若存在,那么通过navigator.serviceWorker.register()(也就是ServiceWorkerContainer.register())来注册叁个新的SW,.register()经受八个
路径 作为第贰个参数。

ServiceWorkerContainer.register()回到二个Promise,所以能够用.then()
.catch()来展开持续处理。

三. SW的功能域

假诺未有点名该SW的作用域,那么它的私下认可成效域就是其所在的目录。
比如,.register('/sw.js')中,sw.js在根目录中,所以成效域是总体项指标公文。

壹旦是这么:.register('/controlled/sw.js'),sw.js的效能域是/controlled。

我们得以手动为SW钦赐三个功能域:
.register('service-worker.js', { scope: './controlled' });

三. 怎么在load事件中举办登记

为啥供给在load事件运维呢?因为您要优异运维三个线程,运营今后您只怕还会让它去加载财富,这么些皆以亟需占用CPU和带宽的,大家相应保险页面能平时加载完,然后再开发银行大家的后台线程,无法与健康的页面加载发生竞争,这么些在低端移动装备意义比较大。

三、install 安装

大家曾经注册好了SW,借使 sw.js
下载并且解析成功,大家的SW就进来安装阶段了,这时候会触发install事件。我们一般在install事件中缓存我们想要缓存的静态能源,供SW控制主页面之后接纳:

// sw.js

var CACHE_NAME = 'my-site-cache-v1'; // cache对象的名字
var urlsToCache = [ // 想要缓存的文件的数组
  '/',
  '/styles/main.css',
  '/script/main.js'
];

// 如果所有文件都成功缓存,则将安装成功
self.addEventListener('install', function(event) {
  // 执行安装步骤
  // ExtendableEvent.waitUntil()方法延长了安装过程,直到其传回的Promise被resolve之后才会安装成功
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        // console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

知识点:

1. cache

Cache是允许我们管理缓存的
Request
/
Response
对象对的接口,能够透过这些接口增删查改 Request / Response 对。

地点代码中cache.addAll(urlsToCache)表示把数组中的文件都缓存在内部存款和储蓄器中。
详见询问请戳 :
Cache

2. caches

caches是一个CacheStorage指标,提供三个可被访问的命名Cache对象的目录,维护字符串名称到对应Cache对象的炫耀。

咱俩得以通过该对象打开某多个特定的Cache对象,也许查看该列表中是还是不是盛名称为“xxx”的Cache对象,也得以去除某三个Cache对象。

四、activate 激活

大家的SW已经安装成功了,它能够准备控制客户端并拍卖 push 和 sync
等成效事件了,那时,大家赢得2个 activate 事件。

// sw.js

self.addEventListener("activate", function(event) {
    console.log("service worker is active");
});

如若SW安装成功并被激活,那么控制台会打字与印刷出”service worker is active”。

比方大家是在立异SW的图景下,此时理应还有三个旧的SW在做事,那时大家的新SW就不会被激活,而是进入了
“Waiting” 状态。

我们需求关闭此网址的全部标签页来关闭旧SW,使新的SW激活。恐怕手动激活。

那么activate事件能够用来干什么呢?即便我们今日换了二个新的SW,新SW要求缓存的静态能源和旧的两样,那么大家就须求消除旧缓存。

缘何吧?因为叁个域能用的缓存空间是不难的,假若没有科管缓存数据,导致数据过大,浏览器会帮大家删除数据,那么恐怕会误删大家想要留在缓存中的数据。

以此未来会详细讲,未来只必要精通activate事件能用来驱除旧缓存旧能够了。

五、 fetch事件

方今大家的SW已经激活了,那么能够开首捕获互联网请求,来加强网址的习性了。

当网页发出请求的时候,会触发fetch事件。

Service Workers能够监听该事件,’拦截’ 请求,并控制回去内容 ————
是回到缓存的数目,如故发送请求,再次回到服务器响应的数码。

上边包车型客车代码中,SW会检查测试缓存中是或不是有用户想要的内容,即便有,就再次回到缓存中的内容。不然再发送互联网请求。

// sw.js

self.addEventListener('fetch', event => {
    const { request } = event; // 获取request
    const findResponsePromise = caches.open(CACHE_NAME)
    // 在match的时候,需要请求的url和header都一致才是相同的资源
    // caches.match(event.request, {ignoreVary: true}) 表示只要请求url相同就认为是同一个资源。
    .then(cache => cache.match(request)) // 查看cache对象中是否有匹配的项
    .then(response => {
        if (response) { // 如果response不为空,则返回response,否则发送网络请求
            return response;
        }

        return fetch(request);
    });
    // event.respondWith 是一个 FetchEvent 对象中的特殊方法,用于将请求的响应发送回浏览器。它接收一个对响应(或网络错误)resolve 后的 Promise 对象作为参数。
    event.respondWith(findResponsePromise);
});

箭头函数真的很符合用于Promise对象,省略了一群的functionreturn要害字,望着清爽多了……

关于缓存策略
今非昔比的应用场景要求运用差别的缓存策略。

比如,小红希望她的网站在在线的时候总是回到缓存中的内容,然后在后台更新缓存;在离线的时候,再次来到缓存的始末。

譬如,小明希望他的网址能够在在线的时候回来最新的响应内容,离线的时候再回到缓存中的内容。
……
如果想要切磋一下各个缓存策略,可以参考上边包车型客车素材,那里就不详述了,不然文章就成裹脚布了……
The Service Worker
Cookbook

离线指南
ServiceWorker最好实践

可是,既然标题是“做一个离线网页应用”,那大家就做叁个最简便的缓存策略:如若缓存中保存着恳求的内容,则赶回缓存中的内容,不然,请求新内容,并缓存新内容。

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request)
        .then(response => {
            // Cache hit - return response
            if (response) {
                return response;
            }
            // 克隆请求。因为请求是一个“stream”,只能用一次。但我们需要用两次,一次用来缓存,一次给浏览器抓取内容,所以需要克隆
            var fetchRequest = event.request.clone();
            // 返回请求的内容
            return fetch(fetchRequest).then(
                response => {
                    // 检查是否为有效的响应。basic表示同源响应,也就是说,这意味着,对第三方资产的请求不会添加到缓存。
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    // 同request,response是一个“stream”,只能用一次,但我们需要用两次,一次用来缓存一个返回给浏览器,所以需要克隆。
                    var responseToCache = response.clone();
                    // 缓存新请求
                    caches.open(CACHE_NAME)
                        .then(cache => cache.put(event.request, responseToCache));
                    return response;
                }
            );
        })
    );
});

 
做到啦!大家简陋的离线应用!
打开页面,看一下缓存中有怎么样内容:

图片 13

offline1

下一场点击“Vue”的链接:

图片 14

offline2

能够观察缓存中多了一张后缀为.png的图样。
SW缓存了我们的新请求!

打开chrome的开发者工具,点击offline,使标签页处于离线状态:

图片 15

offline3

然后,刷新页面。

图片 16

offline4

照例得以访问页面。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图