PWA的概念以及用途

更新日期: 2020-10-10阅读: 2.3k标签: pwa

PWA英文为Progressive Web Apps,即渐进式Web应用,是提升Web Apps体验的一种新方法。


PWA简介

PWA 能做到原生应用的体验不是靠特指某一项技术,而是经过应用一些新技术进行改进,在安全、性能和体验三个方面都有很大提升,PWA 本质上是 Web App,借助一些新技术也具备了 Native App 的一些特性,兼具 Web App 和 Native App 的优点。

PWA的特点

  • 采用https协议(PWA的基础,没有 HTTPS,就没有 Service Worker)
  • 使用Service Worker(离线或者网络较差的情况下正常访问)
  • Manifest(允许将站点添加至主屏幕)
  • Notifications(允许服务器向用户提示一些信息)


Service Worker - 离线和缓存

前端有很多性能优化的手段,包括 CDN、css Sprite、文件的合并压缩、异步加载、资源缓存等等。其实绝大部分情况是在干一件事情,那就是尽量降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时。当然减少用户可感知的延时也不仅仅是在网络请求成本层面,还有浏览器渲染效率,代码质量等等,而Service Worker就是一种提升体验的的东东。

什么是 Service Worker

Service Worker最重要的特点是**持久的离线缓存**,这是它区别于Web Worker的主要能力。

Service Worker功能和特性:

  • 必须在HTTPS环境下才能工作
  • 独立的 worker 线程
  • 一旦被 install,就永远存在,除非被手动 unregister
  • 用到的时候可以直接唤醒,不用的时候自动睡眠
  • 能向客户端推送消息
  • 不能直接操作dom
  • 异步实现

Service Worker的作用域

Scope规定了Service Worker的作用范围。它在注册的时候已经明确了,如代码navigator.serviceWorker.register('/sw.js', {scope: '/'}),其作用的范围只能是它本身与它的子路径。

也就是说sw.js文件在根目录下,则它的作用域在根目录及子路径下,如果sw.js在/assets,则它的作用域只能在/assets及其子路径下,其他目录的serviceWorker不生效。

关于作用域的误解,Service Worker的作用域是指在浏览器地址栏上输入的url作用域,比如我设置作用域为/test/,那么url地址上包含/test/的才会被缓存,其他比如我请求/a/index.html就算这个页面里面包含/test/img.jpg这张图片也不会被缓存。我们可以通过navigator.serviceWorker.controller(在控制的输入,查看是否为actived)来判断某个client是否受控于service worker之下。

Service Worker的调试

Service Worker必须在HTTPS环境和本地环境下(127.0.0.1和localhost)才能被注册成功。

  • offline 可以将 DevTools 切换至离线模式。

  • Update on reload 强制Service Worker线程在每次页面加载时更新。

  • Bypass for network 绕过Service Worker线程并强制浏览器转至网络寻找请求的资源。

  • Update 对指定的Service Worker线程执行一次更新。

  • Push 在没有负载的情况下模拟推送通知。

  • Sync 模拟后台同步事件。

  • Unregister 注销指定的Service Worker线程。

  • Source 当前正在运行的Service Worker线程的安装时间。

  • Status Service Worker线程的状态。此行上的数字(上方截图中的#514)指示Service Worker线程已被更新的次数。如果启用update on reload复选框,您会注意到每次页面加载时此数字都会增大。

  • Clients 告诉Service Worker线程作用域的原点。 如果您已启用 show all 复选框,focus 按钮将非常实用。 在此复选框启用时,系统会列出所有注册的 Service Worker 线程。如果您点击正在不同标签中运行的Service Worker线程旁的focus 按钮,Chrome会聚焦到该标签。

怎么使用 Service Worker

注册

要安装 Service Worker, 我们需要通过在 js 主线程(常规的页面里的 js )注册 Service Worker 来启动安装,这个过程将会通知浏览器我们的 Service Worker 线程的 javaScript 文件在什么地方呆着。

如下,register 方法的 scope 参数是可选的,用于指定你想让 Service Worker 控制的内容的子目录。

if('serviceWorker' in navigator){
    // 在页面onload时候注册Service Worker
    window.addEventListener('load',function(){
        navigator.serviceWorker.register('/test.js',{scope: '/'})
        .then(function(success){
            console.log('注册成功')
        })
        .catch(function(err){

        })
    })
}

查看是否注册成功

在chrome 浏览器输入 chrome://inspect/#service-workers查看是否安装成功。还可以通过 chrome://serviceworker-internals 来查看服务工作线程详情

安装

Service Worker注册成功之后,我们的浏览器中已经有了一个属于你自己web App的worker context啦, 在此时,浏览器就会马不停蹄的尝试为你的站点里面的页面安装并激活它,并且在这里可以把静态资源的缓存给办了。

// 监听 service worker 的 install 事件
this.addEventListener('install',function(event){
    // 如果service worker已安装,则会调用waitUntil回调函数
    event.waitUntil(
        // 安装成功后操作缓存
        // caches.open()方法创建一个缓存
        caches.open('test-caches')
        .then(function(cache){
            // 添加需要缓存的资源
            return cache.addAll([
                '/',
                '/index.html',
                '/main.css',
                '/main.js'
            ])
        })
    )
})

自定义请求响应

每次任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件,这些资源包括了指定的 scope 内的 html 文档,和这些 html 文档内引用的其他任何资源(比如 index.html 发起了一个跨域的请求来嵌入一个图片,这个也会通过 Service Worker),这下 Service Worker 代理服务器的形象开始慢慢露出来了,而这个代理服务器的钩子就是凭借 scope 和 fetch 事件两大利器就能把站点的请求管理的井井有条。

话扯这么多,代码怎么实现呢?你可以给 Service Worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 响应,然后你可以用自己的魔法来更新他们。

// 监听fetch方法
this.addEventListener('fetch',function(event){
    // event.respondWith劫持http的响应内容
    event.respondWith(
        caches.match(event.request)
        .then(function(response){
            // 如果Service Worker有自己的返回,就直接返回,减少一次http请求
            if(response){
                return response;
            }
            // 如果service worker没有返回,那就得直接请求真实远程服务
            var request = event.request.clone(); // 拷贝原始请求
            return fetch(request)
                .then(function(httpRes){
                    // 请求失败了,直接返回失败的结果
                    if(!httpRes||httpRes.status!==200){
                        return httpRes;
                    }
                    // 请求成功,则缓存结果
                    var responseClone = httpRes.clone();
                    caches.open('test-caches')
                    .then(function(cache){
                        cache.put(event.request, responseClone);
                    });
                    return httpRes;
                })
        })
    )
})

我们可以在 install 的时候进行静态资源缓存,也可以通过 fetch 事件处理回调来代理页面请求从而实现动态资源缓存。两种方式可以比较一下:

  • on install 的优点是第二次访问即可离线,缺点是需要将需要缓存的 URL 在编译时插入到脚本中,增加代码量和降低可维护性;
  • on fetch 的优点是无需更改编译过程,也不会产生额外的流量,缺点是需要多一次访问才能离线可用。

Service Worker 版本更新

test.js控制着页面资源和请求的缓存,那么如果缓存策略需要更新呢?也就是如果test.js有更新怎么办?test.js自身该如何更新?

如果test.js内容有更新,当访问网站页面时浏览器获取了新的文件,逐字节比对 /sw.js 文件发现不同时它会认为有更新启动 更新算法,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来重新打开的页面里生效。

如果希望在有了新版本时,所有的页面都得到及时自动更新怎么办呢?可以在 install 事件中执行 self.skipWaiting() 方法跳过 waiting 状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行 self.clients.claim() 方法,更新所有客户端上的 Service Worker。

// 安装阶段跳过等待,直接进入 active
this.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([
            // 更新客户端
            self.clients.claim(),
            // 清理旧版本
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (cacheName !== 'test-caches') {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

Service Worker的生命周期

Service Worker的生命周期

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。install 事件回调中有两个方法:
    • event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
    • self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。
  • 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。

  • 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。activate 回调中有两个方法:
    • event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
    • self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
  • 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。

  • 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。

Service Worker支持的事件

  • install:Service Worker 安装成功后被触发的事件,在事件处理函数中可以添加需要缓存的文件

  • activate:当 Service Worker 安装完成后并进入激活状态,会触发 activate 事件。通过监听 activate 事件你可以做一些预处理,如对旧版本的更新、对无用缓存的清理等。

  • message:Service Worker 运行于独立 context 中,无法直接访问当前页面主线程的 DOM 等信息,但是通过 postMessage api,可以实现他们之间的消息传递,这样主线程就可以接受 Service Worker 的指令操作 DOM。

  • fetch:当浏览器在当前指定的 scope 下发起请求时,会触发 fetch 事件,并得到传有 response 参数的回调函数,回调中就可以做各种代理缓存的事情了。

  • push:push 事件是为推送准备的。

  • sync:sync 事件由 background sync (后台同步)发出。background sync 配合 Service Worker 推出的 API,用于为 Service Worker 提供一个可以实现注册和监听同步处理的方法。


其他

报错The path of the provided scope ('/') is not under the max scope allowed ('/assets/')

Scope规定了Service Worker的作用范围。例如,一个注册在https://www.example.com/list路径下的Service Worker,其作用的范围只能是它本身与它的子路径:https://www.sample.com/list、https://www.sample.com/list/test/等,而在https://www.sample.com/是无效的。同时,scope的默认值为./(注意,这里所有的相对路径不是相对于应用,而是相对于sw.js脚本的)。


链接: https://fly63.com/article/detial/9838

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!