PWA(渐进式WEB应用)

什么是渐进式WEB应用

PWA 指的是使用指定技术和标准模式来开发的 Web 应用,这就使得它们同时拥有 Web 应用和原生应用的特性。

一方面,Web应用更加容易使用,相比于安装一个原生应用,访问网站更加容易和迅速。你还可以通过链接来分享Web应用。

另一方面,原生应用与操作系统更加完美的融合,因此可以为用户提供了更无缝的体验。你可以通过安装使应用可以在离线时正常运行;相较于浏览器访问,用户也更喜欢点击图标打开应用。

PWA同时赋予了我们以上两种优势。

渐进式增加和响应式设计已经可以使我们构建效果出色的移动端网站,而在很多年之前的Firefox OS的生态系统中,离线运行和安装的Web应用已经出现。

什么使应用成为PWA

正如前文所说,PWA不只是使用一种技术创建的。它代表了构建Web应用程序的新理念,涉及一些特定的模式,API和功能。从外观,我们无法直接分辨一个应用是否是PWA。只有当应用程序满足某些特定要求,或者实现了一组特定功能,例如离线工作、可安装、易于同步、可以发送推送通知等,我们可以将它视为PWA。

此外,还有一些工具可以按百分比衡量应用的完整性。例如Lighthouse,我们可以用它的分数判断我们程序的完整性。

辨别一个Web应用是否是PWA有一些关键性原则,一个PWA应具有以下特定:

  • 可发现,可以通过搜索引擎发现
  • 可安装,可以出现在设备的主屏幕上
  • 可链接,可以简单的通过URL分享
  • 独立于网络,可以在离线状态或者网速很差的情况下运行
  • 渐进式,在老版本的浏览器依旧可以使用,在新版本的浏览器上可以使用全部功能
  • 可重入,无论何时有新内容,都可以发送通知
  • 响应式,任何具有屏幕和浏览器的设备上都可以正常使用——平板、手机、电视、冰箱、汽车
  • 安全,在用户、浏览器、应用之间的连接是安全的,第三方无法访问敏感数据

意义

只需较小的代价就可以实现PWA的核心特性,而它的优势是巨大的。例如:

  • 减少应用安装后的加载时间,通过Service Workers来进行缓存, 以此来节省带宽和时间。
  • 当应用有可用的更新时,可以只改变更新的部分,而对于原生应用,哪怕一些微小改动也需要用户去下载整个应用
  • 外观和使用感受与原生平台更加融为一体——图标可以在主屏幕打开、全屏运行
  • 凭借系统通知和推送消息与用户保持连接,对用户产生更多的吸引力,并且提高转换效率。

这里是使用PWA的例子,PWA Stats

技术支持

如前文所述,PWA不只依赖单个API,而是使用多种技术来实现提供最佳Web体验的目标。PWA所需关键要素是Service Worker

目前PC和移动设备所有主流浏览器均支持Service Worker。

至于其他功能,像是推送通知通知功能添加至主屏功能也得到了广泛的支持。 目前,Safari对添加到主屏的支持有限,并且不支持 Web 推送通知。 但是,其他主流浏览器都支持这里的所有功能。

其中一些 API 是实验性的,文档仍在完善中,我们的原则是要遵循渐进增强理念:在客户端支持它们的情况下,使用提供此类增强功能的技术,但如果客户端不支持,则仍然提供应用程序的基本功能。 这样,应用对每个人都可用,但使用现代浏览器的人能更多地从 PWA 功能中受益。

应用架构

渲染网站主要有两种方法 - 在服务器上或在客户端上。它们都有其优点和缺点,可以适当地混合使用这两种方法:

  • 服务器端渲染(SSR)的意思是在服务器上渲染网页,因此首次加载会更快,但是在不同页面之间导航都需要下载新的HTML内容。它的跨浏览器兼容性良好,但代价是页间加载时间延长,也就是总体感知上的性能降低:每加载一个页面,都需要一个服务器请求往返的时间。
  • 客户端渲染(CSR)允许在导航到不同页面时几乎立即在浏览器中更新网站,但在开始时需要更多的初始下载和客户端上的额外渲染。 首次访问时网站速度较慢,但后续访问速度要快得多。

PWA的原理就是改变HTTP缓存的机制,优先取本地的资源,在下一次加载才会采用新的内容。

将 SSR 与 CSR 混用可以获得最佳效果:可以在服务器上渲染网站,缓存其内容,然后在客户端需要时更新渲染。因为使用了 SSR,第一页加载很快;因为客户端可以仅使用已更改的部分重新渲染页面,所以页面之间的导航也是平滑的。

目前最流行的是APP shell概念,它按照上述方式混用SSR和CSR;此外还遵循”离线优先”。

APP Shell

App Shell 是为了尽快加载最小用户界面,然后缓存它,以便在后续访问时可以离线使用,然后再加载应用程序的所有内容。这样,下次有人从设备访问应用程序时,UI 立即从缓存加载;如果缓存数据不可用的话,就从服务器请求新内容。

这种结构的页面很快,给用户的感觉也很快:用户会立即看到内容而不是加载动画或空白页。如果网络连接不可用,它还允许离线访问网站。我们可以通过 service worker 控制从服务器请求的内容以及从缓存中检索的内容。

Service Worker

Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。

service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本,它不能访问 DOM 结构。

service worker不需要用户打开 web 页面,也不需要其他交互,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。基于service worker可以实现消息推送,静默更新以及地理围栏等服务。

service worker提供一种渐进增强的特性,使用特性检测来渐渐增强,不会在老旧的不支持 service workers 的浏览器中产生影响。可以通过service workers解决让应用程序能够离线工作,让存储数据在离线时使用的问题。

出于安全考量,Service workers只能由HTTPS承载。在Firefox浏览器的用户隐私模式,Service Worker不可用。

离线优先

“离线优先”或“缓存优先”模式是向用户提供内容的最流行策略,如果资源已缓存且可用,就在从服务器下载资源之前先将其返回;如果资源不存在,就下载并缓存以备使用。

生命周期

service worker拥有一个完全独立于Web页面的生命周期。

img

  1. 注册service worker,在网页上生效

  2. 安装成功,激活 或者 安装失败(下次加载会尝试重新安装)

  3. 激活后,在sw的作用域下作用所有的页面,首次控制sw不会生效,下次加载页面才会生效。

  4. sw作用页面后,处理fetch(网络请求)和message(页面消息)事件 或者 被终止(节省内存)。

我们可以用Service Workers API 添加事件监听器

  • 安装:install 事件:在 install 的监听函数中, 我们可以初始化缓存并添加离线应用时所需的文件。

  • 激活:activate事件:用法和install相同,通常用来删除我们已经不需要的文件或者做一些清理操作。

  • 响应:在每次发起HTTP请求触发,允许我们拦截请求并对请求做出自定义响应。

更新

当我们的应用有了一个新版本,并且它包含了一些可用的新资源时,我们应该如何去更新它的 Service Worker?

1
var cacheName = 'js13kPWA-v1';

当我们把版本号更新到 v2,Service Worker 会将我们所有的文件(包括那些新的文件)添加到一个新的缓存中。

这个时候一个新的 Service Worker 会在后台被安装,而旧的 Service Worker 仍然会正常运行,直到没有任何页面使用到它为止,这时候新的 Service Worker 将会被激活,然后接管所有的页面。

PWA安装

除了实现离线工作外,我们还可以更进一步,让用户如同本地应用一样在支持的移动浏览器上安装 web 应用。这能使应用从设备主屏直接启动,而不需要在浏览器输入url,同时也可以全屏运行,看上去更像原生应用。

要求

可安装网站需要满足以下条件:

  • 网站的协议必须是安全的(即使用 HTTPS 协议)
  • 一份网页清单,填好正确的字段
  • 一个在设备上代表应用的图标
  • 一个注册好的 Service Worker,可以让应用离线工作(这仅对于安卓设备上的 Chrome 浏览器是必需的)

清单文件(Manifest)

离线访问的关键在于一份网页清单,它通过 JSON 形式列举了网站的所有信息。

它通常位于网页应用的根目录,包含一些有用的信息,比如应用的标题、在移动设备操作系统上显示的代表该应用的不同大小的图标(例如主屏图标)的路径,和用于加载页或启动画面的背景颜色。浏览器需要这些信息来安装 web 应用并使其在主屏上显示。

1
<link rel="manifest" href="js13kpwa.webmanifest">
  • name: 网站应用的全名。
  • short_name: 显示在主屏上的短名字。
  • description: 一两句话解释你的应用的用途。
  • icons: 一串图标信息:源 URL,大小和类型。多包含几个图标,这样就能选中一个最适合用户设备的。
  • start_url: 启动应用时打开的主页。
  • display: 应用的显示方式;可以是 fullscreenstandaloneminimal-ui 或者 browser
  • theme_color: UI 主颜色,由操作系统使用。
  • background_color: 背景色,用于安装和显示启动画面时。

添加到主屏

“添加到主屏” (或者英语短语 A2HS (Add to Home Screen)) 是移动浏览器实现的一个特性,它利用网页清单中的信息来在设备主屏上显示应用图标和文字。只有应用满足上述必备条件,这个功能才可以正常运作。

当用户使用支持的移动浏览器访问 PWA 时,浏览器会显示一条横幅信息表示可以安装这个应用。

线上地址

推送&通知

本地缓存实现离线应用是一个强大的特性,允许用户在主屏幕安装应用也是很了不起。但是除了他们之外,我们还可以走的更远,利用推送和通知提高用户的参与度,并随时更新新的内容。

通知API和推送API是两个互相独立的API,他们互相配合,给用户更好的体验,推送API可以用来从服务端推送新的内容而无需客户端介入,它是由应用的Service Work实现的,通知功能则通过Service Work来向用户展示一些信息,做一些提醒。

跟 Service Worker 一样,这些工作是在浏览器外部实现的,所以即使应用被隐藏到后台甚至被关闭了,我们仍然能够推送更新或者通知给用户。

通知

为了能够显示通知,我们需要先请求用户授权。当用户确定接收通知,应用就可以获得推送通知的功能。用户的授权的结果有三种,default(默认)、granted(允许)或者 denied(拒绝),当用户没有做出选择的时候,授权结果会返回 default,另外两种结果分别在用户选择了允许或者拒绝的时候返回。

一旦用户选择授权,这个授权结果对通知 API 和推送 API 两者都有效。

推送

推送比通知要复杂一些,我们需要从服务端订阅一个服务,之后服务端会推送数据到客户端应用。应用的 Service Worker 将会接收到从服务端推送的数据,这些数据可以用来做通知推送,或者实现其他的需求。

渐进式加载

尽快加载在用户浏览体验中是一件很重要的事,等待页面加载的时间越长,用户在页面加载完成之前离开的概率就越大。为了达到这个目的,网页加载完成前,我们应该用占位符在最终资源将会加载的地方展示最起码的视图骨架。

这个功能可以用渐进式加载来实现,它也被称为惰性加载。它的做法是延迟加载尽可能多的资源(HTML、CSS 和 JavaScript),只有在用户第一次使用到它的时候,它才会被立刻加载。

打包还是拆分

大部分用户不会用到一个网站的所有页面,但我们最常见的做法是把所有的功能都打包进一个很大的文件里面。一个 bundle.js 文件的大小可能会有几 M,一个打包后的 style.css 会包含网站的一切样式,从 CSS 结构定义到网站在各个版本的样式:移动端、平板、桌面、打印版等等。

通常来说,只加载一个较大的打包后文件会比加载很多个小文件要快一些,但如果用户并不是一开始就需要所有的资源,我们就可以首先加载那些关键的资源,其他的资源等到需要的时候再去加载它。

阻塞渲染的资源

将所有文件打包在一起并不是最好的做法,浏览器在渲染之前,需要先把HTML,css,js下载下来。在页面加载完成之前,用户会看到一个空白页面,体验很差。

为了解决这个问题,举个例子,我们可以在 script 标签上面加上一个 defer

1
<script src="app.js" defer></script>

如果script标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续DOM的渲染;如果有多个设置了deferscript标签存在,则会按照顺序执行所有的script
defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行,所以不会阻塞 HTML 页面的渲染。

我们还可以拆分 CSS 文件并给它们加上 media 属性:

1
2
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="print.css" media="print">

这种做法告诉浏览器,只有在条件满足的情况下才加载这些资源(例如指定了 print,则在打印环境下才会加载这些资源)。

图片

除了JS和CSS,网站通常还会包含大量图片,当把<img>添加到网站时,对应的所有图片资源都会在页面初始化时被下载下来,在网站就绪之前下载几M的图片资源时很平常的,它会给用户带来不好的体验。

  1. 图片占位符:我们可以通过 JavaScript 有选择地加载图片,而不是把所有的图片路径都直接放进 <img> 标签的 src 属性里面,因为这会使浏览器自动下载所有的图片。在图片最终加载之前,示例页面会将图片的最终路径存放到 data-src 中。在这个阶段,应用会使用图片占位符来代替真正的图片,它更轻量级,体积更小,加载也更快。
  2. 通过JS加载
  3. 用CSS制造模糊效果

按需加载

上面讨论的图片加载机制比原有的机制体验好很多:在 HTML 文档加载完成之后再开始加载图片,在加载过程中还提供了一个很漂亮的过渡效果。问题是,即使用户有可能只看前两张或者三张图片,它仍然会一次性加载所有的图片。

这个问题可以用新的 Intersection Observer API 来解决。通过这个 API,我们可以确保只有当图片出现在可见区域时,它才会被加载。

如果浏览器支持 IntersectionObserver 对象,应用会新建一个它的实例。当监听对象跟 Observer 发生交互时(即图片出现在视口中时),作为参数传递的函数可以用来处理一些回调事务,例如图片加载。我们可以迭代每一个对象,并对它们进行相应的处理:当图片可见时,我们开始加载真正的图片并且停止监听这张图片,因为在图片加载完成之后,我们已经没必要再知道它的状态了。

结论

渐进增强的要点:不管在任何硬件或平台,都能提供一个可用的应用,但在现代浏览器中可以有更好的用户体验。

结束。。。

缓存机制 特点 适用场景
浏览器缓存(详情见上一篇文章) HTTP协议层支持 静态文件的缓存
Application Cache 方便构建离线App 离线App,静态文件的缓存
Local Storage 更大存储量、更安全、更便捷;持久存在,在页面关闭后也可以使用 存储文本类型的信息和数据或用户个性化设置数据
Session Storage 页面关闭后无法使用,更大存储量、更安全、更便捷 存储与页面相关的数据,如恢复在表单中已经填写的数据
Web SQL 存储、管理复杂结构数据 用IndexedDB替代,不推荐使用
IndexedDB 存储任何类型数据,使用简单,支持索引 结构、关系复杂的数据存储
Cache Storage 存储Response 对象,和Service Worker配合使用 离线

缓存数据怎么用

fetch

self是啥:window