菜单

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

2019年4月19日 - Html/Html5

利用 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 Cookbook(搜聚了ServiceWorker的片段实行例子)
理解 Service
Workers

1. 什么是Service Worker

Service Worker是谷歌(Google)倡导的兑现PWA(Progressive Web
App)的贰个最重要角色,PWA是为着消除守旧Web 应用程式的老毛病:

(一)未有桌面入口

(2)不能够离线使用

(3)没有Push推送

那Service Worker的具体表现是哪些的吗?如下图所示:

图片 1

ServiceWorker是在后台运维的一条服务Worker线程,上航海用体育场面笔者开了八个标签页,所以显得了八个Client,可是不管开多少个页面都唯有三个Worker在负责管理。那一个Worker的职业是把一些能源缓存起来,然后拦截页面包车型大巴伸手,先看下缓存库里有未有,若是有个别话就从缓存里取,响应200,反之未有的话就走符合规律的乞求。具体来说,ServiceWorker结合Web App Manifest能形成以下工作(那也是PWA的检验标准):

图片 2

蕴含能够离线使用、断网时回来200、能提醒用户把网址加多贰个Logo到桌面上等。

祥和提示

二. 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.壹版本现已支撑WebRTC了,所以Safari照旧3个向上的男女。

艾德ge也准备帮助,所以Service Worker的前景格外美好。

  1. 动用限制
    Service Worker由于权力相当高,只支持https协议或许localhost。
    私家感觉Github
    Pages
    是一个很完美的演练场面。
  2. 储备知识
    瑟维斯Worker大批量使用Promise,不打听的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的运用套路是先挂号三个Worker,然后后台就会运维一条线程,能够在那条线程运维的时候去加载1些能源缓存起来,然后监听fetch事件,在那几个事件里拦截页面包车型大巴呼吁,先看下缓存里有未有,假使有直接回到,不然正常加载。或许是一齐头不缓存,各个财富请求后再拷贝1份缓存起来,然后下3回呼吁的时候缓存里就有了。

兼容性

(一)注册二个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完现在注册,注册的时候传1个js文件给它,这一个js文件就是ServiceWorker的运作环境,要是不能够成功注册的话就会抛格外,如Safari
TP尽管有那个目的,不过会抛极度不能利用,就足以在catch里面处理。那里有个难题是为什么必要在load事件运营呢?因为您要万分运营3个线程,运行未来你大概还会让它去加载能源,那个都是索要占用CPU和带宽的,大家理应保险页面能健康加载完,然后再起步我们的后台线程,无法与正常的页面加载发生竞争,这一个在低档移动装备意义相当的大。

再有某个内需专注的是ServiceWorker和Cookie一样是有Path路径的定义的,如若你设定一个cookie假使叫time的path=/page/A,在/page/B那么些页面是不能收获到这一个cookie的,假使设置cookie的path为根目录/,则兼具页面都能取获得。类似地,若是注册的时候利用的js路线为/page/sw.js,那么那一个ServiceWorker只好管理/page路线下的页面和财富,而不可见处理/api路线下的,所以一般把ServiceWorker注册到一流目录,如上面代码的”/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);
        })
    );
});

透过上面包车型大巴操作,创制和加多了3个缓存库叫fed-cache,如下Chrome调整台所示:

图片 7

ServiceWorker的API基本上都以重临Promise对象制止堵塞,所以要用Promise的写法。下面在安装ServiceWorker的时候就把首页的伏乞给缓存起来了。在ServiceWorker的运转环境之中它有3个caches的大局对象,那些是缓存的入口,还有二个常用的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事件了,我们愿意每获得2个财富就把它缓存起来,就毫无像上一篇涉嫌的Manifest必要先生成二个列表。

你或然会问,当我刷新页面包车型客车时候不是又再度注册安装和激活了3个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都同样才是壹致的能源,能够设定第一个参数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,倘若跨域的能源支撑CORAV肆S,那么可以把request的mod改成cors。如若请求退步了,如40四或许是过期等等的,那么也直接重返response让主页面处理,不然的话表明加载成功,把那一个response克隆二个平放cache里面,然后再回到response给主页面线程。注意能缓慢存里的能源壹般只可以是GET,通过POST获取的是不能够缓存的,所以要做个剖断(当然你也足以手动把request对象的method改成get),还有把部分私家不愿意缓存的财富也做个判别。

那般只要用户张开过3回页面,ServiceWorker就安装好了,他刷新页面或许张开第二个页面包车型地铁时候就可以把请求的财富11做缓存,包蕴图片、CSS、JS等,只要缓存里有了不管用户在线或然离线都能够健康访问。那样大家当然会有1个主题素材,这几个缓存空间到底有多大?上1篇我们提到Manifest也终究地点存款和储蓄,PC端的Chrome是五Mb,其实那几个说法在新本子的Chrome已经不准确了,在Chrome
陆1版本能够见到地面存储的空间和接纳情形:

图片 8

中间Cache Storage是指ServiceWorker和Manifest占用的半空中山高校小和,上海教室能够看到总的空间尺寸是20GB,大概是unlimited,所以基本上不用操心缓存会不够用。

一、 生命周期

民用认为先知道一下它的生命周期很关键!在此之前查资料的时候,许多小说1上来就监听install事件、waiting事件、activate事件……反正本身是壹脸懵逼。

图片 9

Service Worker的生命周期

(4)cache html

上边第(3)步把图纸、js、css缓存起来了,不过倘若把页面html也缓存了,例如把首页缓存了,就会有二个啼笑皆非的主题材料——ServiceWorker是在页面注册的,不过今后得到页面包车型大巴时候是从缓存取的,每趟都是同壹的,所以就导致力不从心立异ServiceWorker,如变成sw-五.js,不过PWA又供给大家能缓存页面html。那怎么办吧?谷歌的开辟者文书档案它只是提到会存在那些题目,但并不曾认证怎么化解那些难点。这几个的主题材料的解决就要求大家要有2个编制能明白html更新了,从而把缓存里的html给替换掉。

Manifest更新缓存的建制是去看Manifest的文本内容有未有发生变化,假若爆发变化了,则会去创新缓存,ServiceWorker也是基于sw.js的文件内容有未有产生变化,我们得以借鉴这些思量,假设请求的是html并从缓存里抽出来后,再发个请求获取1个文件看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 UEnclaveL(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});
        });
    }
};

代码先去赢得叁个json文件,贰个页面会对应一个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的字段,假如地点内部存款和储蓄器未有这几个页面包车型大巴updateTime的数码可能是和流行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);
    }
});

依据差别的音信类型调区别的回调函数,假使是一的话就是去除cache。用户公布完评论后会触发刷新页面,刷新的时候缓存已经被删了就会再也去央浼了。

这么就缓解了实时更新的难点。

1. Parsed

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

现今并不要求知道那一个艺术各样部分的事无巨细含义,只要知道大家未来在为大家的网页注册三个SW就足以了。

能够看出大家传入的参数是几个JS文件的路线,当浏览器试行到那边的时候,就会到相应的门径下载该公文,然后对该脚本实行解析,假若下载恐怕解析退步,那么那个SW就会被舍弃。

要是条分缕析成功了,那就到了parsed状态。能够张开上面包车型地铁办事了。

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

要缓存能够采用二种花招,使用Http
Cache设置缓存时间,也能够用Manifest的Application Cache,还足以用ServiceWorker缓存,若是3者都用上了会怎么样呢?

会以Service Worker为事先,因为ServiceWorker把请求拦截了,它开头做处理,假设它缓存Curry部分话向来重回,未有的话平常请求,就一定于尚未ServiceWorker了,今年就到了Manifest层,Manifest缓存里如若局地话就取这几个缓存,借使未有的话就相当于尚未Manifest了,于是就会从Http缓存里取了,假若Http缓存里也并未有就会发请求去赢得,服务端根据Http的etag大概Modified
Time大概会回到30四 Not
Modified,不然平常重临200和数据内容。这正是整一个收获的进程。

由此假设既用了Manifest又用ServiceWorker的话应该会招致同一个能源存了两回。不过能够让帮助ServiceWorker的浏览器选用Service Worker,而不匡助的使用Manifest.

2. Installing

在installing状态中,SW 脚本中的 install
事件被实行。在可以支配客户端之前,install
事件让我们有机会缓存大家须要的全体剧情。

譬如,我们得以先缓存一张图纸,那么当SW调整客户端之后,客户点击该链接的图片,我们就足以用SW捕获请求,直接回到该图形的缓存。

若事件中有 event.waitUntil() 方法,则 installing
事件会一贯等到该形式中的 Promise 达成今后才会中标;若 Promise
被拒,则设置败北,瑟维斯 Worker 直接进去遗弃(redundant)状态。

5. 接纳Web App Manifest加多桌面入口

专注那里说的是别的一个Manifest,那几个Manifest是2个json文件,用来放网址icon名称等音信以便在桌面加多三个Logo,以及创制1种展开那些网页就像是展开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,从转变的Logo展开就会像张开2个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指向的页面用瑟维斯Worker缓存起来,这样当用户用Chrome浏览器张开那几个网页的时候,Chrome就会在底层弹3个唤起,询问用户是不是把这么些网页增多到桌面,假设点“增多”就会变卦贰个桌面图标,从那个Logo点进去就像是展开3个App同样。感受如下:

图片 11

相比较难堪的是Manifest近年来唯有Chrome援助,并且不得不在安卓系统上运用,IOS的浏览器不可能增多2个桌面Logo,因为IOS未有开放那种API,可是我的Safari却又是足以的。

综上,本文介绍了怎么用Service Worker结合Manifest做三个PWA离线Web
应用程式,主要是用ServiceWorker调控缓存,由于是写JS,相比灵敏,仍是能够与页面举行通讯,其余通过请求页面的换代时间来判定是还是不是供给更新html缓存。ServiceWorker的兼容性不是专程好,但是前景相比较光明,浏览器都在预备支持。现阶段得以组合offline
cache的Manifest做离线应用。

相关阅读:

  1. 为什么要把网站晋级到HTTPS
  2. 怎么把网址晋级到http/贰
  3. 自家是何许让网址用上HTML5Manifest

1 赞 1 收藏
评论

图片 12

3. Installed / Waiting

只要设置成功,Service Worker
进入installed(waiting)状态。在此情景中,它是多少个管用的但未有激活的
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 注册

先是要登记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目标,该目的轻易的话就是允许大家赢得我们用户代理(浏览器)的1对音讯。比如,浏览器的官方名称,浏览器的版本,互连网连接情况,设备地点信息等等。

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事件运转呢?因为你要相当运行3个线程,运维之后您大概还会让它去加载能源,那些都以急需占用CPU和带宽的,大家应有保证页面能符合规律加载完,然后再开行大家的后台线程,无法与常规的页面加载产生竞争,这一个在低级移动设备意义异常的大。

三、install 安装

咱们曾经登记好了SW,如若 sw.js
下载并且解析成功,大家的SW就进入安装阶段了,那时候会触发install事件。大家1般在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对象,也可以去除某2个Cache对象。

四、activate 激活

作者们的SW已经安装成功了,它能够准备调整客户端并拍卖 push 和 sync
等功效事件了,那时,我们得到3个 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事件能够用来干什么吗?假若大家前天换了1个新的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最好试行

只是,既然标题是“做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地图