写点什么

天人合一物我相融,站点升级渐进式 Web 应用 PWA(Progressive Web Apps) 实践

  • 2022 年 6 月 14 日
  • 本文字数:4153 字

    阅读完需:约 14 分钟

天人合一物我相融,站点升级渐进式Web应用PWA(Progressive Web Apps)实践

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_216


PWA(Progressive web apps,渐进式 Web 应用)使用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序,说白了,PWA 可以让我们的站点以原生 APP 的形式运行,但相比于安装原生 APP 应用,访问 PWA 显然更加容易和迅速,还可以通过链接来分享 PWA 应用。


有许多知名的网络平台已经将 PWA 方案落地,比如 Twitter。选择增强的网站体验而不是原生应用。事实上使用 PWA 也确实从中获得了显而易见的益处。https://www.pwastats.com 这个网站上分享了许多案例研究,PWA 相比于传统应用有以下好处:


1、减少应用安装后的加载时间,通过 Service Workers 来进行缓存,以此来节省带宽和时间。


2、当应用有可用的更新时,可以只更新发生改变的那部分内容。相比之下,对于一个原生应用而言,即便是最微小的改动也需要强制用户去进行热更新或者再次下载整个应用。


3、外观和使用感受与原生平台更加融为一体——应用图标被放置在主屏幕上,应用可以全屏运行等。


凭借系统通知和推送消息与用户保持连接,对用户产生更多的吸引力,并且提高转换效率。


诚然,从零开始研发 PWA 应用会有一定的成本,但如果我们本身就拥有基于 Web 的站点,那么就可以通过增加对应的配置文件和服务进行升级操作,直接拥有 PWA 应用。

HTTPS 服务

首先 PWA 要求站点的请求方式为 HTTPS,如果是生产环境,可以通过为 Nginx 服务器配置 SSL 的方式进行适配,但是线下环境测试 PWA 时就有点费劲了,所以通过 openssl 工具为本地域名 localhost 做自签证书:


openssl req -x509 -out localhost.crt -keyout localhost.key \    -newkey rsa:2048 -nodes -sha256 \    -days 3650 \    -subj '/CN=localhost' -extensions EXT -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",      })    # 指定端口  server.listen(443)
复制代码


这里通过设置 ssl_options 参数来导入私钥和证书,同时将端口改为 HTTPS 默认端口号 443。如此,在本地也可以对 PWA 进行测试了,当然了,如果不需要本地操作,也可以跳过这步。

manifest.json 配置文件

为了实现 PWA 应用添加至桌面的功能,除了要求站点支持 HTTPS 之外,还需要准备 manifest.json 文件去配置应用的图标、名称等信息。


以本站为例,在站点根目录创建 manifest.json 文件:


{      "name": "刘悦的技术博客",      "short_name": "刘悦的技术博客",      "description": "刘悦的技术博客",      "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 的作用域。为此我们需要提供两张不同分辨率的站点图标文件:


ServiceWorker 服务

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


Service Worker 本质上就是一个 Web Worker,因此它具有 Web Worker 的特点:无法操作 DOM、脱离主线程、独立上下文。


Service Worker 还具有这些特点:只能在 Https 下使用、运行在浏览器后台,不受页面刷新影响、更强大的离线缓存能力(使用 Cache API)、请求拦截能力、完全异步,不能使用同步 API、持续运行,第一次访问页面后,Service Worker 就会安装激活并持续运行,直到手动销毁。


以本站为例,在站点根目录创建 sw.js 文件,注意 Service Worker 文件位置一定得在根目录,如果不在根目录也要通过重写或者 url 映射让其可以通过根目录路径进行访问,如:https://v3u.cn/sw.js,否则浏览器会检测不到 Service Worker 服务:


var CACHE_NAME = 'v3u-cache-v1';  var urlsToCache = [      '/',      '/v3u/Public/css/tidy_min.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();          })      );  }); 
复制代码


当我们为页面注册 Service Worker 后,Service Worker 开始进行安装,安装成功之后,会在 worker 中触发 install 事件;如果安装失败,则进入废弃状态。


如果 Service Worker 逻辑文件更新(相关资源文件变动或者内部逻辑更新等),Service Worker 会重新安装,如果这个时候,页面依然存在激活状态下的 worker(旧的 Service Worker),那么新的 worker 会进入 waiting 状态进行等待,直到我们主动去操作 worker 强制其更新,或者等待用户关闭所有页面,这个时候新的 worker 才会进入到激活状态。


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


随后,添加 fetch 事件:


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 页面的缓存给用户,告知用户当前处于无网络状态,不能访问相关页面。指定了一些页面和文件进行缓存,我们希望用户在无网络的情况下只能访问到我们指定缓存的页面。


当然,还有另外一种情况,我们指定了一些页面进行缓存(常用页面),当用户访问到一些不常用页面时,再对其进行缓存。这样,我们可以对资源配置进行优化,不过多的占用用户本地资源去缓存所有页面,因为 PWA 的缓冲本身是存储到客户端的,对于非所有用户的常用页面,按需缓存:


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;                      });                    }              })          )      });
复制代码


至此,ServiceWorker 服务文件就撰写完成了。

生产环境上线配置:

分别将 manifest.json 和 sw.js 文件分别上传到生产环境之后,在页面的 head 标签中进行声明:


<link rel="manifest" href="manifest.json">
复制代码


声明后,注意访问一下是否正确返回:https://v3u.cn/manifest.json


随后在页面中注册 Service Worker 服务:


<script>    if ('serviceWorker' in navigator) {      window.addEventListener('load', () =>          navigator.serviceWorker.register("/sw.js?v0")              .catch(() => {}) // ignore      );  }  </script>
复制代码


这里首先判断当前浏览器的 navigator 是否支持 serviceWorker,随后使用 navigator.serviceWorker.register 函数来注册 Service Worker。其中,参数为要执行的 worker 逻辑文件路径,注意这个路径是基于 origin 的,而非当前文件。


接着键入组合键,打开 chrome 浏览器的开发者工具:


Mac 系统上的“⌥+⌘+I”


Win 系统上的“F12+Ctrl+Shift+I”


在 Chrome 的应用标签下进行检查,看应用清单有没有读出你的 PWA 应用信息配置文件:



随后在 serviceWorker 标签下检查 serviceWorker 是否正确运行:



接着访问站点,在地址栏即可添加 PWA 应用:



访问效果:


结语

渐进式增强和响应式设计已经可以让我们构建对移动端非常友好的站点,而 PWA 则又在我们的身后轻轻地推了一把,黄河之水源可滥觞,星星之火正在燎原,一年以内,我们都将感到 PWA 的灼人温度。


原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_216

发布于: 刚刚阅读数: 6
用户头像

专注技术,凝聚意志,解决问题 v3u.cn 2020.12.21 加入

还未添加个人简介

评论

发布
暂无评论
天人合一物我相融,站点升级渐进式Web应用PWA(Progressive Web Apps)实践_前端_刘悦的技术博客_InfoQ写作社区