菜单

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

2019年4月6日 - CSS/CSS3

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

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

初稿出处:
人人网FED博客   

在上一篇《自小编是怎么着让网址用上HTML5Manifest》介绍了怎么用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,可是不管开多少个页面都唯有3个Worker在负责管理。那一个Worker的行事是把一部分财富缓存起来,然后拦截页面包车型地铁请求,先看下缓存Curry有未有,假使部分话就从缓存里取,响应200,反之未有的话就走正规的呼吁。具体来说,ServiceWorker结合Web App Manifest能一气呵成以下工作(那也是PWA的检测标准):

图片 2

归纳能够离线使用、断网时重返200、能唤起用户把网址添加一个图标到桌面上等。

友好提示

二. Service Worker的支撑情状

Service Worker最近只有Chrome/Firfox/Opera帮衬:

图片 3

Safari和艾德ge也在预备协助Service Worker,由于ServiceWorker是谷歌(谷歌)为主的1项正式,对于生态相比封闭的Safari来说也是迫于时局起始准备扶助了,在Safari
TP版本,能够看出:

图片 4

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

图片 5

但无论是如何,至少申明Safari已经准备协理ServiceWorker了。别的还足以见见在当年20一七年一月表露的Safari
1一.0.1版本已经补助Web锐界TC了,所以Safari依然1个迈入的男女。

艾德ge也准备帮忙,所以Service Worker的前景10分美好。

  1. 运用范围
    Service Worker由于权力很高,只协助https协议或然localhost。
    村办觉得Github
    Pages
    是二个很出彩的勤学苦练地方。
  2. 储备知识
    瑟维斯Worker多量使用Promise,不理解的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的行使套路是首先登场记二个Worker,然后后台就会运行一条线程,可以在那条线程运维的时候去加载壹些财富缓存起来,然后监听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完以往注册,注册的时候传三个js文件给它,那个js文件正是ServiceWorker的运作环境,借使无法学有所成注册的话就会抛万分,如Safari
TP纵然有这一个指标,不过会抛非凡无法利用,就足以在catch里面处理。这里有个难点是干吗供给在load事件运转呢?因为您要卓殊运维贰个线程,运维今后你或者还会让它去加载能源,那么些都以须求占用CPU和带宽的,我们应当保证页面能符合规律加载完,然后再起步我们的后台线程,不可能与常规的页面加载发生竞争,这几个在低端移动设备意义对比大。

再有一些亟待小心的是瑟维斯Worker和Cookie1样是有Path路径的概念的,若是您设定三个cookie假使叫time的path=/page/A,在/page/B那么些页面是无法得到到那些cookie的,假诺设置cookie的path为根目录/,则装有页面都能博取到。类似地,假诺注册的时候利用的js路径为/page/sw.js,那么这几个瑟维斯Worker只可以管理/page路径下的页面和财富,而不可见处理/api路径下的,所以一般把ServiceWorker注册到顶尖目录,如上边代码的”/sw-三.js”,那样那一个ServiceWorker就能接管页面包车型大巴有所能源了。

图片 6

(二)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”); // 创立和开拓3个缓存库 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的大局对象,那些是缓存的进口,还有八个常用的clients的大局对象,3个client对应3个标签页。

在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事件了,大家愿意每得到一个财富就把它缓存起来,就不用像上1篇涉嫌的Manifest供给先生成2个列表。

你只怕会问,当笔者刷新页面包车型客车时候不是又再次注册安装和激活了3个ServiceWorker?就算又调了1次注册,但并不会另行挂号,它发现”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})

代表只要请求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,要是跨域的财富支撑COEscortS,那么能够把request的mod改成cors。借使请求战败了,如40四要么是晚点等等的,那么也一直回到response让主页面处理,不然的话表明加载成功,把这一个response克隆1个内置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也缓存了,例如把首页缓存了,就会有3个窘迫的标题——ServiceWorker是在页面注册的,不过今后获得页面包车型客车时候是从缓存取的,每一趟都是同等的,所以就导致不能立异ServiceWorker,如变成sw-伍.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 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); pageUpdate提姆e[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});
        });
    }
};

代码先去取得2个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: 1就象征那是三个更新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是3个JS文件,假若大家要动用3个SW(ServiceWorker),那么大家须要在咱们的js代码中登记它,类似于:
navigator.serviceWorker.register('/sw-1.js')

于今并不需求知道这一个措施各种部分的详实含义,只要知道我们后天在为我们的网页注册二个SW就能够了。

能够见见大家传入的参数是三个JS文件的路线,当浏览器执行到那边的时候,就会到相应的路子下载该公文,然后对该脚本实行解析,要是下载可能解析战败,那么那些SW就会被放任。

若是条分缕析成功了,那就到了parsed状态。能够开始展览下边包车型地铁做事了。

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

要缓存能够使用三种手段,使用Http
Cache设置缓存时间,也能够用Manifest的Application Cache,还是能用瑟维斯Worker缓存,假若三者都用上了会怎么啊?

会以Service Worker为事先,因为ServiceWorker把请求拦截了,它初阶做拍卖,如若它缓存Curry部分话一直再次回到,未有的话平常请求,就一定于尚未ServiceWorker了,那年就到了Manifest层,Manifest缓存里若是局地话就取那几个缓存,固然没有的话就相当于尚未Manifest了,于是就会从Http缓存里取了,如若Http缓存里也从没就会发请求去赢得,服务端依据Http的etag可能Modified
Time大概会回到30四 Not
Modified,不然符合规律重返200和数量内容。那就是整二个收获的进度。

故此一旦既用了Manifest又用ServiceWorker的话应该会造成同1个能源存了五遍。可是能够让扶助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名称等音信以便在桌面添加二个图标,以及创制一种打开这么些网页就像是打开App1样的功效。上边平素说的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就会在后面部分弹贰个晋升,询问用户是或不是把那些网页添加到桌面,假诺点“添加”就会转移一个桌面图标,从这么些图标点进去如同打开二个App1样。感受如下:

图片 11

相比较难堪的是Manifest近年来唯有Chrome匡助,并且不得不在安卓系统上利用,IOS的浏览器无法添加2个桌面图标,因为IOS未有开放那种API,不过本身的Safari却又是足以的。

综上,本文介绍了怎么用Service Worker结合Manifest做二个PWA离线Web
APP,首借使用ServiceWorker控制缓存,由于是写JS,相比灵活,仍是能够与页面进行通讯,其余通过请求页面包车型大巴更新时间来判断是还是不是必要立异html缓存。ServiceWorker的包容性不是专门好,可是前景比较光明,浏览器都在准备补助。现阶段得以整合offline
cache的Manifest做离线应用。

连锁阅读:

  1. 何以要把网站升级到HTTPS
  2. 什么样把网址升级到http/2
  3. 本人是什么样让网址用上HTML5Manifest

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

归来贰个Navigator目的,该目的简单的讲就是同意大家取得大家用户代理(浏览器)的壹些音信。比如,浏览器的法定名称,浏览器的版本,网络连接处境,设备地点音信等等。

2. navigator.serviceWorker

归来多少个
ServiceWorkerContainer指标,该目的允许大家对SW实行挂号、删除、更新和通信。

上边包车型大巴代码中率先判断navigator是否有serviceWorker属性(存在的话代表浏览器帮衬SW),假设存在,那么通过navigator.serviceWorker.register()(也就是ServiceWorkerContainer.register())来注册二个新的SW,.register()接受一个
路径 作为第三个参数。

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

3. 怎么在load事件中进行登记

怎么需求在load事件运营呢?因为您要相当运维三个线程,运转未来你恐怕还会让它去加载财富,这几个都是须要占用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对象,提供1个可被访问的命名Cache对象的目录,维护字符串名称到相应Cache对象的照耀。

咱俩能够通过该对象打开某八个特定的Cache对象,大概查看该列表中是不是盛名称叫“xxx”的Cache对象,也得以去除某3个Cache对象。

四、activate 激活

笔者们的SW已经安装成功了,它能够准备控制客户端并处理 push 和 sync
等效能事件了,那时,大家获取多个 activate 事件。

// sw.js

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

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

假定大家是在更新SW的事态下,此时应当还有1个旧的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最佳实践

不过,既然标题是“做1个离线网页应用”,那我们就做贰个最简便易行的缓存策略:假使缓存中保留着伸手的始末,则赶回缓存中的内容,不然,请求新内容,并缓存新剧情。

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