必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

即渐进式web应用必威,原文出处

2019-09-19 08:47 来源:未知

小结

时至明天,相信您假使根据本文一步一步操作下来,你也足以连忙把温馨的Web应用转为PWA。在转为了PWA后,若是有使用满意PWA 模型的前端控件的要求,你能够试试纯前端表格控件SpreadJS,适用于 .NET、Java 和移动端等楼台的报表控件一定不会令你失望的。

原稿链接:

1 赞 1 收藏 评论

必威 1

加上到主屏

PWA协理将web应用在主屏桌面上增添四个快捷方式,以福利客户火速采访,相同的时间升级web应用重复访谈的可能率。你大概会说,将来运动端上的浏览器功能列表里一般都提供了“加多到桌面”的职能呀,然而PWA与浏览器自带的丰裕到桌面包车型地铁贯彻格局区别。

PWA无需顾客特意去功效列表中运用那些功能开关,而是在顾客访谈web应用时,直接在分界面中唤醒几个增进到主屏桌面包车型地铁横幅,从web应用角度来说,那实际就是一往直前与低沉的区分。

PWA实现该效用极度轻巧,只供给一个manifest.json文件,文件中客商能够自定义应用的运行页面、模板颜色、Logo等剧情。上面是作者的manifest.json文件设置,大家可作参谋:

{

"short_name": "蝉知能源站",

"name": "蝉知财富站",

"icons": [

{

"src": "chanzhiicon.png",

"type": "image/png",

"sizes": "48x48"

},

{

"src": "192.png",

"type": "image/png",

"sizes": "192x192"

},

{

"src": "512.png",

"type": "image/png",

"sizes": "512x512"

},

{

"src": "144.png",

"type": "image/png",

"sizes": "144x144"

}

],

"start_url": "/index.html",

"display": "standalone",

"background_color": "#2196F3",

"orientation": "landscape",

"theme_color": "#2196F3"

}

内需提示的是,如今运动端IOS系统的补助并不好,安卓手提式有线电话机上须动用57本子以上的Google浏览器能够支撑该意义,上边是本人在安卓手提式有线电话机上的操作演示:

必威 2拉长到主屏

(4)cache html

下边第(3)步把图片、js、css缓存起来了,然则一旦把页面html也缓存了,举个例子把首页缓存了,就会有三个狼狈的难点——ServiceWorker是在页面注册的,不过将来得到页面包车型地铁时候是从缓存取的,每一回都以平等的,所以就招致力不胜任创新瑟维斯Worker,如形成sw-5.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 UPAJEROL(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 "

  • url); cache.delete(url, {ignoreVary: true}); }); } };
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: 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 "

  • 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
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。客商发布完商议后会触发刷新页面,刷新的时候缓存已经被删了就能够再一次去乞求了。

与上述同类就化解了实时更新的难点。

要是设置退步了,未有很优雅的法子赢得通报

假使一个worker被注册了,然则并未有现身在chrome://inspect/#service-workers或chrome://serviceworker-internals,那么很可能因为异常而安装失败了,或者是产生了一个被拒绝的的promise给event.waitUtil。

要消除那类难题,首先到 chrome://serviceworker-internals检查。打开开发者工具窗口准备调试,然后在你的install event代码中添加debugger;语句。这样,通过断点调试你更容易找到问题。

缓存静态财富

第一是像 CSS、JS 这么些静态能源,因为本身的博客里援用的剧本样式都是通过 hash 做漫长化缓存,类似于:main.ac62dexx.js 那样,然后打开强缓存,那样下一次顾客下一次再走访小编的网址的时候就无须再行诉求能源。直接从浏览器缓存中读取。对于那部分财富,service worker 没须求再去管理,间接放行让它去读取浏览器缓存就可以。

自己感到一旦你的站点加载静态能源的时候作者并未有开启强缓存,何况你只想通过前端去达成缓存,而没有要求后端在加入实行调解,那能够利用 service worker 来缓存静态财富,不然就有一点点画蛇添足了。

Activate 事件

本条事件会在service worker被激活时发出。你或许没有要求这几个事件,但是在演示代码中,大家在该事件产生时将老的缓存全部清理掉了:

// clear old caches function clearOldCaches() { return caches.keys() .then(keylist => { return Promise.all( keylist .filter(key => key !== CACHE) .map(key => caches.delete(key)) ); }); } // application activated self.addEventListener('activate', event => { console.log('service worker: activate'); // delete old caches event.waitUntil( clearOldCaches() .then(() => self.clients.claim()) ); });

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
// clear old caches
function clearOldCaches() {
  return caches.keys()
    .then(keylist => {
      return Promise.all(
        keylist
          .filter(key => key !== CACHE)
          .map(key => caches.delete(key))
      );
    });
}
// application activated
self.addEventListener('activate', event => {
  console.log('service worker: activate');
    // delete old caches
  event.waitUntil(
    clearOldCaches()
    .then(() => self.clients.claim())
    );
});

注意self.clients.claim()试行时将会把当下service worker作为被激活的worker。

Fetch 事件 该事件将会在互连网初阶央浼时发起。该事件管理函数中,大家得以行使respondWith()情势来威胁HTTP的GET诉求然后再次回到:

  1. 从缓存中取到的财富文件
  2. 万一第一步失利,能源文件将会从网络中动用Fetch API来得到(和service worker中的fetch事件非亲非故)。获取到的能源将会投入到缓存中。
  3. 即便第一步和第二步均战败,将会从缓存中回到准确的能源文件。

// application fetch network data self.addEventListener('fetch', event => { // abandon non-GET requests if (event.request.method !== 'GET') return; let url = event.request.url; event.respondWith( caches.open(CACHE) .then(cache => { return cache.match(event.request) .then(response => { if (response) { // return cached file console.log('cache fetch: ' + url); return response; } // make network request return fetch(event.request) .then(newreq => { console.log('network fetch: ' + url); if (newreq.ok) cache.put(event.request, newreq.clone()); return newreq; }) // app is offline .catch(() => offlineAsset(url)); }); }) ); });

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
31
32
33
34
35
36
37
38
39
40
41
// application fetch network data
self.addEventListener('fetch', event => {
  // abandon non-GET requests
  if (event.request.method !== 'GET') return;
  let url = event.request.url;
  event.respondWith(
    caches.open(CACHE)
      .then(cache => {
        return cache.match(event.request)
          .then(response => {
            if (response) {
              // return cached file
              console.log('cache fetch: ' + url);
              return response;
            }
            // make network request
            return fetch(event.request)
              .then(newreq => {
                console.log('network fetch: ' + url);
                if (newreq.ok) cache.put(event.request, newreq.clone());
                return newreq;
              })
              // app is offline
              .catch(() => offlineAsset(url));
          });
      })
  );
});

offlineAsset(url)措施中采纳了部分helper方法来回到正确的多少:

// 是还是不是为图片地址? let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f); function isImage(url) { return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false); } // return 再次回到离线财富 function offlineAsset(url) { if (isImage(url)) { // 重返图片 return new Response( '<svg role="img" view博克斯="0 0 400 300" xmlns=" d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>', { headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'no-store' }} ); } else { // return page return caches.match(offlineURL); } }

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
31
32
// 是否为图片地址?
let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f);
function isImage(url) {
  
  return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false);
  
}
  
  
// return 返回离线资源
function offlineAsset(url) {
  
  if (isImage(url)) {
  
    // 返回图片
    return new Response(
      '<svg role="img" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title>offline</title><path d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>',
      { headers: {
        'Content-Type': 'image/svg+xml',
        'Cache-Control': 'no-store'
      }}
    );
  
  }
  else {
  
    // return page
    return caches.match(offlineURL);
  
  }
  
}

offlineAsset()办法检查诉求是还是不是为四个图纸,然后回到三个含有“offline”文字的SVG文件。其余央求将会回来 offlineURAV4L 页面。

Chrome开垦者工具中的ServiceWorker部分提供了有关当前页面worker的音信。个中会来得worker中生出的一无可取,还足以强制刷新,也得以让浏览器步入离线方式。

Cache Storage 部分例举了当前颇具曾经缓存的资源。你可以在缓存要求更新的时候点击refresh开关。

Service Worker

ServiceWorker是PWA的大旨手艺,它亦可为web应用提供离线缓存功效,当然不仅仅如此,下边罗列了有些ServiceWorker的风味:

基于HTTPS 情形,那是创设PWA的硬性前提。(LAMP景况网站升级HTTPS应用方案)

是贰个独门的 worker 线程,独立于当下网页进度,有谈得来独自的 worker context

可掣肘HTTP央求和响应,可缓存文件,缓存的文件能够在网络离线状态时取到

能向顾客端推送音讯

不能够直接操作 DOM

异步完成,内部大都以因而 Promise 完成

(2)Service Worker安装和激活

注册完之后,ServiceWorker就能够进展设置,那年会触发install事件,在install事件之中能够缓存一些资源,如下sw-3.js:

JavaScript

const CACHE_NAME = "fed-cache"; this.addEventListener("install", function(event) { this.skipWaiting(); console.log("install service worker"); // 创造和开荒二个缓存库 caches.open(CACHE_NAME); // 首页 let cacheResources = ["]; 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调控台所示:

必威 3

ServiceWorker的API基本上都以回来Promise对象制止堵塞,所以要用Promise的写法。上面在安装ServiceWorker的时候就把首页的伸手给缓存起来了。在ServiceWorker的运行情况之中它有一个caches的大局对象,这一个是缓存的输入,还大概有二个常用的clients的全局对象,二个client对应二个标签页。

在ServiceWorker里面能够动用fetch等API,它和DOM是割裂的,未有windows/document对象,不大概直接操作DOM,不恐怕直接和页面交互,在瑟维斯Worker里面不能够得知当前页面展开了、当前页面包车型客车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事件了,咱们希望每得到贰个能源就把它缓存起来,就毫无像上一篇涉嫌的Manifest须要先生成一个列表。

你恐怕会问,当自家刷新页面包车型大巴时候不是又重新挂号安装和激活了三个ServiceWorker?纵然又调了二遍注册,但并不会再度注册,它发掘”sw-3.js”这么些早就登记了,就不会再登记了,进而不会触发install和active事件,因为最近ServiceWorker已经是active状态了。当须求创新ServiceWorker时,如形成”sw-4.js”,可能改造sw-3.js的文件内容,就能够再度注册,新的ServiceWorker会先install然后步入waiting状态,等到重启浏览器时,老的ServiceWorker就能够被沟通掉,新的ServiceWorker踏入active状态,假诺不想等到再度起动浏览器能够像上边同样在install里面调skipWaiting:

JavaScript

this.skipWaiting();

1
this.skipWaiting();

Service Worker的装置步骤

在页面上实现注册手续之后,让我们把专注力转到service worker的脚本里来,在这其间,大家要到位它的安装步骤。

在最中央的事例中,你要求为install事件定义贰个callback,并垄断(monopoly)怎样文件你想要缓存。

JavaScript

// The files we want to cache var urlsToCache = [ '/', '/styles/main.css', '/script/main.js' ]; // Set the callback for the install step self.addEventListener('install', function(event) { // Perform install steps });

1
2
3
4
5
6
7
8
9
10
11
// The files we want to cache
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];
 
// Set the callback for the install step
self.addEventListener('install', function(event) {
    // Perform install steps
});

在我们的install callback中,我们需求实践以下步骤:

  1. 张开二个缓存
  2. 缓存大家的文件
  3. 调控是或不是具有的能源是还是不是要被缓存

JavaScript

var CACHE_NAME = 'my-site-cache-v1'; var urlsToCache = [ '/', '/styles/main.css', '/script/main.js' ]; self.addEventListener('install', function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];
 
self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

地方的代码中,大家由此caches.open张开大家内定的cache文件名,然后大家调用cache.addAll并传到大家的文书数组。那是经过系列promise(caches.open 和 cache.addAll)完毕的。event.waitUntil获得一个promise并选拔它来赢得安装成本的年华以及是或不是安装成功。

固然具有的文本都被缓存成功了,那么service worker就设置成功了。如若其余二个文件下载败北,那么安装步骤就能够停业。那几个法子允许你依附于你和谐钦点的具备能源,然则那意味你须求非常严苛地调节怎样文件供给在设置步骤中被缓存。钦点了太多的文本的话,就能够追加设置战败率。

上面只是一个简易的例证,你可以在install事件中试行别的操作依然以至忽视install事件。

页面缓存战略

因为是 React 单页同构应用,每一遍加载页面的时候数据都以动态的,所以笔者利用的是:

  1. 互连网优先的主意,即优先得到网络上最新的能源。当网络诉求退步的时候,再去获得service worker 里以前缓存的财富
  2. 当网络加载成功现在,就立异 cache 中对应的缓存资源,保险后一次每回加载页面,都以上次访谈的摩登财富
  3. 例如找不到 service worker 中 url 对应的财富的时候,则去赢得 service worker 对应的 /index.html 默许首页

// sw.js self.add伊芙ntListener('fetch', (e) => { console.log('未来正在呼吁:' + e.request.url); const currentUrl = e.request.url; // 匹配上页面路线 if (matchHtml(currentUrl)) { const requestToCache = e.request.clone(); e.respondWith( // 加载网络上的财富fetch(requestToCache).then((response) => { // 加载失败 if (!response || response.status !== 200) { throw Error('response error'); } // 加载成功,更新缓存 const responseToCache = response.clone(); caches.open(cacheName).then((cache) => { cache.put(requestToCache, responseToCache); }); console.log(response); return response; }).catch(function() { // 获取对应缓存中的数据,获取不到则失败到收获暗中同意首页 return caches.match(e.request).then((response) => { return response || caches.match('/index.html'); }); }) ); } });

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
// sw.js
self.addEventListener('fetch', (e) => {
  console.log('现在正在请求:' + e.request.url);
  const currentUrl = e.request.url;
  // 匹配上页面路径
  if (matchHtml(currentUrl)) {
    const requestToCache = e.request.clone();
    e.respondWith(
      // 加载网络上的资源
      fetch(requestToCache).then((response) => {
        // 加载失败
        if (!response || response.status !== 200) {
          throw Error('response error');
        }
        // 加载成功,更新缓存
        const responseToCache = response.clone();
        caches.open(cacheName).then((cache) => {
          cache.put(requestToCache, responseToCache);
        });
        console.log(response);
        return response;
      }).catch(function() {
        // 获取对应缓存中的数据,获取不到则退化到获取默认首页
        return caches.match(e.request).then((response) => {
           return response || caches.match('/index.html');
        });
      })
    );
  }
});

何以存在命中连连缓存页面包车型客车处境?

  1. 第一须求分明的是,客户在率先次加载你的站点的时候,加载页面后才会去运维sw,所以率先次加载不可能通过 fetch 事件去缓存页面
  2. 自个儿的博客是单页应用,可是客商并不一定会因而首页步向,有希望会由此任何页面路线步向到本身的网址,那就变成自家在 install 事件中根本不可能钦赐须求缓存那多少个页面
  3. 最终兑现的功效是:客户率先次张开页面,马上断掉互连网,还是得以离线访问我的站点

结合方面三点,笔者的办法是:第三次加载的时候会缓存 /index.html 那一个能源,何况缓存页面上的多寡,假若客户及时离线加载的话,那时候并从未缓存对应的路径,比如 /archives 资源访谈不到,那再次来到 /index.html 走异步加载页面包车型客车逻辑。

在 install 事件缓存 /index.html,保险了 service worker 第一回加载的时候缓存暗中同意页面,留下退路。

import constants from './constants'; const cacheName = constants.cacheName; const apiCacheName = constants.apiCacheName; const cacheFileList = ['/index.html']; self.addEventListener('install', (e) => { console.log('Service Worker 状态: install'); const cacheOpenPromise = caches.open(cacheName).then((cache) => { return cache.addAll(cacheFileList); }); e.waitUntil(cacheOpenPromise); });

1
2
3
4
5
6
7
8
9
10
11
12
import constants from './constants';
const cacheName = constants.cacheName;
const apiCacheName = constants.apiCacheName;
const cacheFileList = ['/index.html'];
 
self.addEventListener('install', (e) => {
  console.log('Service Worker 状态: install');
  const cacheOpenPromise = caches.open(cacheName).then((cache) => {
    return cache.addAll(cacheFileList);
  });
  e.waitUntil(cacheOpenPromise);
});

在页面加载完后,在 React 组件中立时缓存数据:

// cache.js import constants from '../constants'; const apiCacheName = constants.apiCacheName; export const saveAPIData = (url, data) => { if ('caches' in window) { // 伪造 request/response 数据 caches.open(apiCacheName).then((cache) => { cache.put(url, new Response(JSON.stringify(data), { status: 200 })); }); } }; // React 组件 import constants from '../constants'; export default class extends PureComponent { componentDidMount() { const { state, data } = this.props; // 异步加载数据 if (state === constants.INITIAL_STATE || state === constants.FAILURE_STATE) { this.props.fetchData(); } else { // 服务端渲染成功,保存页面数据 saveAPIData(url, data); } } }

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
// cache.js
import constants from '../constants';
const apiCacheName = constants.apiCacheName;
 
export const saveAPIData = (url, data) => {
  if ('caches' in window) {
    // 伪造 request/response 数据
    caches.open(apiCacheName).then((cache) => {
      cache.put(url, new Response(JSON.stringify(data), { status: 200 }));
    });
  }
};
 
// React 组件
import constants from '../constants';
export default class extends PureComponent {
  componentDidMount() {
    const { state, data } = this.props;
    // 异步加载数据
    if (state === constants.INITIAL_STATE || state === constants.FAILURE_STATE) {
      this.props.fetchData();
    } else {
        // 服务端渲染成功,保存页面数据
      saveAPIData(url, data);
    }
  }
}

如此就确认保证了顾客率先次加载页面,立即离线访谈站点后,即使不大概像第二遍同样能够服务端渲染数据,可是随后能因而获取页面,异步加载数据的点子营造离线应用。

必威 4

顾客率先次访问站点,假诺在不刷新页面包车型客车场馆切换路由到另外页面,则会异步获取到的数额,当后一次拜访对应的路由的时候,则战败到异步获取数据。

必威 5

当客户第一遍加载页面包车型地铁时候,因为 service worker 已经调控了站点,已经持有了缓存页面包车型地铁本领,之后在拜候的页面都将会被缓存恐怕更新缓存,当客商离线访问的的时候,也能访谈到服务端渲染的页面了。

必威 6

渐进式Web应用的大旨

渐进式Web应用是一种新的技巧,所以使用的时候自然要小心。也等于说,渐进式Web应用能够让你的网址在几个时辰内获得改进,何况在不协助渐进式Web应用的浏览器上也不会影响网址的展现。

可是大家供给思索以下几点:

Service Worker生命周期

serviceworker的使用流程能够轻松计算为注册--安装--激活。

注册其实正是报告浏览器serviceworkerJS文件存放在什么职位,然后浏览器下载、分析、试行该公文,从而运营安装。这里小编创造多个app.js文件,注册代码如下,将该公文在网址的head标签里引进。

if ('serviceWorker' in navigator) {

window.addEventListener('load', function () {

navigator.serviceWorker.register

.then(function (registration) {

// 注册成功

console.log('ServiceWorker registration successful with scope: ', registration.scope);

})

.catch(function {

// 注册失利:(

console.log('ServiceWorker registration failed: ', err);

});

});

}

当推行serviceworkerJS文件时,首先接触的是install事件,举办安装。安装的长河正是将钦赐的局地静态财富扩充离线缓存。下边是自己的sw.js文件中的安装代码:

var CACHE_VERSION = 'sw_v8';

var CACHE_FILES = [

'/js/jquery/min.js',

'/js/zui/min.js',

'/js/chanzhi.js',

];

self.addEventListener('install', function {

event.waitUntil(

caches.open(CACHE_VERSION)

.then(cache => cache.addAll(CACHE_FILES)

));

});

当安装成功后,serviceworker就能够激活,那时就能管理 activate 事件回调 (提供了立异缓存战略的机缘)。并得以管理功用性的事件 fetch 、sync 、push 。

self.addEventListener('activate', function {

event.waitUntil(

caches.keys().then(function{

return Promise.all(keys.map(function{

if(key !== CACHE_VERSION){

return caches.delete;

}

}));

})

);

});

(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福特ExplorerS,那么能够把request的mod改成cors。假诺央浼退步了,如404依然是逾期等等的,那么也直接回到response让主页面处理,不然的话表明加载成功,把那几个response克隆三个放权cache里面,然后再回到response给主页面线程。注意能缓慢存里的财富一般只可以是GET,通过POST获取的是无法缓存的,所以要做个判定(当然你也得以手动把request对象的method改成get),还应该有把一些民用不愿意缓存的财富也做个推断。

如此即便客户展开过壹遍页面,ServiceWorker就安装好了,他刷新页面大概展开第二个页面包车型地铁时候就可见把央求的财富一一做缓存,蕴涵图形、CSS、JS等,只要缓存里有了随便顾客在线或许离线都能够平常访问。那样我们当然会有一个主题素材,这一个缓存空间到底有多大?上一篇大家关系Manifest也好不轻巧地点存款和储蓄,PC端的Chrome是5Mb,其实那些说法在新本子的Chrome已经不标准了,在Chrome 61版本能够看看当地存储的空间和利用状态:

必威 7

当中Cache Storage是指ServiceWorker和Manifest占用的长空尺寸和,上海体育场所可以观察总的空间大小是20GB,大致是unlimited,所以基本上不用挂念缓存会远远不足用。

越来越多内容

那边有局地有关的文书档案能够参照他事他说加以考察:

测试

今后,大家早就达成了接纳 service worker 对页面进行离线缓存的效劳,若是想感受效果的话,访谈小编的博客:

轻松浏览任性的页面,然后关掉网络,再度拜谒,在此以前你浏览过的页面都足以在离线的景况下打开拜谒了。

IOS 需求 11.3 的版本才支撑,使用 Safari 实行拜访,Android 请选取协助service worker 的浏览器

Install事件

该事件就要选拔设置到位后触发。大家一般在此处运用Cache API缓存一些少不了的文本。

首先,大家要求提供如下配置

  1. 缓存名称(CACHE)以及版本(version)。应用能够有多少个缓存存款和储蓄,可是在接纳时只会使用在那之中多少个缓存存款和储蓄。每当缓存存款和储蓄有变化时,新的本子号将会钦定到缓存存储中。新的缓存存款和储蓄将会作为当前的缓存存款和储蓄,在此之前的缓存存储将会被作废。
  2. 四个离线的页面地址(offlineUTiguanL):当客商访谈了前头未有访谈过的地方时,该页面将会来得。
  3. 二个分包了独具必得文件的数组,饱含保持页面符合规律职能的CSS和JavaScript。在本示例中,笔者还加多了主页和logo。当有例外的U揽胜极光L指向同一个财富时,你也可以将这个U汉兰达L分别写到这么些数组中。offlineU途乐L将会参与到那几个数组中。
  4. 大家也能够将部分非须求的缓存文件(installFilesDesirable)。那个文件在装置进程少校会被下载,但只要下载失利,不会触发安装退步。

// 配置文件 const version = '1.0.0', CACHE = version + '::PWAsite', offlineUEvoqueL = '/offline/', installFilesEssential = [ '/', '/manifest.json', '/css/styles.css', '/js/main.js', '/js/offlinepage.js', '/images/logo/logo152.png' ].concat(offlineURL), installFilesDesirable = [ '/favicon.ico', '/images/logo/logo016.png', '/images/hero/power-pv.jpg', '/images/hero/power-lo.jpg', '/images/hero/power-hi.jpg' ];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 配置文件
const
  version = '1.0.0',
  CACHE = version + '::PWAsite',
  offlineURL = '/offline/',
  installFilesEssential = [
    '/',
    '/manifest.json',
    '/css/styles.css',
    '/js/main.js',
    '/js/offlinepage.js',
    '/images/logo/logo152.png'
  ].concat(offlineURL),
  installFilesDesirable = [
    '/favicon.ico',
    '/images/logo/logo016.png',
    '/images/hero/power-pv.jpg',
    '/images/hero/power-lo.jpg',
    '/images/hero/power-hi.jpg'
  ];

installStaticFiles() 方法运用基于Promise的不二诀窍选择Cache API将文件存款和储蓄到缓存中。

// 安装静态财富 function installStaticFiles() { return caches.open(CACHE) .then(cache => { // 缓存可选文件 cache.addAll(installFilesDesirable); // 缓存必得文件 return cache.addAll(installFilesEssential); }); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 安装静态资源
function installStaticFiles() {
  return caches.open(CACHE)
    .then(cache => {
      // 缓存可选文件
      cache.addAll(installFilesDesirable);
      // 缓存必须文件
      return cache.addAll(installFilesEssential);
    });
}

最终,大家增多多个install的轩然大波监听器。waitUntil办法保障了service worker不会安装直到其连带的代码被试行。这里它会实行installStaticFiles()方法,然后self.skipWaiting()方式来激活service worker:

// 应用设置 self.addEventListener('install', event => { console.log('service worker: install'); // 缓存首要文件 event.waitUntil( installStaticFiles() .then(() => self.skipWaiting()) ); });

1
2
3
4
5
6
7
8
9
10
11
12
// 应用安装
self.addEventListener('install', event => {
  console.log('service worker: install');
  // 缓存主要文件
  event.waitUntil(
    installStaticFiles()
    .then(() => self.skipWaiting())
  );
});

关于PWA

PWA(Progressive Web App), 即渐进式web应用。PWA本质上是web应用,指标是因而多项新技艺,在平安、品质、体验等方面给客户原生应用的经验。并且无需像原生应用那样繁琐的下载、安装、晋级等操作。

此地解释下概念中的“渐进式”,意思是以此web应用还在不断地开垦进取中。因为如今来讲,PWA还从未成熟到一蹴即至的水准,想在海东、品质、体验上完成原生应用的功用依然有相当的多的升官空间的。一方面是创设PWA的资金难题,另一方面是现阶段各大浏览器、安卓和IOS系统对于PWA的协助和包容性还应该有待提升。

正文作者将从网站缓存、http央求拦截、推送到主屏等成效,结合实例操作,一步步引领咱们一点也不慢玩转PWA技艺。

3. 使用Service Worker

ServiceWorker的行使套路是先挂号叁个Worker,然后后台就能运维一条线程,能够在那条线程运营的时候去加载一些能源缓存起来,然后监听fetch事件,在那些事件里拦截页面包车型客车呼吁,先看下缓存里有未有,借使有平素回到,不然正常加载。可能是一最先不缓存,各样财富央求后再拷贝一份缓存起来,然后下一回呼吁的时候缓存里就有了。

Service Worker 是什么?

多个 service worker 是一段运维在浏览器后台进度里的脚本,它独立于当下页面,提供了那一个无需与web页面交互的效劳在网页背后悄悄试行的力量。在现在,基于它能够完毕消息推送,静默更新以及地理围栏等劳务,可是当前它首先要负有的效果与利益是阻挠和拍卖网络央求,包蕴可编制程序的响应缓存管理。

缘何说这一个API是多少个要命棒的API呢?因为它使得开荒者能够支撑特别好的离线体验,它赋予开垦者完全调节离线数据的力量。

在service worker建议从前,其他一个提供开采者离线体验的API叫做App Cache。但是App Cache有个别局限性,举例它能够很轻松地缓和单页应用的难点,不过在多页应用上会很麻烦,而Serviceworkers的产出就是为了化解App Cache的痛点。

上面详细说一下service worker有何样必要静心的地方:

  • 它是JavaScript Worker,所以它不可能直接操作DOM。然而service worker能够因而postMessage与页面之间通讯,把音信文告给页面,若是必要的话,让页面自个儿去操作DOM。
  • Serviceworker是四个可编制程序的互连网代理,允许开荒者调控页面上拍卖的网络央浼。
  • 在不被选取的时候,它会本人终止,而当它再度被用到的时候,会被重新激活,所以您无法依赖于service worker的onfecth和onmessage的管理函数中的全局状态。即使您想要保存一些长久化的消息,你能够在service worker里使用IndexedDB API。
  • Serviceworker多量使用promise,所以只要你不打听怎么是promise,这您需求先读书这篇文章。

明显什么财富需求被缓存?

那么在起来使用 service worker 以前,首先须要通晓哪些财富须要被缓存?

TAG标签:
版权声明:本文由必威发布于必威-前端,转载请注明出处:即渐进式web应用必威,原文出处