前端技术这几年发展迅速,其中就有PWA,全名Progressive Web APP即渐进式Web应用程序,在2016年,Google I/O 大会上提出的一个 Next Web Generation 概念。PWA是专门应对手机web开发提出来的,它可以将 web 和 app 各自优势结合到一起:渐进式、离线加载等实现趋近于App的交互,实时更新、可推送、可安装等,达到一个当我们使用Web应用时体验可以像一款原生App一样。特别说明一下,很多人以为PWA是一种新技术,然而并不是,它是应用多项Web技术的一个集合,其中核心技术即 “Service Worker”,我们把它放在后面说。

目前我们使用Web应用和很多国内SPA一样,通常都是在打开一个页面的时候发起请求来获取数据,在离线的情况一般就不可用了。而PWA是旨在改善Web的体验,将网络的优势与应用的优势融合起来,给用户更优的体验。PWA具有的特点分别是:

  • 可靠 即使在不稳定的网络环境下(包括无网络的情况),也可以立即加载并展现。
  • 响应快速 用户交互响应迅速,有平滑流畅的动画进行响应。
  • 粘性 像设备上原生应用一样,具有逼真的用户体验,同时用户还可以将其添加到桌面。
  • 渐进式 适用于使用任何浏览器的用户,因为它的核心是以渐进式增强。
  • 自适应 任何形式上都可使用:桌面设备、移动设备等。
  • 安全 通过HTTPS,防止窥探以及保障内容不会被篡改。
  • 可发现 因为W3C清单和服务工作注册范围,可以让搜索引擎找到它,将其识别为“应用程序”。
  • 可安装 用户再去app store去下载,可以直接将应用添加到主屏幕从而保留进行使用。
  • 可链接 可以通过url地址分享应用程序,省去了复杂的安装。

我们在其中选择几点着重解释一下:

(1)可安装

它是指在设备的主屏幕上像原生APP一样留有图标。要实现这特点首先需要提供 Web app manifest(web应用程序清单),manifest是一个json文件,它里面描述的是应用程序的图标如何在主屏幕显示以及点击后跳转到的页面是什么,我们可以直接在html中引用它“<link rel=”manifest” href=”/manifest.json”>”,它的格式如下图:

其中几个属性代表的意思是:

• start_url 设置跳转的地址

• icons 让我们设置页面的图标

• background_color 设置背景颜色, 应用启动后会立即使用此颜色,这一颜色将保留在屏幕上,直至网络应用首次呈现

• theme_color 会设置主题颜色

• display 设置是否启动状态

这里的display设置的是网络应用是否隐藏浏览器的UI,当display的值为”standalone”时,网络应用隐藏浏览器的UI;当display的值为”browser”时,则显正常显示。

关于manifest.json里字段更加具体的含义,感兴趣的同学可以去谷歌开发者文档里探索一下啦。

(2)这是一个非常diao的特性:可离线使用即在没有网络环境的情况下也能打开应用程序,实现这一强大功能的幕后大佬是:Service worker(服务人员)。

Service worker其实是一段脚本,我们平时缓存都是session、localStorage、CacheStorage这些,PWA通过Service worker访问CacheStorage实现缓存及离线开发。

这里简单讲一下Service worker的概念,通常浏览器加载页面运行的是主Java线程,而Service worker作为一个独立的线程,可以理解为在浏览器身后默默无闻运行的一个线程。

正因为这个特性,Service worker无论如何都不会阻塞我们的主线程,意味着不会使我们的浏览器页面卡顿等。在使用Service worker时要注意,我们使用的协议必须是https,当然如果想在本地开发弄一个https是很让人头疼的,幸运的是Service worker允许在本地 host 为 localhost 或者 127.0.0.1 也可以跑起来。

我们来看一下,如何注册Service worker?

首先,我们要判断当前使用的浏览器是否支持Service worker,支持我们才能继续进行下去,如果支持,那么在页面加载的时候注册位于/sw.js的Service worker,看下面的代码:

每次页面加载成功后,就会调用 register() 方法,浏览器将会判断 Service Worker 线程是否已注册并做出相应的处理。

register 方法的 scope 参数是可选的,用于指定你想让 Service Worker 控制的内容的子目录。本 demo 中服务工作线程文件位于根网域, 这意味着服务工作线程的作用域将是整个来源。

关于Service Worker更加详细的介绍,感兴趣的同学可以参考MDN文档。

对啦,Service worker也是有生命周期的,它的详细介绍可以参考下图:

说了这么多,那我们开发者如何了解一个网页是否具备了 PWA 的一些特点呢?

我们可以通过谷歌开发工具,在其中找到Audits面板,它可以检测出页面是否具备PWA的特点:

下面我们来看一个简单的demo,看一下PWA在离线时依然能够快速加载应用。

首先我们将加载loading直接显示出来,确保用户在打开网页可以立即看到,让用户知道此时页面在加载中:

此时我们将html页面中引用的js的注释取消,将我们写的虚假数据加载出来:

那么如何实现缓存呢?即在离线的环境下加载出来数据,此时将网络环境调成Offline,页面无法加载:

这里就要应用到上面我们说的Service worker服务工作线程来实现。

先来检查一下浏览器是否支持Service worker:

如果浏览器支持,就会注册服务工作线程,当用户第一次打开页面时就会触发安装事件从而将缓存所需的内容。

下图为核心代码,实现了真正的离线缓存:

首先,我们需要通过 caches.open() 打开缓存并提供一个缓存名称。提供缓存名称可让我们对文件进行版本控制,或将数据与 App Shell 分开,以便我们能轻松地更新某个数据,而不会影响其他数据。

我们在install侦听器下面添加activate事件侦听器,因为activate事件会在Service worker启动时触发,图中activate事件里面的代码可以确保文件更改的时候更新缓存。

最后我们需要从缓存中提取我们需要的内容,就是下面这段代码:

caches.match() 会由内而外对触发抓取事件的网络请求进行评估,并检查以确认它是否位于缓存内。它随即使用已缓存版本作出响应,或者利用 fetch 从网络获取一个副本,response 通过 e.respondWith() 传回至网页。

现在来看一下我们在离线的时候页面是否会加载呢?

如图,在Offline的网络环境下,我们的应用成功加载出来了。

这个小demo就到这里啦!

目前Progressive Web App仍处于初级阶段,国内普及度还不够,但是不可忽视其背后的的技术,以及对前端全新的定位,或许它会像十年前的AJAX那样重新改变前端的生态。