菜单

Javascript:用Service Worker做三个离线网页应用

2019年4月10日 - Json

运用 Service Worker 做2个 PWA 离线网页应用

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

原来的作品出处:
人人网FED博客   

在上一篇《自家是何等让网址用上HTML伍Manifest》介绍了怎么用Manifest做1个离线网页应用,结果被广泛网上朋友戏弄说那几个东西已经被deprecated,移出web标准了,未来被ServiceWorker替代了,不管如何,Manifest的局部构思仍然得以借用的。作者又将网站升级到了ServiceWorker,借使是用Chrome等浏览器就用ServiceWorker做离线缓存,若是是Safari浏览器就仍旧用Manifest,读者能够打开这些网址https://fed.renren.com感受一下,断网也是能通常打开。

参考资料
MDN — Service Worker
API

Service Workers: an
Introduction

劳动办事线程生命周期
瑟维斯 Worker Cookbook(收集了ServiceWorker的有的举办例子)
理解 Service
Workers

1. 什么是Service Worker

Service Worker是谷歌(Google)发起的贯彻PWA(Progressive Web
App)的3个关键剧中人物,PWA是为了缓解守旧Web 应用软件的缺点:

(一)未有桌面入口

(二)不能够离线使用

(3)没有Push推送

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

图片 1

ServiceWorker是在后台运行的一条服务Worker线程,上海体育场面小编开了五个标签页,所以体现了多个Client,但是不管开多少个页面都只有三个Worker在负责管理。那几个Worker的干活是把某些能源缓存起来,然后拦截页面包车型大巴央求,先看下缓存Curry有未有,假使局部话就从缓存里取,响应200,反之未有的话就走正规的请求。具体来说,ServiceWorker结合Web App Manifest能完毕以下工作(那也是PWA的检查测试专业):

图片 2

包含能够离线使用、断网时再次来到200、能提示用户把网站添加3个图标到桌面上等。

友善提醒

二. Service Worker的支撑情形

Service Worker方今唯有Chrome/Firfox/Opera协理:

图片 3

Safari和艾德ge也在准备辅助Service Worker,由于ServiceWorker是谷歌(谷歌(Google))着力的壹项专业,对于生态比较封闭的Safari来说也是迫于时势起头准备协理了,在Safari
TP版本,能够看来:

图片 4

在试行成效(Experimental Features)里早就有ServiceWorker的菜单项了,只是就算打开也是不可能用,会唤起您还未曾兑现:

图片 5

但无论是怎么着,至少表明Safari已经准备帮助ServiceWorker了。其余还足以看出在当年20一7年一月表露的Safari
1壹.0.1版本已经援救Web奥迪Q5TC了,所以Safari仍然1个前行的儿女。

艾德ge也准备辅助,所以Service Worker的前景至很美丽好。

  1. 使用限制
    Service Worker由于权力很高,只帮衬https协议只怕localhost。
    民用觉得Github
    Pages
    是1个很可观的勤学苦练场地。
  2. 储备知识
    ServiceWorker大批量使用Promise,不通晓的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的采取套路是先挂号一个Worker,然后后台就会运行一条线程,能够在那条线程运行的时候去加载1些能源缓存起来,然后监听fetch事件,在那些事件里拦截页面包车型客车乞请,先看下缓存里有未有,假设有直接重返,不然符合规律加载。只怕是一开头不缓存,各个能源请求后再拷贝一份缓存起来,然后下三次呼吁的时候缓存里就有了。

兼容性

(壹)注册一个Service 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完之后注册,注册的时候传3个js文件给它,那些js文件正是ServiceWorker的周转条件,要是不可能得逞注册的话就会抛非凡,如Safari
TP即使有其一指标,然则会抛十分不能运用,就能够在catch里面处理。那里有个难题是干什么须要在load事件运维呢?因为您要相当运维一个线程,运营之后你大概还会让它去加载能源,这一个都是内需占用CPU和带宽的,大家应该保险页面能健康加载完,然后再起步大家的后台线程,无法与正规的页面加载爆发竞争,那几个在低端移动装备意义相比大。

再有少数急需小心的是ServiceWorker和Cookie一样是有Path路径的概念的,假如您设定3个cookie要是叫time的path=/page/A,在/page/B那几个页面是不可知赢获得这么些cookie的,若是设置cookie的path为根目录/,则装有页面都能收获到。类似地,若是注册的时候使用的js路径为/page/sw.js,那么这几个ServiceWorker只好管理/page路径下的页面和财富,而不能处理/api路径下的,所以壹般把瑟维斯Worker注册到五星级目录,如上边代码的”/sw-三.js”,那样这一个ServiceWorker就能接管页面包车型客车有着能源了。

图片 6

(二)Service Worker安装和激活

挂号完事后,ServiceWorker就会开始展览安装,那个时候会触发install事件,在install事件之中能够缓存壹些财富,如下sw-三.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的运维环境之中它有1个caches的大局对象,这些是缓存的进口,还有1个常用的clients的大局对象,3个client对应三个标签页。

在ServiceWorker里面可以选择fetch等API,它和DOM是与世隔膜的,未有windows/document对象,不能够直接操作DOM,无法直接和页面交互,在瑟维斯Worker里面不可能获知当前页面打开了、当前页面包车型地铁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事件了,大家期望每得到一个资源就把它缓存起来,就无须像上1篇涉嫌的Manifest供给先生成二个列表。

你恐怕会问,当作者刷新页面包车型客车时候不是又再一次挂号安装和激活了叁个ServiceWorker?纵然又调了1次注册,但并不会再度登记,它发现”sw-3.js”那个曾经注册了,就不会再登记了,进而不会触发install和active事件,因为最近ServiceWorker已经是active状态了。当必要更新ServiceWorker时,如变成”sw-4.js”,大概转移sw-三.js的公文内容,就会再次登记,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的ServiceWorker就会被调换掉,新的ServiceWorker进入active状态,要是不想等到再也开动浏览器能够像下面1样在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都壹律才是均等的财富,能够设定第3个参数ignoreVary:

JavaScript

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

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

表示一旦请求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,即使跨域的能源支撑COPRADOS,那么能够把request的mod改成cors。如若请求失利了,如40四要么是晚点等等的,那么也一直回到response让主页面处理,不然的话表达加载成功,把这一个response克隆一个停放cache里面,然后再回去response给主页面线程。注意能减缓存里的能源一般只好是GET,通过POST获取的是不可能缓存的,所以要做个判断(当然你也足以手动把request对象的method改成get),还有把壹部分民用不期待缓存的能源也做个判断。

诸如此类一旦用户打开过一回页面,ServiceWorker就安装好了,他刷新页面或然打开第二个页面包车型大巴时候就可见把请求的能源一一做缓存,包蕴图形、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

上面第(叁)步把图片、js、css缓存起来了,不过倘诺把页面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 UHighlanderL(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,借使是的话就去发个请求获取一个文本,依照那个文件的内容决定是不是必要删除缓存,这几个立异的函数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});
        });
    }
};

代码先去赢得1个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: []}}

中间根本有3个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缓存删了重复赢得,那是贰个扭转的音讯公告:

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

依照分裂的新闻类型调区别的回调函数,要是是1的话就是剔除cache。用户宣布完评论后会触发刷新页面,刷新的时候缓存已经被删了就会再次去央浼了。

如此那般就缓解了实时更新的题材。

1. Parsed

SW是3个JS文件,倘诺大家要利用二个SW(瑟维斯Worker),那么我们必要在大家的js代码中登记它,类似于:
navigator.serviceWorker.register('/sw-1.js')

于今并不要求知道那一个主意种种部分的详尽含义,只要知道大家未来在为大家的网页注册3个SW就足以了。

能够见见咱们传入的参数是五个JS文件的路线,当浏览器执行到那边的时候,就会到相应的路子下载该公文,然后对该脚本实行分析,若是下载恐怕解析战败,那么这么些SW就会被丢弃。

1经条分缕析成功了,那就到了parsed状态。能够实行下边的办事了。

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

要缓存能够行使三种手段,使用Http
Cache设置缓存时间,也足以用Manifest的Application Cache,还足以用ServiceWorker缓存,假如三者都用上了会怎样啊?

会以Service Worker为先行,因为ServiceWorker把请求拦截了,它首先做拍卖,借使它缓存Curry1些话平素回到,未有的话符合规律请求,就一定于尚未ServiceWorker了,这年就到了Manifest层,Manifest缓存里即便有的话就取这几个缓存,假诺未有的话就相当于尚未Manifest了,于是就会从Http缓存里取了,如若Http缓存里也从不就会发请求去赢得,服务端根据Http的etag可能Modified
Time大概会回到304 Not
Modified,不然不奇怪重临200和数据内容。那就是整三个收获的进程。

故而只要既用了Manifest又用ServiceWorker的话应该会招致同一个能源存了四遍。可是可以让帮助ServiceWorker的浏览器选拔Service Worker,而不帮助的选用Manifest.

2. Installing

在installing状态中,SW 脚本中的 install
事件被实践。在能够支配客户端在此之前,install
事件让大家有空子缓存大家必要的有所内容。

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

若事件中有 event.waitUntil() 方法,则 installing
事件会直接等到该措施中的 Promise 完结现在才会中标;若 Promise
被拒,则设置败北,Service Worker 直接进入舍弃(redundant)状态。

伍. 应用Web App Manifest添加桌面入口

留意那里说的是其余三个Manifest,这些Manifest是多少个json文件,用来放网址icon名称等音信以便在桌面添加贰个图标,以及创制壹种打开那几个网页就如打开App一样的意义。上边一向说的Manifest是被舍弃的Application
Cache的Manifest。

本条Maifest.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,从转变的图标打开就会像打开二个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就会在尾巴部分弹二个升迁,询问用户是或不是把那几个网页添加到桌面,假如点“添加”就会转移1个桌面图标,从那么些图标点进去就像打开二个App1样。感受如下:

图片 11

正如为难的是Manifest如今唯有Chrome帮衬,并且只可以在安卓系统上利用,IOS的浏览器不也许添加2个桌面图标,因为IOS没有开放那种API,但是作者的Safari却又是足以的。

综上,本文介绍了怎么用Service Worker结合Manifest做1个PWA离线Web
APP,重假若用ServiceWorker控制缓存,由于是写JS,比较灵活,还可以与页面进行通信,别的通过请求页面包车型地铁翻新时间来判断是还是不是必要创新html缓存。ServiceWorker的包容性不是特意好,不过前景相比光明,浏览器都在备选协助。现阶段得以构成offline
cache的Manifest做离线应用。

连带阅读:

  1. 缘何要把网址升级到HTTPS
  2. 何以把网址升级到http/二
  3. 本人是何许让网址用上HTML5Manifest

1 赞 1 收藏
评论

图片 12

3. Installed / Waiting

要是设置成功,Service Worker
进入installed(waiting)状态。在此情况中,它是3个有效的但并未有激活的
worker。它尚无纳入 document 的主宰,确切来说是在等待着从当下 worker
接手。

处于 Waiting 状态的 SW,在以下之一的情事下,会被触发 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的生命周期了,那么今后就发轫来做1个离线应用。

大家只兑现最简便的效果:用户每发送2个http请求,大家就用SW捕获那几个请求,然后在缓存里找是不是缓存了那些请求对应的响应内容,假设找到了,就把缓存中的内容再次回到给主页面,不然再发送请求给服务器。

二、 register 注册

先是要注册3个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

归来七个Navigator指标,该目的简而言之便是允许大家获得大家用户代理(浏览器)的局地新闻。比如,浏览器的法定名称,浏览器的版本,互连网连接境况,设备地方音信等等。

2. navigator.serviceWorker

重临三个
ServiceWorkerContainer对象,该对象允许大家对SW进行挂号、删除、更新和通讯。

地点的代码中第三判断navigator是否有serviceWorker属性(存在的话表示浏览器扶助SW),假诺存在,那么通过navigator.serviceWorker.register()(也就是ServiceWorkerContainer.register())来注册一个新的SW,.register()接受三个
路径 作为第1个参数。

ServiceWorkerContainer.register()回去叁个Promise,所以能够用.then()
.catch()来进展一而再处理。

三. SW的成效域

假如未有点名该SW的成效域,那么它的私下认可效率域正是其所在的目录。
比如,.register('/sw.js')中,sw.js在根目录中,所以功能域是整套项指标文本。

若果是那样:.register('/controlled/sw.js'),sw.js的成效域是/controlled。

大家能够手动为SW内定3个功效域:
.register('service-worker.js', { scope: './controlled' });

三. 为什么在load事件中展开挂号

怎么要求在load事件运维呢?因为您要至极运维2个线程,运维之后你恐怕还会让它去加载能源,这么些都是急需占用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对象的炫耀。

大家得以经过该指标打开某2个特定的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供给缓存的静态财富和旧的不等,那么大家就需求排除旧缓存。

怎么呢?因为三个域能用的缓存空间是简单的,如果未有正确管理缓存数据,导致数据过大,浏览器会帮大家删除数据,那么或然会误删我们想要留在缓存中的数据。

其1现在会详细讲,今后只要求明白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最棒实践

可是,既然标题是“做2个离线网页应用”,那大家就做3个最简易的缓存策略:假如缓存中保留着伸手的内容,则赶回缓存中的内容,否则,请求新情节,并缓存新故事情节。

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地图