菜单

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

2019年4月20日 - Ajax

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

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

原版的书文出处:
人人网FED博客   

在上①篇《本人是何等让网址用上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 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、能唤起用户把网站加多1个Logo到桌面上等。

友好提示

二. Service Worker的支撑景况

Service Worker近年来唯有Chrome/Firfox/Opera协助:

图片 3

Safari和艾德ge也在盘算辅助瑟维斯 Worker,由于瑟维斯Worker是谷歌(谷歌)着力的一项专门的工作,对于生态相比封闭的Safari来说也是迫于形势起头图谋辅助了,在Safari
TP版本,能够观察:

图片 4

在实验作用(Experimental Features)里早已有ServiceWorker的菜单项了,只是固然打开也是无法用,会提醒您还向来不兑现:

图片 5

但不论怎么,至少表明Safari已经希图援救ServiceWorker了。此外仍是能够见见在今年201七年六月颁发的Safari
1一.0.一本子现已支撑WebRTC了,所以Safari照旧1个迈入的子女。

艾德ge也计划帮忙,所以瑟维斯 Worker的前景10分美好。

  1. 选拔范围
    Service Worker由于权力异常高,只援助https协议可能localhost。
    村办感觉Github
    Pages
    是一个很了不起的演习场面。
  2. 储备知识
    ServiceWorker大批量使用Promise,不打听的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的施用套路是先挂号一个Worker,然后后台就会运营一条线程,能够在那条线程运转的时候去加载一些财富缓存起来,然后监听fetch事件,在那么些事件里拦截页面包车型地铁央浼,先看下缓存里有未有,要是有间接回到,否则经常加载。或许是1初叶不缓存,每一种财富请求后再拷贝一份缓存起来,然后下一次呼吁的时候缓存里就有了。

兼容性

(一)注册一个Service Worker

瑟维斯 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路线的概念的,假如您设定3个cookie假使叫time的path=/page/A,在/page/B这几个页面是不可见赢获得这些cookie的,要是设置cookie的path为根目录/,则具备页面都能获得到。类似地,要是注册的时候使用的js路线为/page/sw.js,那么这些ServiceWorker只好管理/page路线下的页面和财富,而不可见处理/api路线下的,所以一般把ServiceWorker注册到5星级目录,如上面代码的”/sw-3.js”,那样这一个ServiceWorker就能接管页面包车型客车具有能源了。

图片 6

(2)Service Worker安装和激活

挂号完事后,ServiceWorker就会议及展览开设置,那个时候会触发install事件,在install事件之中可以缓存一些财富,如下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的写法。上边在设置瑟维斯Worker的时候就把首页的央浼给缓存起来了。在ServiceWorker的运维情状之中它有叁个caches的大局对象,那一个是缓存的入口,还有1个常用的clients的全局对象,二个client对应二个标签页。

在ServiceWorker里面能够运用fetch等API,它和DOM是割裂的,未有windows/document对象,不可能直接操作DOM,不大概直接和页面交互,在ServiceWorker里面不可能得知当前页面张开了、当前页面包车型大巴url是如何,因为2个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-4.js”,也许改动sw-三.js的公文内容,就会另行挂号,新的ServiceWorker会先install然后进入waiting状态,等到重启浏览器时,老的瑟维斯Worker就会被替换掉,新的ServiceWorker进入active状态,假诺不想等到再也开动浏览器能够像上面同样在install里面调skipWaiting:

JavaScript

this.skipWaiting();

1
this.skipWaiting();

瑟维斯 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一样就以为是同3个财富。

上边代码的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,如若跨域的财富支持COEscortS,那么能够把request的mod改成cors。如若请求退步了,如40四照旧是逾期等等的,那么也一贯回到response让主页面管理,不然的话表明加载成功,把这些response克隆八个平放cache里面,然后再回到response给主页面线程。注意能缓慢存里的财富一般只好是GET,通过POST获取的是不能够缓存的,所以要做个判定(当然你也足以手动把request对象的method改成get),还有把部分私家不愿意缓存的能源也做个判定。

那样一旦用户张开过2回页面,ServiceWorker就安装好了,他刷新页面或然展开第3个页面包车型地铁时候就能够把请求的资源1一做缓存,包含图形、CSS、JS等,只要缓存里有了不管用户在线只怕离线都能够健康访问。这样大家当然会有2个题目,那一个缓存空间到底有多大?上一篇大家提到Manifest也好不轻松地方存储,PC端的Chrome是伍Mb,其实那一个说法在新本子的Chrome已经不规范了,在Chrome
陆1本子能够看看本地存储的上空和使用境况:

图片 8

个中Cache Storage是指瑟维斯Worker和Manifest占用的空间大小和,上海体育场所能够看出总的空间尺寸是20GB,大约是unlimited,所以基本上不用操心缓存会不够用。

1、 生命周期

民用认为先理解一下它的生命周期很关键!从前查资料的时候,许多篇章1上来就监听install事件、waiting事件、activate事件……反正笔者是1脸懵逼。

图片 9

Service Worker的生命周期

(4)cache html

上边第(三)步把图纸、js、css缓存起来了,但是一旦把页面html也缓存了,比如把首页缓存了,就会有一个两难的主题材料——ServiceWorker是在页面注册的,可是未来获得页面包车型客车时候是从缓存取的,每便都以千篇壹律的,所以就招致不可能立异ServiceWorker,如产生sw-5.js,可是PWA又要求大家能缓存页面html。这如何做吧?谷歌(谷歌(Google))的开拓者文书档案它只是提到会存在那个主题材料,但并未评释怎么消除那么些标题。这一个的难题的缓慢解决将在求大家要有1个编写制定能掌握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 U揽胜L(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文件,二个页面会对应2个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: []}}

个中重要有1个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: 一就象征那是3个创新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也不会缓存了。

再有1种更新是用户更新的,比方用户宣布了商量,须求在页面布告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);
    }
});

据书上说差别的音讯类型调不相同的回调函数,要是是壹的话就是删除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缓存,若是3者都用上了会如何呢?

会以瑟维斯 Worker为先行,因为ServiceWorker把请求拦截了,它首先做拍卖,借使它缓存Curry一些话一贯回到,未有的话不荒谬请求,就也就是尚未ServiceWorker了,那个时候就到了Manifest层,Manifest缓存里即使有的话就取这么些缓存,就算未有的话就一定于尚未Manifest了,于是就会从Http缓存里取了,固然Http缓存里也从不就会发请求去赢得,服务端依照Http的etag可能Modified
Time或许会回到30四 Not
Modified,不然寻常重临200和多少内容。那就是整三个获得的长河。

因此只要既用了Manifest又用ServiceWorker的话应该会导致同2个能源存了五回。不过足以让帮助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是3个json文件,用来放网址icon名称等音讯以便在桌面增添1个Logo,以及创建一种张开那个网页就好像打开App同样的效益。下边一贯说的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,从变化的Logo展开就会像展开贰个App一样,未有浏览器地址栏那2个东西了。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个桌面Logo,从那一个Logo点进去仿佛张开1个App一样。感受如下:

图片 11

相比较难堪的是Manifest方今唯有Chrome辅助,并且不得不在安卓系统上使用,IOS的浏览器不能够增多2个桌面Logo,因为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)状态。在此情况中,它是一个立竿见影的但尚无激活的
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的生命周期了,那么将来就起来来做一个离线应用。

咱俩只兑现最轻易易行的职能:用户每发送多少个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

归来1个
ServiceWorkerContainer目的,该对象允许大家对SW举行挂号、删除、更新和通讯。

地点的代码中第二判别navigator是否有serviceWorker质量(存在的话代表浏览器扶助SW),如若存在,那么通过navigator.serviceWorker.register()(也就是ServiceWorkerContainer.register())来注册一个新的SW,.register()经受2个
路径 作为第贰个参数。

ServiceWorkerContainer.register()归来贰个Promise,所以能够用.then()
.catch()来进行继续管理。

3. 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对象的映照。

大家得以经过该目的展开某2个特定的Cache对象,或然查看该列表中是还是不是有名称为“xxx”的Cache对象,也得以去除某2个Cache对象。

四、activate 激活

我们的SW已经安装成功了,它能够打算调控客户端并拍卖 push 和 sync
等效果事件了,那时,大家收获1个 activate 事件。

// sw.js

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

万1SW安装成功并被激活,那么调节台会打字与印刷出”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地图