Progressive Web Applications

Progressive Web Applications take advantage of new technologies to bring the best of mobile sites and native applications to users. They're reliable, fast, and engaging.html

基础知识

用户首次访问service worker控制的网站或页面时,service worker会马上被下载。
ServiceWorker(web worker 的一种)接口
Cache:表示对request/response对象的存储,一个域能够有多个 Cache 对象. 你将在你的代码中处理和更新缓存 . 在 Cache 除非显示地更新缓存, 不然缓存将不会被更新; 缓存数据不会过时, 除非删除它
Cache.match(request, options)返回一个Promise,查找cache中匹配的request
Cache.match(request, options)匹配一个数组对象中的request
Cache.add(request)发送请求并将请求放入cache中,
Cache.put(request, response)将request和response都添加到cache中
Cache.delete(request, options) 才cache中查找乡音的值,并删除返回一个promise,resoleve为true,若是找不到返回false
Cache,keys(request, options)返回一个promise,resolve为全部的cache键值vue

CacheStorage: 对Cache对象的存储,提供命名缓存的主目录,sw能够经过访问并维护名字字符串到Cache对象的映射
caches.open(cacheName).then(names){};//打开一个cache对象ios

Client: 表示sw client的做用域。git

sw.js中的self:这个关键字表示的是一个service worker 的执行上下文的一个全局属性(ServiceWorkerGlobalScope),相似于window对象,不过这个self是做用于service worker的全局做用域中。github

sw生命周期

image.png

覆盖率

image.png

注意点

  1. 基于https
    可使用http-server+ngrok配合,固然更简单的使用github。
    2.Service worker是一个注册在指定源和路径下的事件驱动worker。实际上 SW 在你网页加载完成一样也能捕获已经发出的请求,因此,为了减小性能损耗,咱们通常直接在 onload 事件里面注册 SW 便可。
  2. 做用域问题
    SW 的做用域不一样,监听的 fetch 请求也是不同的。 例如,咱们将注册路由换成: /example/sw.js,那么,SW 后面只会监听 /example 路由下的全部 fetch 请求,而不会去监听其余

register

if(navigator.serviceWorker){
    navigator.serviceWorder.register('sw.js')
        .then(registration  =>  {
              console.log(`registered event at scope:${registration.scope}`);
        })
        .cache(err => {
               throw err;
         })
}

install

self.addEventListener('install', function(event) {
  // Perform install steps
});

缓存文件web

const cacheVersion = 'v1';
const cacheList = [
    '/',
    'index.html',
    'logo.png',
    'manifest.json',
    '/dist/build.js'
];
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheVersion).then(function(cache) {
      return cache.addAll([cacheList]);
    })
  );
});

event.waitUntil()参数必须为promise,它能够延长一个事件的做用时间,由于咱们在打开缓存或者更新的时候颇有可能会有延迟,而event.waitUntil()能够防止事件终端。另外它会监听全部的异步promise,一旦有一个reject那么该次event即是失败的,也就是说sw启动失败。固然若是有些文件比较大很差缓存的话别让它返回就行了:json

cache.addAll([cachelist1]);
return cache.addAll([cachelist2]);

fetchEvent

缓存捕获,当发起请求的时候将request和response缓存下来(缓存一开始定义的缓存列表)。axios

self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                if(response){
                    return reponse;
                 }
                  return fetch(event.request);
             })
    )
})

这是个比较简单的格式,event.respondWith(r),包含请求响应代码,能够设置一个参数r,r是一个promise,resolve以后是一个response对象。整段代码意思就是当请求一个文件时,若是缓存中已经有了,那么就直接返回缓存结果,不然发起请求。数组

问题:若是没有缓存咱们怎么处理?
  1. 等下次sw根据路由去缓存;
  2. 手动缓存promise

    手动缓存
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                if(response){
                    return response;
                }
                //fetch请求的request、response都定义为stream对象,因此只能读一次这里须要clone一个新的
                let requestObj = event.request.clone();

                return fetch(requestObj)
                            .then(response => {
                                //检测是否成功
                                if(!response || response.status !== 200 || response.type !== 'basic') {
                                    return response;
                                }
                                //若是请求成功,第一要去渲染,第二要缓存
                                //cache.put()也使用stream,因此这里也须要复制一份
                                let responseObj = response.clone();

                                caches.open(cacheVersion)
                                    .then(cache => {
                                        cache.put(event.request, responseObj);
                                    });
                                return response;

                            })
            })
    )
})
为何stream只能读一次?

当可读流读取一次以后可能已经读到stream结尾或者stream已经close了,这里request和response都实现了clone接口来复制一份,因此在须要二次使用stream的时候就须要用副原本实现了。

删除旧的缓存

self.addEventListener('activate', evnet => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.filter(cachename => {
                    if(cachename == cacheVersion){
                        return caches.delete(cachename);
                    }
                })
            ).then(() => {
                return self.clients.claim()
            })
        })
    )
})

咱们检查以前保存的sw缓存,还要注意一点就是Promise.all()中不能有undefined,因此咱们对于相同的版本要过滤,于是不使用map,避免返回undefined。
经过调用 self.clients.claim() 取得页面的控制权, 这样以后打开页面都会使用版本更新的缓存。

更新

当你更新了你的sw文件,并修改了cacheVersion以后,刷新浏览器,期待的变化并无发生,由于虽然你改变了缓存版本,可是此时旧的sw还在控制整个应用,新的sw并无生效。这时就须要更新一下sw,有如下方法

  1. registration.update() ,也就是在注册的时候选择合适方式更新
navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
    reg.update();
});
  1. 使用self.skipWaiting();
    在install阶段使用这个可使得新的sw当即生效。
self.addEventListener('install', event => {
      self.skipWaiting();

      event.waitUntil(
        // caching
      );
});
  1. 调试手动更新
    image.png

直接点击update便可。
注意,咱们更新了某些文件的时候也要同时更新sw中的缓存版本(cacheVersion)

manifest文件

这个文件主要是配置添加到桌面的一些基本信息,好比图标启动页等。详细能够看这个https://developer.mozilla.org/zh-CN/docs/Web/Manifest

下面是我写的一个示例https://github.com/Stevenzwzhai/vue2.0-elementUI-axios-vueRouter/blob/master/pwa/sw.js

或者拉取这个项目https://github.com/Stevenzwzhai/PWA-demo

最近写了个知乎日报的pwa,有兴趣能够看下一篇文章

项目地址https://github.com/Stevenzwzhai/zhihu-daily
演示地址https://stevenzwzhai.github.io/zhihu-daily/

相关文章
相关标签/搜索