菜单

应用 Service Worker 做三个 PWA 离线网页应用

2019年4月4日 - Bootstrap

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

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

初稿出处:
人人网FED博客   

在上一篇《本人是如何让网址用上HTML五Manifest》介绍了怎么用Manifest做二个离线网页应用,结果被普遍网络朋友作弄说那一个东西已经被deprecated,移出web标准了,今后被ServiceWorker替代了,不管怎么样,Manifest的1对思虑仍然得以借用的。小编又将网址升级到了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的行事是把部分财富缓存起来,然后拦截页面包车型客车呼吁,先看下缓存库里有未有,如若有的话就从缓存里取,响应200,反之未有的话就走正规的请求。具体来说,ServiceWorker结合Web App Manifest能做到以下工作(那也是PWA的检查评定标准):

图片 2

包罗能够离线使用、断网时回来200、能唤起用户把网址添加3个图标到桌面上等。

温馨提示

二. 瑟维斯 Worker的援救景况

Service Worker近来只有Chrome/Firfox/Opera支持:

图片 3

Safari和艾德ge也在预备援救Service Worker,由于ServiceWorker是谷歌(Google)主导的1项专业,对于生态比较封闭的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. 储备知识
    ServiceWorker大批量使用Promise,不打听的请移步:Javascript:Promise对象基础

3. 使用Service Worker

ServiceWorker的行使套路是首先登场记三个Worker,然后后台就会运行一条线程,能够在那条线程运行的时候去加载一些能源缓存起来,然后监听fetch事件,在那么些事件里拦截页面的央求,先看下缓存里有未有,借使有一贯回到,不然符合规律加载。只怕是一开头不缓存,每一个财富请求后再拷贝一份缓存起来,然后下贰次呼吁的时候缓存里就有了。

兼容性

(一)注册2个瑟维斯 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文件正是瑟维斯Worker的周转条件,如若不能够不负众望注册的话就会抛万分,如Safari
TP固然有其一指标,但是会抛非凡无法运用,就能够在catch里面处理。那里有个难题是干什么需求在load事件运转呢?因为您要优秀运营一个线程,运转今后你恐怕还会让它去加载财富,这几个都以须求占用CPU和带宽的,我们应当保险页面能健康加载完,然后再起步我们的后台线程,不能够与正规的页面加载爆发竞争,这么些在低端移动设备意义相比大。

还有少数索要专注的是瑟维斯Worker和Cookie1样是有Path路径的定义的,假若你设定三个cookie倘诺叫time的path=/page/A,在/page/B这一个页面是不可见获得到这么些cookie的,假若设置cookie的path为根目录/,则怀有页面都能赢获得。类似地,假若注册的时候利用的js路径为/page/sw.js,那么这几个ServiceWorker只好管理/page路径下的页面和财富,而不可见处理/api路径下的,所以1般把ServiceWorker注册到顶尖目录,如上面代码的”/sw-三.js”,那样那个ServiceWorker就能接管页面包车型地铁具备财富了。

图片 6

(2)瑟维斯 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的运营条件之中它有2个caches的大局对象,这么些是缓存的输入,还有三个常用的clients的大局对象,一个client对应三个标签页。

在ServiceWorker里面能够选取fetch等API,它和DOM是隔绝的,未有windows/document对象,十分小概间接操作DOM,不能够直接和页面交互,在ServiceWorker里面不能获悉当前页面打开了、当前页面包车型大巴url是怎么,因为3个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-3.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,如若跨域的能源支撑CO汉兰达S,那么能够把request的mod改成cors。要是请求败北了,如40四或许是逾期等等的,那么也直接重回response让主页面处理,不然的话表达加载成功,把那几个response克隆2个置于cache里面,然后再回到response给主页面线程。注意能放缓存里的能源1般只能是GET,通过POST获取的是不能够缓存的,所以要做个判断(当然你也得以手动把request对象的method改成get),还有把部分个体不指望缓存的能源也做个判断。

诸如此类壹旦用户打开过3回页面,瑟维斯Worker就设置好了,他刷新页面或然打开第二个页面包车型地铁时候就可见把请求的财富11做缓存,包括图形、CSS、JS等,只要缓存里有了随便用户在线也许离线都能够不奇怪访问。那样大家本来会有三个难点,这一个缓存空间到底有多大?上1篇我们提到Manifest也终归地方存款和储蓄,PC端的Chrome是伍Mb,其实这几个说法在新本子的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本田CR-VL(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,假如是的话就去发个请求获取3个文本,依照那个文件的内容决定是不是须求删除缓存,这么些立异的函数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: []}}

其间首要有三个update提姆e的字段,假若地点内部存款和储蓄器未有这一个页面包车型地铁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缓存删了再度赢得,那是1个转头的音讯布告:

JavaScript

if (“serviceWorker” in navigator) {
document.querySelector(“.comment-form”).addEventListener(“submit”,
function() { navigator.serviceWorker.controller.postMessage({ type: 1,
desc: “remove html cache”, url: window.location.href} ); } }); }

1
2
3
4
5
6
7
8
9
10
if ("serviceWorker" in navigator) {
    document.querySelector(".comment-form").addEventListener("submit", function() {
            navigator.serviceWorker.controller.postMessage({
                type: 1,
                desc: "remove html cache",
                url: window.location.href}
            );
        }
    });
}

Service Worker也监听message事件:

JavaScript

const messageProcess = { // 删除html index 1: function (url) {
util.delCache(url); } }; let util = { delCache: function (url) {
caches.open(CACHE_NAME).then(cache => { console.log(“delete cache “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const messageProcess = {
    // 删除html index
    1: function (url) {
        util.delCache(url);
    }
};
 
let util = {
    delCache: function (url) {
        caches.open(CACHE_NAME).then(cache => {
            console.log("delete cache " + url);
            cache.delete(url, {ignoreVary: true});
        });
    }
};
 
this.addEventListener("message", function(event) {
    let msg = event.data;
    console.log(msg);
    if (typeof messageProcess[msg.type] === "function") {
        messageProcess[msg.type](msg.url);
    }
});

依照差异的新闻类型调差异的回调函数,假如是1的话正是去除cache。用户宣布完评论后会触发刷新页面,刷新的时候缓存已经被删了就会再也去乞求了。

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

1. Parsed

SW是一个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,还足以用ServiceWorker缓存,假如三者都用上了会如何呢?

会以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
被拒,则设置退步,Service Worker 直接进入吐弃(redundant)状态。

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

在意那里说的是其余2个Manifest,那么些Manifest是三个json文件,用来放网址icon名称等音信以便在桌面添加一个图标,以及成立壹种打开那几个网页就像是打开App壹样的机能。上边一直说的Manifest是被放弃的Application
Cache的Manifest。

其一Maifest.json文件能够如此写:

JavaScript

{ “short_name”: “人人FED”, “name”: “人人网FED,专注于前者技术”,
“icons”: [ { “src”: “/html/app-manifest/logo_48.png”, “type”:
“image/png”, “sizes”: “48×48” }, { “src”:
“/html/app-manifest/logo_96.png”, “type”: “image/png”, “sizes”: “96×96”
}, { “src”: “/html/app-manifest/logo_192.png”, “type”: “image/png”,
“sizes”: “192×192” }, { “src”: “/html/app-manifest/logo_512.png”,
“type”: “image/png”, “sizes”: “512×512” } ], “start_url”:
“/?launcher=true”, “display”: “standalone”, “background_color”:
“#287fc5”, “theme_color”: “#fff” }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "short_name": "人人FED",
  "name": "人人网FED,专注于前端技术",
  "icons": [
    {
      "src": "/html/app-manifest/logo_48.png",
      "type": "image/png",
      "sizes": "48×48"
    },
    {
      "src": "/html/app-manifest/logo_96.png",
      "type": "image/png",
      "sizes": "96×96"
    },
    {
      "src": "/html/app-manifest/logo_192.png",
      "type": "image/png",
      "sizes": "192×192"
    },
    {
      "src": "/html/app-manifest/logo_512.png",
      "type": "image/png",
      "sizes": "512×512"
    }
  ],
  "start_url": "/?launcher=true",
  "display": "standalone",
  "background_color": "#287fc5",
  "theme_color": "#fff"
}

icon须求预备各个准绳,最大要求51二px *
51二px的,那样Chrome会自动去挑选合适的图片。如若把display改成standalone,从转变的图标打开就会像打开多少个App一样,未有浏览器地址栏那一个东西了。start_url钦命打开之后的进口链接。

下一场添加二个link标签指向这么些manifest文件:

JavaScript

<link rel=”manifest” href=”/html/app-manifest/manifest.json”>

1
<link rel="manifest" href="/html/app-manifest/manifest.json">

这般组合Service Worker缓存:
图片 10把start_url指向的页面用ServiceWorker缓存起来,那样当用户用Chrome浏览器打开那个网页的时候,Chrome就会在底部弹1个提醒,询问用户是还是不是把那一个网页添加到桌面,尽管点“添加”就会扭转一个桌面图标,从那个图标点进去就像是打开2个App壹样。感受如下:

图片 11

比较狼狈的是Manifest如今唯有Chrome协助,并且不得不在安卓系统上行使,IOS的浏览器不恐怕添加1个桌面图标,因为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的生命周期了,那么未来就早先来做3个离线应用。

我们只兑现最容易易行的机能:用户每发送三个http请求,大家就用SW捕获这么些请求,然后在缓存里找是不是缓存了那个请求对应的响应内容,假使找到了,就把缓存中的内容再次回到给主页面,不然再发送请求给服务器。

二、 register 注册

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

回去1个Navigator对象,该目的简单来讲正是同意大家获得我们用户代理(浏览器)的一对消息。比如,浏览器的合法名称,浏览器的版本,互联网连接情况,设备地点新闻等等。

2. navigator.serviceWorker

回到1个
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钦命1个作用域:
.register('service-worker.js', { scope: './controlled' });

三. 怎么在load事件中展开登记

怎么须求在load事件运维呢?因为您要11分运行四个线程,运行现在你恐怕还会让它去加载能源,那一个都是索要占用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对象的投射。

咱俩得以因此该对象打开某三个特定的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须要缓存的静态财富和旧的分裂,那么大家就须要排除旧缓存。

为什么吧?因为一个域能用的缓存空间是个别的,假诺未有正确管理缓存数据,导致数据过大,浏览器会帮大家删除数据,那么只怕会误删大家想要留在缓存中的数据。

其1现在会详细讲,以往只供给精晓activate事件能用来清除旧缓存旧能够了。

五、 fetch事件

以往大家的SW已经激活了,那么能够起来捕获互联网请求,来增强网址的性质了。

当网页发出请求的时候,会触发fetch事件。

瑟维斯 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地图