Progressive Web App, 简称 PWA,是提升Web App
的体验的一种新方法,能给用户原生应用的体验,致力于用前沿的技术开发,让网页使用如同原生App般的体验的一系列方案。
用来自Google Developers的解答Progressive Web Apps
:
- 渐进式 - 适用于所有现代浏览器,因为它是以渐进式增强作为宗旨开发的
- 离线使用 - 借助
Service Worker
能够在离线或者网络较差的情况下正常访问 - 可安装 - 用户可以添加到桌面并生成快捷方式,一键访问
- 类似应用 - 由于是在
App Shell
模型基础上开发,因为应具有Native App
的交互和导航,给用户Native App
的体验 - 持续更新 - 始终是最新的,无版本和更新问题
- 安全 - 通过
HTTPS
协议提供服务,防止窥探和确保内容不被篡改 - 可索引 - 应用清单文件和
Service Worker
可以让搜索引擎索引到,从而将其识别为『应用』 - 粘性 - 网页已经关闭的情况下还可以通过推送后台通知等,让用户回流
我们也可以通过一个DEMO看看实际效果=>天气 PWA
而其中,PWA方案的最主要核心功能都是依赖于Service Worker
这个API来实现的.
Service Worker是什么呢?
W3C 组织早在 2014 年 5 月就提出过 Service Worker
这样的一个 HTML5 API ,主要用来做持久的离线缓存。
当然这个 API 不是凭空而来,至于其中的由来我们可以简单的捋一捋:
浏览器中的javaScript
都是运行在一个单一主线程上的,在同一时间内只能做一件事情。随着 Web 业务不断复杂,我们逐渐在 js 中加了很多耗资源、耗时间的复杂运算过程,这些过程导致的性能问题在 WebApp 的复杂化过程中更加凸显出来。
W3C 组织早早的洞察到了这些问题可能会造成的影响,这个时候有个叫Web Worker
的 API 被造出来了,这个 API 的唯一目的就是解放主线程,Web Worker
是脱离在主线程之外的,将一些复杂的耗时的活交给它干,完成后通过 postMessage
方法告诉主线程,而主线程通过 onMessage
方法得到 Web Worker
的结果反馈。
一切问题好像是解决了,但 Web Worker 是临时(即浏览器关闭后就关闭了)的,我们能不能有一个东东是一直持久存在的,并且随时准备接受主线程的命令呢?基于这样的需求推出了最初版本的 Service Worker
,Service Worker
在 Web Worker
的基础上加上了持久离线缓存能力.
Service Worker
有以下功能和特性:
- 一个独立的 worker 线程,独立于当前网页进程。
- 一旦被 install,就永远存在,除非被 uninstall
- 需要的时候可以直接唤醒,不需要的时候自动睡眠
- 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
- 离线内容开发者可控
- 能向客户端推送消息
- 不能直接操作 DOM
- 出于安全的考虑,必须在 HTTPS 环境下才能工作
所以我们基本上知道了 Service Worker
的伟大使命,就是让缓存做到优雅和极致,让 Web App 相对于 Native App 的缺点更加弱化,也为开发者提供了对性能和体验的无限遐想。
Service Worker工作原理
Service Worker
的技术核心是Service Worker
脚本,它 是一种由Javascript
编写的浏览器端代理脚本。
前端页面向内核发起注册时会将脚本地址通知内核,内核会启动独立进/线程加载Service Worker
脚本并执行Service Worker
安装及激活动作。成功激活后便进入空闲等待状态,若当前的Service Worker
进/线程一直没有管辖的页面或者事件消息时会自动终止(具体的终止策略视不同浏览器及版本而定,不会影响前端编写逻辑,但前端勿在Service Worker
脚本中保存需要持久化的信息,可以借助localstorage
),当打开新的可管辖页面或者已管辖页面发起message
等消息时,Service Worker
进/线程会被重新唤起。
每当已安装的Service Worker
有管辖页面被打开时,便会触发Service Worker
脚本更新,当Service Worker
脚本发生了更改,便会忽略本地网络cache
的Service Worker
脚本直接从网络拉取。若网络拉取的与本地有一个字节的差异都会触发Service Worker
脚本的更新,更新流程与安装类似,只是在更新安装成功后不会立即进入active
状态,需要等待旧版本的Service Worker
进/线程终止。
实例代码
// 在html里注册service-worker
if (navigator.serviceWorker != null) {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Registered events at scope: ', registration.scope);
});
}
复制代码
// 首先定义需要缓存的路径, 以及需要缓存的静态文件的列表。
var cacheStorageKey = 'minimal-pwa-8'
var cacheList = [
'/',
"index.html",
"main.css",
"e.png",
"*.png"
]
// 借助 Service Worker, 可以在注册完成安装 Service Worker 时, 抓取资源写入缓存:
window.addEventListener('install', function(e) {
console.log('Cache event!')
e.waitUntil(
caches.open(cacheStorageKey).then(function(cache) {
console.log('Adding to Cache:', cacheList)
return cache.addAll(cacheList)
}).then(function() {
console.log('Skip waiting!')
return self.skipWaiting()
})
)
})
// 网页抓取资源的过程中, 在 Service Worker 可以捕获到 fetch 事件, 可以编写代码决定如何响应资源的请求:
window.addEventListener('fetch', function(e) {
// console.log('Fetch event:', e.request.url)
e.respondWith(
caches.match(e.request).then(function(response) {
if (response != null) {
console.log('Using cache for:', e.request.url)
return response
}
console.log('Fallback to fetch:', e.request.url)
return fetch(e.request.url)
})
)
})
复制代码
关于事件
install 事件:当前Service Worker
脚本被安装时,会触发 install 事件。
push事件: push 事件是为推送通知而准备的。不过首先你需要了解一下 Notification API
和 PUSH API
。
通过 PUSH API
,当订阅了推送服务后,可以使用推送方式唤醒 Service Worker
以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。
online/offline事件: 当网络状态发生变化时,会触发 online
或 offline
事件。结合这两个事件,可以与 Service Worker
结合实现更好的离线使用体验,例如当网络发生改变时,替换/隐藏需要在线状态才能使用的链接导航等。
fetch 事件: 当我们安装完Service Worker
成功并进入激活状态后即运行于浏览器后台,我们的这个线程就会一直监控我们的页面应用,如果出现HTTP
请求,那么就会触发fetch
事件,并且给出自己的响应。 这个功能是十分强大的,借助 Fetch API
和 Cache API
可以编写出复杂的策略用来区分不同类型或者页面的资源的处理方式。它能够提供更加好的用户体验: 例如可以实现缓存优先、降级处理的策略逻辑:监控所有 http 请求,当请求资源已经在缓存里了,直接返回缓存里的内容;否则使用 fetch API 继续请求,如果是 图片或 css、js 资源,请求成功后将他们加入缓存中;如果是离线状态或请求出错,则降级返回预缓存的离线内容。
使用webpack插件
看到这里很多人会有疑问了,既然可以通过service-worker
缓存资源,那如果一个正式项目,在项目迭代后,并将代码推送到正式环境后,前端怎么实时知道并重新缓存新的资源呢?
第一种方式,就是每次修改都手动去更改sw文件的版本号,触发更新。
第二种就是使用webpack
插件自动化处理
事实上,在我们真实的用webpack
生成的项目中,如果按照第一种方式手动去写Service-worker.js
文件的话,会遇到两个问题:
webpack
生成的资源多会生成一串hash,Service-worker.js
的资源列表里面需要同步更新这些带hash的资源;- 每次更新代码,都需要通过更新
service-worker
文件版本号来通知客户端对所缓存的资源进行更新.
看到这里就该让用webpack
插件:offline-plugin 登场了,官方同时也推荐sw-precache-webpack-plugin ,offline-plugin不仅能够解决刚刚那个提到的缓存更新的问题,同时还具备以下的优点:
- 1、自动生成和更新Service-worker.js文件和自动为SW添加缓存资源列表
- 2、更为详细的文档和例子;
- 3、迭代频率相对更高,star数更多;
- 4、自动处理生命周期,用户无需纠结生命周期的坑;
- 5、支持自动生成
manifest
文件。
部署到项目中也十分的简单
1.安装
npm install offline-plugin [--save-dev]
复制代码
2.初始化
第一步,进入webpack.config.js
:
// webpack.config.js example
var OfflinePlugin = require('offline-plugin');
module.exports = {
// ...
plugins: [
// ... other plugins
// it's always better if OfflinePlugin is the last plugin added
new OfflinePlugin()
]
// ...
}
复制代码
3.入口文件导入
import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install();
复制代码
经过上面的步骤,offline-plugin
已经集成到项目之中,通过webpack
构建即可。
具体代码也可查看 demo
PWA的浏览器支持情况
兼容性查看
毕竟是自家产品,chrome浏览器肯定是支持度最高的浏览器,chrome64版本是基本支持所有PWA功能API
的。
但国内的浏览器·支持情况相对差一些,而且chrome移动版的使用人群还是偏少的,不过在UC的支持程度也不低