简介PWA与Service Worker

1.关于PWA

PWA,全称Progressive web apps,即渐进式Web应用。PWA技术主要作用为构建跨平台的Web应用程序,并使其具有与原生应用程序相同的用户体验。
早在2014年,W3C就已经公布了PWA的相关技术草案,Chrome于2015年在生产环境下落地支持,但是由于兼容性的问题,PWA技术并没有引起广泛的关注。一直到2018年,苹果在IOS 11.3中正式支持PWA,PWA才又重新回到了广大技术开发人员的视野中。

2.为什么使用PWA

传统Web存在的问题:

1.缺乏直接性的入口。打开网站需要记住它的域名,即使将它保存在收藏夹,寻找起来也不够直接,不够简便。
2.依赖于网络。只要客户机处于断网状态,整个Web系统就处于瘫痪状态,在客户机上无法使用。
3.无法向Native APP一样推送消息。

传统Native APP存在的问题:

1.需要下载与安装。哪怕我只是使用这款APP的一个功能,或者临时使用一下这个APP,我也需要全盘下载并安装APP。
2.开发成本高。你需要准备安卓和IOS两个版本。
3.发布需要审核。在APP Store或者安卓应用市场进行发布,都需要提交审核。
4.更新成本高。即使很小的程序更新,也会需要重新发版,重新提交应用商店审核。
5.内容无法被索引。由于其封闭性,Native APP的内容无法被预先索引,无法进行基于内容的APP检索,只能根据介绍去推断APP的内容。

PWA的存在,就是为了解决以上问题给用户带来的麻烦。
它的优势包括:

1.桌面入口,打开便捷。
2.离线可用,不过度依赖网络。
3.安装简便。
4.一次开发,无需审核,所有平台可用。
5.能够进行消息的推送。

3.PWA技术包括了什么

PWA并不是指某一个具体的技术,要实现PWA应用,需要多种技术的支持。
其中的核心技术包括了:Web App Manifest、Service worker、Cache API、Indexed DB、Push API、Notification API等等。

4.Service Worker与Cache API

读完了上边的内容,相信很多人还是云里雾里的,只是知道了个关于PWA的大概,但是对于如何构建一个PWA应用,还是一无所知。我就是这样的,当初为了学习PWA,参阅了很多资料,很多都是在讲类似于上边的内容,具体的内容却没有说的很明白。这里,我们就如何构建一个PWA应用,进行一下说明。
本质上,PWA就是一个网站,其根本的内容,还是通过HTML + CSS + Javascript去构建的Web页面。为了让它实现添加桌面入口、离线使用、消息推送等PWA特有的功能,就需使用一些Web API为我们的网站添加一些额外的代码。而其中最根本、最基本的,就是Service Worker以及在其内部使用的Cache API。只要通过Service Worker与Cache API,实现了对网站页面的缓存、对页面请求的拦截、对页面缓存的操纵,我认为一个简易的PWA应用就算搭建完成了。

1.什么是Service Worker

Service Worker是一个注册在指定源和路径下的事件驱动型Web Worker。它充当了Web应用程序与浏览器之间的代理服务器,进行资源在文件级别下的缓存与操控,拦截页面请求,实现在不同的情况下对不同请求的响应策略。

2.Service Worker的特点

首先,Service Worker本质上就是一个Web Worker,因此它具有Web Worker的特点:

1.无法操作DOM;
2.脱离主线程;
3.独立上下文;

除此之外,Service Worker还具有以下特点:

4.只能在Https下使用;
5.运行在浏览器后台,不受页面刷新影响;
6.更强大的离线缓存能力(使用Cache API);
7.请求拦截能力;
8.完全异步,不能使用同步API;
9.持续运行,第一次访问页面后,Service Worker就会安装激活并持续运行,直到手动销毁;
3.在页面中注册一个Service Worker
    if('serviceWorker' in navigator){
        window.addEventListener('load', function(){
            navigator.serviceWorker.register('/sw.js', {scope: '/' }).then(function(registration){
                console.log('ServiceWorker registration successful with scope: ' + registration.scope);
            }, function(err){
                console.log('ServiceWorker registration failed: ' + err);
            });
        });
    }

我们使用navigator.serviceWorker.register函数来注册Service Worker。其中,第一个参数为要执行的worker逻辑文件路径,注意这个路径是基于origin的,而非当前文件。第二个参数为Service Worker所能控制的文件范围,如果不填,则默认控制与sw.js文件同级范围下的所有文件。

4.Service Worker的生命周期

WechatIMG15.png
完成注册后,Service Worker开始进行安装,安装成功之后,会在worker中触发install事件;如果安装失败,则进入废弃状态。
安装成功之后,Service Worker开始进行激活。激活成功之后,会在worker中触发activate事件,这个时候,Service Worker就能够控制页面了,我们可以通过Service Worker去进行文件的缓存、请求的监听等操作。如果Service Worker激活失败,则会进入废弃状态。
如果Service Worker逻辑文件更新(相关资源文件变动或者内部逻辑更新等),Service Worker会重新安装,如果这个时候,页面依然存在激活状态下的worker(旧的Service Worker),那么新的worker会进入waiting状态进行等待,直到我们主动去操作worker强制其更新,或者等待用户关闭所有页面,这个时候新的worker才会进入到激活状态。

5.install事件
    //sw.js
    const CACHE_NAME = 'myCache';
    let urlsToCache = [
        '/html/index.html',
        'css/index.css'
    ];
    self.addEventListener('install', function(event){
        event.waitUntil(
            caches.open(CACHE_NAME).then(function(cache){
                console.log('Open cache');
                return cache.addAll(urlsToCache);
            }).then(function(){
                self.skipWaiting();
            })
        );
    });

在install事件中,我们使用caches.open方法打开cache对象,并通过cache.addAll缓存所有我们列出的文件。
如果Service Worker存在更新,我们使用skipWaiting跳过等待,直接强制新的worker进入激活状态。

6.fetch事件
    //sw.js
    self.addEventListener('fetch', function(event){
        if(event.request.method !== 'GET') return;
        event.respondWith(
            caches.match(event.request).then(function(response){
                if(response){
                    console.log('return caches');
                    return response;
                }else{
                    return fetch(event.request).catch(function(){
                        if(/\.html$/.test(event.request.url))
                            return caches.match('/html/neterror.html');
                    });
                }
            })
        )
    });

这里我们只监听了get请求,即我们只希望控制资源请求。通过caches.match检查请求是否命中了缓存,如果命中,则直接返回缓存给用户,防止重复请求,节约资源。如果没有命中,则将使用fetch方法请求网络资源并返回给用户。当网络状态异常时(fetch().catch()),返回404页面的缓存给用户,告知用户当前处于无网络状态,不能访问相关页面。
这种写法适用的情况为:我们指定了一些页面进行缓存,我们希望用户在无网络的情况下只能访问到我们指定缓存的页面。
当然,还有一种情况为,我们指定了一些页面进行缓存(常用页面),当用户访问到一些不常用页面时,再对其进行缓存。这样,我们对资源配置进行了优化,不过多的占用用户本地资源去缓存所有页面,对于非所有用户的常用页面,按需缓存。

    self.addEventListener('fetch', function(event){
        if(event.request.method !== 'GET') return;
        event.respondWith(
            caches.match(event.request).then(function(response){
                if(response){
                    console.log('return caches');
                    return response;
                }else{
                    return fetch(event.request).then(function(res){
                        var responseToCache = res.clone();
                        caches.open(CACHE_NAME).then(function(cache){
                            catch.put(event.request, responseToCache);
                        })
                        return res;
                    });

                }
            })
        )
    });
7.至此,一个简易的PWA应用,就算是完成了。

5.结束语

作为一个PWA的初学者,我也仅仅是把现阶段学到的部分总结在这里,还有很多的问题和情况都没有想到、遇到。这些等以后再慢慢的补充。文章或者代码中肯定也有许多讲的不合适、不规范的地方,如果有大佬看到了,还望提点。

阅读 378

推荐阅读