原文转载自《刘越的科技博客》https://v3u.cn/a_id_216PWA(渐进式网络)apps,ProgressiveWebApps)Webapplication)使用现代WebAPI和传统的渐进式增强策略来创建跨平台的Web应用程序。说白了,PWA就是让我们的站点作为一个原生APP来运行,但是比起安装原生APP应用,访问PWA显然更加简单快捷,而且还可以通过链接分享PWA应用。有许多知名的网络平台已经实现了PWA解决方案,例如Twitter。选择比本机应用程序更好的网站体验。事实上,使用PWA确实从中获得了明显的好处。https://www.pwastats.com许多案例研究都在这个网站上共享。与传统应用相比,PWA具有以下优势:1.减少应用安装后的加载时间,使用ServiceWorkers进行缓存,节省带宽和时间。2.当应用程序有更新可用时,只能更新更改的部分内容。相比之下,对于原生应用程序,即使是最微小的更改也会迫使用户执行热更新或重新下载整个应用程序。3、外观和使用体验更贴近原生平台——应用图标置于主屏、应用可全屏运行等。通过系统通知和推送消息与用户保持联系,产生更多吸引力用户,提高转化效率。诚然,从头开始开发PWA应用会有一定的成本,但是如果我们有一个基于Web的站点,我们可以通过添加相应的配置文件和服务进行升级操作,直接拥有PWA应用。HTTPS服务首先,PWA要求站点的请求方式为HTTPS。如果是生产环境,可以通过为Nginx服务器配置SSL来适配。但是在离线环境下测试PWA有点费力,所以使用openssl工具对本地域名localhost做自签名证书:opensslreq-x509-outlocalhost.crt-keyoutlocalhost.key\-newkeyrsa:2048-nodes-sha256\-days3650\-subj'/CN=localhost'-extensionsEXT-config<(\printf"[dn]\nCN=localhost\n[req]\ndistinguished_name=dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")输出:localhost.crt和localhost.key文件,key为openssl格式的私钥,一般为rsa算法。csr是证书请求文件,用于申请证书。制作csr文件时,必须使用自己的私钥对应用进行签名,也可以设置一个密钥。将该文件放在项目的根目录下,然后在构建项目服务的时候进行配置。以Tornado为例:server=httpserver.HTTPServer(app,xheaders=True,ssl_options={"certfile":"./localhost.crt","keyfile":"./localhost.key",})#指定portserver.listen(443)通过设置ssl\_options参数导入私钥和证书,修改端口为HTTPS默认端口号443。这样也可以在本地测试PWA。当然,如果不需要本地操作,可以跳过这一步。manifest.json配置文件要实现将PWA应用添加到桌面的功能,除了需要站点支持HTTPS外,还需要准备一个manifest.json文件,用于配置图标、名称等信息的应用程序。以本站为例,在本站根目录下创建manifest.json文件:{"name":"刘越技术博客","short_name":"刘越技术博客","description":"刘越悦的技术博客Blog","icons":[{"src":"https://v3u.cn/v3u/Public/images/pwa192.png","sizes":"192x192","type":"image/png"},{"src":"https://v3u.cn/v3u/Public/images/pwa512.png","sizes":"512x512","type":"image/png"}],“background_color”:“#FFF”,“theme_color”:“#FFF”,“display”:“standalone”,“orientation”:“portrait”,“start_url”:“/”,“scope”:“/”}从上到下依次是PWA应用的名称、描述、图标文件,Banner颜色、显示方式、起始页链接、PWA范围。为此,我们需要提供两个不同分辨率的站点图标文件:ServiceWorkerServiceServiceWorker是在指定源和路径下注册的事件驱动的WebWorker。它充当Web应用程序和浏览器之间的代理服务器,在文件级别缓存和操作资源,拦截页面请求,实现不同情况下对不同请求的响应策略。ServiceWorker本质上是一个WebWorker,所以它具有WebWorker的特点:不能操作DOM,脱离主线程,有独立的上下文。ServiceWorker还有这些特点:只能在HTTPS下使用,运行在浏览器后台,不受页面刷新影响,具有更强的离线缓存能力(使用CacheAPI),请求拦截能力,完全异步,以及不能使用同步API。持续运行,在第一次访问页面后,ServiceWorker会被安装激活并持续运行,直到手动销毁。以本站为例,在本站根目录下创建一个sw.js文件。请注意,ServiceWorker文件的位置必须在根目录中。如果不在根目录下,则必须通过重写或url映射的方式从根目录路径访问,如:https://v3u.cn/sw.js,否则浏览器检测不到ServiceWorker服务:varCACHE_NAME='v3u-cache-v1';varurlsToCache=['/','/v3u/Public/css/tidy_min.css'];self.addEventListener('install',function(event){event.waitUntil(caches.open(CACHE_NAME).then(function(cache){console.log('Opencache');returncache.addAll(urlsToCache);}).then(function(){self.skipWaiting();}));});在我们为页面注册ServiceWorker后,ServiceWorker开始安装。安装成功后,worker中的install事件被触发;如果安装失败,则进入废弃状态。如果ServiceWorker逻辑文件有更新(相关资源文件改变或内部逻辑更新等),ServiceWorker会被重新安装。如果此时页面上还有活跃的worker(老的ServiceWorker),新的worker会进入waiting状态,一直等到我们主动操作worker强制更新,或者等待用户关闭所有页面,在这个时候新的worker就会进入active状态。在安装事件中,我们使用caches.open方法打开缓存对象,并缓存我们使用cache.addAll列出的所有文件。如果ServiceWorker有更新,我们使用skipWaiting跳过等待,直接强制新的worker进入活动状态。然后,添加获取事件:self.addEventListener('fetch',function(event){if(event.request.method!=='GET')return;event.respondWith(caches.match(event.request).then(function(response){if(response){console.log('returncaches');returnresponse;}else{returnfetch(event.request).catch(function(){if(/\.html$/.测试(event.request.url))returncaches.match('/html/neterror.html');});}}))});这里我们只监听整个站点的GET请求方法,也就是我们只想控制资源ask。使用caches.match检查请求是否命中缓存。如果命中,直接将缓存返回给用户,防止重复请求,节省资源。如果没有命中,则使用fetch方法请求网络资源返回给用户。当网络状态异常时(fetch().catch()),将404页面的缓存返回给用户,告知用户当前处于无网络状态,无法访问相关页面。有些页面和文件是指定缓存的,我们希望用户在没有网络的情况下只能访问我们指定缓存的页面。当然,还有一种情况,我们指定一些页面(常用页面)进行缓存,当用户访问一些不常用的页面时,再进行缓存。这样,我们可以优化资源配置,但是不会占用用户过多的本地资源去缓存所有的页面,因为PWAbuffer本身是存放在客户端的,并不是所有用户的常用页面,都缓存在需求:self.addEventListener('fetch',function(event){if(event.request.method!=='GET')return;event.respondWith(caches.match(event.request).then(function(response){if(response){console.log('returncaches');returnresponse;}else{returnfetch(event.request).then(function(res){varresponseToCache=res.clone();caches.open(CACHE_NAME).then(函数(缓存){catch.put(event.request,responseToCache);})returnres;});}}))});至此,ServiceWorker服务文件就写好了。生产环境在线配置:将manifest.json和sw.js文件分别上传到生产环境后,在页面的head标签中声明:
