菜单

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

2019年4月8日 - JavaScript

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

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

原稿出处:
人人网FED博客   

在上一篇《自己是怎样让网站用上HTML5Manifest》介绍了怎么用Manifest做一个离线网页应用,结果被大面积网络好友戏弄说这些事物已经被deprecated,移出web标准了,现在被瑟维斯Worker替代了,不管怎么着,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是谷歌倡导的贯彻PWA(Progressive Web
App)的多少人命关天剧中人物,PWA是为了消除守旧Web 应用程式的缺陷:

(一)未有桌面入口

(二)不可能离线使用

(3)没有Push推送

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

图片 1

ServiceWorker是在后台运维的一条服务Worker线程,上海体育场面笔者开了八个标签页,所以体现了四个Client,但是不管开多少个页面都唯有二个Worker在负责管理。那么些Worker的办事是把有个别财富缓存起来,然后拦截页面包车型大巴请求,先看下缓存Curry有未有,假使局地话就从缓存里取,响应200,反之未有的话就走正规的呼吁。具体来说,ServiceWorker结合Web App Manifest能不辱职务以下工作(这也是PWA的检查测试专业):

图片 2

席卷能够离线使用、断网时再次来到200、能提醒用户把网址添加叁个图标到桌面上等。

祥和提示

2. Service Worker的支撑情形

Service Worker近期只有Chrome/Firfox/Opera协理:

图片 3

Safari和艾德ge也在预备协理Service Worker,由于ServiceWorker是谷歌(谷歌(Google))大旨的一项专业,对于生态相比较封闭的Safari来说也是迫于形势先河准备帮衬了,在Safari
TP版本,能够看到:

图片 4

在尝试效率(Experimental Features)里早就有ServiceWorker的菜单项了,只是即便打开也是无法用,会唤起您还未曾落到实处:

图片 5

但随便如何,至少表明Safari已经准备帮助ServiceWorker了。其余还足以看出在当年前年5月宣告的Safari
11.0.1版本已经协理Web普拉多TC了,所以Safari依旧2个前进的儿女。

艾德ge也准备协理,所以Service Worker的前景相当美好。

  1. 使用限制
    Service Worker由于权力很高,只辅助https协议大概localhost。
    村办觉得Github
    Pages
    是二个很可观的练习场面。
  2. 储备知识
    ServiceWorker大量使用Promise,不打听的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的选取套路是先注册多个Worker,然后后台就会运维一条线程,能够在那条线程运转的时候去加载一些能源缓存起来,然后监听fetch事件,在这些事件里拦截页面的请求,先看下缓存里有未有,假使有平昔回到,不然不荒谬加载。也许是1起头不缓存,每个财富请求后再拷贝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和带宽的,大家相应有限扶助页面能健康加载完,然后再起步大家的后台线程,无法与正规的页面加载产生竞争,这么些在低端移动装备意义相比较大。

再有一些索要留意的是ServiceWorker和Cookie壹样是有Path路径的定义的,假若您设定贰个cookie要是叫time的path=/page/A,在/page/B那些页面是无法获得到那些cookie的,借使设置cookie的path为根目录/,则装有页面都能博取到。类似地,若是注册的时候使用的js路径为/page/sw.js,那么那个ServiceWorker只可以管理/page路径下的页面和资源,而不可见处理/api路径下的,所以1般把ServiceWorker注册到5星级目录,如上边代码的”/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的运作环境之中它有一个caches的大局对象,这些是缓存的输入,还有2个常用的clients的大局对象,多个client对应多个标签页。

在ServiceWorker里面能够应用fetch等API,它和DOM是割裂的,未有windows/document对象,不能够直接操作DOM,不能够直接和页面交互,在ServiceWorker里面不可能得知当前页面打开了、当前页面包车型大巴url是何等,因为四个瑟维斯Worker管理当前打开的多少个标签页,能够透过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?固然又调了3回注册,但并不会再次登记,它发现”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相同就认为是同1个能源。

地点代码的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,固然跨域的财富匡助COQashqaiS,那么能够把request的mod改成cors。假如请求失利了,如40肆也许是过期等等的,那么也一直回到response让主页面处理,不然的话表达加载成功,把那几个response克隆四个放权cache里面,然后再回到response给主页面线程。注意能缓慢存里的财富壹般只可以是GET,通过POST获取的是不能够缓存的,所以要做个判断(当然你也能够手动把request对象的method改成get),还有把1些民用不期待缓存的财富也做个判断。

这般要是用户打开过一回页面,ServiceWorker就设置好了,他刷新页面只怕打开首个页面包车型客车时候就可以把请求的财富1一做缓存,包罗图片、CSS、JS等,只要缓存里有了不管用户在线大概离线都能够健康访问。那样大家本来会有贰个题材,那一个缓存空间到底有多大?上1篇大家提到Manifest也究竟地方存款和储蓄,PC端的Chrome是伍Mb,其实这些说法在新本子的Chrome已经不确切了,在Chrome
6一本子可以看到地点存款和储蓄的长空和应用状态:

图片 8

里头Cache Storage是指ServiceWorker和Manifest占用的空间尺寸和,上航海用图书馆能够看来总的空间大小是20GB,大约是unlimited,所以基本上不用顾虑缓存会不够用。

一、 生命周期

民用认为先精通一下它的生命周期很关键!在此之前查资料的时候,很多文章一上来就监听install事件、waiting事件、activate事件……反正自个儿是一脸懵逼。

图片 9

Service Worker的生命周期

(4)cache html

上边第(3)步把图纸、js、css缓存起来了,然则只要把页面html也缓存了,例如把首页缓存了,就会有一个窘迫的标题——ServiceWorker是在页面注册的,可是今后获得页面包车型大巴时候是从缓存取的,每一遍都以1样的,所以就导致不能创新ServiceWorker,如变成sw-五.js,但是PWA又要求大家能缓存页面html。那咋办呢?谷歌(谷歌)的开发者文书档案它只是提到会存在这些标题,但并不曾认证怎么化解这些难点。那几个的题材的消除就要求我们要有多个编写制定能精通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 URAV四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.update提姆e) {
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不1样,则重复去获取
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是八个JS文件,即使大家要利用1个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缓存,假诺叁者都用上了会如何呢?

会以Service Worker为先行,因为ServiceWorker把请求拦截了,它首先做拍卖,如若它缓存Curry一些话一向回到,未有的话经常请求,就约等于尚未ServiceWorker了,那年就到了Manifest层,Manifest缓存里尽管有个别话就取这些缓存,假设未有的话就相当于尚未Manifest了,于是就会从Http缓存里取了,如若Http缓存里也从未就会发请求去取得,服务端依据Http的etag恐怕Modified
Time大概会回来304 Not
Modified,否则符合规律重回200和数量内容。那正是整贰个赢得的进度。

之所以虽然既用了Manifest又用ServiceWorker的话应该会促成同3个财富存了一回。不过足以让援救ServiceWorker的浏览器选拔Service Worker,而不帮助的施用Manifest.

2. Installing

在installing状态中,SW 脚本中的 install
事件被执行。在能够支配客户端从前,install
事件让我们有空子缓存大家要求的有所内容。

譬如说,大家得以先缓存一张图片,那么当SW控制客户端之后,客户点击该链接的图样,大家就足以用SW捕获请求,直接重返该图形的缓存。

若事件中有 event.waitUntil() 方法,则 installing
事件会一直等到该方法中的 Promise 完毕现在才会中标;若 Promise
被拒,则设置失利,Service Worker 直接进去抛弃(redundant)状态。

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

留意这里说的是此外二个Manifest,那几个Manifest是八个json文件,用来放网址icon名称等消息以便在桌面添加三个图标,以及创立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,从变化的图标打开就会像打开三个App1样,未有浏览器地址栏这些东西了。start_url钦定打开之后的进口链接。

下一场添加2个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就会在底部弹3个升迁,询问用户是否把这几个网页添加到桌面,假设点“添加”就会变卦三个桌面图标,从这几个图标点进去就好像打开二个App1样。感受如下:

图片 11

比较狼狈的是Manifest最近只有Chrome辅助,并且不得不在安卓系统上选取,IOS的浏览器无法添加2个桌面图标,因为IOS未有开放那种API,不过作者的Safari却又是足以的。

综上,本文介绍了怎么用瑟维斯 Worker结合Manifest做一个PWA离线Web
应用软件,重假诺用ServiceWorker控制缓存,由于是写JS,相比灵敏,仍可以够与页面举办通讯,别的通过请求页面包车型的士换代时间来判定是还是不是要求更新html缓存。ServiceWorker的兼容性不是专程好,不过前景相比较光明,浏览器都在准备协理。现阶段能够组合offline
cache的Manifest做离线应用。

连锁阅读:

  1. 干什么要把网站升级到HTTPS
  2. 什么样把网址升级到http/二
  3. 自家是什么让网址用上HTML伍Manifest

1 赞 1 收藏
评论

图片 12

3. Installed / Waiting

1旦设置成功,Service Worker
进入installed(waiting)状态。在此景况中,它是1个一蹴而就的但尚无激活的
worker。它从没纳入 document 的操纵,确切来说是在守候着从脚下 worker
接手。

处于 Waiting 状态的 SW,在偏下之一的境况下,会被触发 Activating 状态。

4. Activating

处在 activating 状态之间,SW 脚本中的 activate
事件被实践。大家常常在 activate 事件中,清理 cache
中的文件(清除旧Worker的缓存文件)。

SW激活退步,则向来进去抛弃(redundant)状态。

5. Activated

1旦激活成功,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个离线应用。

咱俩只兑现最简易的作用:用户每发送3个http请求,大家就用SW捕获那个请求,然后在缓存里找是或不是缓存了那个请求对应的响应内容,即便找到了,就把缓存中的内容再次来到给主页面,不然再发送请求给服务器。

二、 register 注册

首先要登记多少个SW,在index.js文件中:

// index.js

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

知识点:

1. window.navigator

回到七个Navigator指标,该对象简单的话就是允许大家收获大家用户代理(浏览器)的部分新闻。比如,浏览器的合法名称,浏览器的本子,网络连接情形,设备地点新闻等等。

2. navigator.serviceWorker

回到1个
ServiceWorkerContainer对象,该指标允许我们对SW举行注册、删除、更新和通讯。

下面的代码中率先判断navigator是否有serviceWorker属性(存在的话代表浏览器帮衬SW),假诺存在,那么通过navigator.serviceWorker.register()(也就是ServiceWorkerContainer.register())来注册1个新的SW,.register()收受三个
路径 作为第三个参数。

ServiceWorkerContainer.register()回来二个Promise,所以能够用.then()
.catch()来进展接二连三处理。

三. SW的作用域

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

假定是这么:.register('/controlled/sw.js'),sw.js的功效域是/controlled。

大家得以手动为SW钦命二个成效域:
.register('service-worker.js', { scope: './controlled' });

三. 为什么在load事件中开始展览注册

为啥必要在load事件运营呢?因为您要分外运行三个线程,运转未来你恐怕还会让它去加载财富,那几个都以索要占用CPU和带宽的,大家理应保障页面能符合规律加载完,然后再起步大家的后台线程,不可能与正规的页面加载发生竞争,那几个在低端移动装备意义相比大。

三、install 安装

咱俩早就注册好了SW,假使 sw.js
下载并且解析成功,大家的SW就进来安装阶段了,那时候会触发install事件。大家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对象,也能够去除某二个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事件能够用来干什么吧?若是我们今后换了三个新的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个离线网页应用”,那大家就做一个最简便的缓存策略:假设缓存中保存着伏乞的剧情,则赶回缓存中的内容,不然,请求新剧情,并缓存新内容。

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