作者:销魂苗苗若郗 | 来源:互联网 | 2023-09-01 19:17
PWA 全称为 Progressive Web App,中文译为渐进式 Web APP。PWA本质上是 Web 应用,使用现代 API 构建和增强,以提供增强的功能、可靠性和可安装性,同时只需一个代码库就可以借助任何设备触及任何用户、任何地方,实现与原生 App 相近的用户体验。
一个 PWA 首先是一个网页, 可以通过 Web 技术编写出一个网页应用,随后添加上 App Manifest 实现添加至设备主屏幕, 通过 Service Worker 来实现离线缓存和消息推送等功能。
Web Application Manifest
Web Application Manifest,即通过一个清单文件向浏览器暴露 web 应用的元数据,包括名称、icon 的 URL 等,以备浏览器使用,比如在添加至主屏或推送通知时暴露给操作系统,从而增强 web 应用与操作系统的集成能力。
一个典型的 manifest.json:
/** 属性含义详解: https://developer.mozilla.org/zh-CN/docs/Web/Manifest */
{"lang": "en","dir": "ltr","name": "Super Racer 3000","short_name": "Racer3K","icons": [{"src": "icon/lowres.webp","sizes": "64x64","type": "image/webp"}, {"src": "icon/lowres.png","sizes": "64x64"}, {"src": "icon/hd_hi","sizes": "128x128"}],"scope": "/","id": "superracer","start_url": "/start.html","display": "fullscreen","orientation": "landscape","theme_color": "aliceblue","background_color": "red"
}
通过对 manifest.json 进行相应配置,可以实现以下功能:
Service Worker
Service Worker 是一个可编程的 Web Worker,它就像一个位于浏览器与网络之间的客户端代理,可以拦截、处理、响应流经的 HTTP 请求;配合随之引入 Cache Storage API,可以自由管理 HTTP 请求文件粒度的缓存,这使得 Service Worker 可以从缓存中向 web 应用提供资源,即使是在离线的环境下。
Service workers 主要是提供详细的浏览器和网络/缓存间的代理服务,如下图所以:
Service workers 的生命周期:
HTTP 缓存与 Service Worker 缓存 的区别:
-
HTTP 缓存中,Web 服务器可以使用 Expires 首部来通知 Web 客户端,它可以使用资源的当前副本,直到指定的“过期时间”。反过来,浏览器可以缓存此资源,并且只有在有效期满后才会再次检查新版本。 使用 HTTP 缓存意味着你要依赖服务器来告诉你何时缓存资源和何时过期(当然,HTTP 缓存控制还包括 cache-control,last-modified,etag 等字段)。
-
Service Workers 的强大在于它们拦截 HTTP 请求的能力,接受任何传入的 HTTP 请求,并决定想要如何响应。在你的 Service Worker 中,可以编写逻辑来决定想要缓存的资源,以及需要满足什么条件和资源需要缓存多久。一切尽归你掌控!(所以,出于安全考虑,Service Workers 要求只能由 Https 承载)
Service Worker 的注意事项:
- Service worker 运行在 worker 上下文(self) –> 不能访问 DOM(这里其实和 Web Worker 是一样的);
- 它设计为完全异步,同步 API(如 XHR 和 localStorage)不能在 service worker 中使用;
- 出于安全考量,Service workers 只能由 HTTPS 承载;
- 某些浏览器的用户隐私模式,Service Worker 不可用;
- 其生命周期与页面无关(关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动)。
Service Worker 缓存优先的示意图:
Service workers 所支持的事件:
通常遵循以下基本步骤来使用 service workers:
- service worker URL 通过 serviceWorkerContainer.register() 来获取和注册。
- 如果注册成功,service worker 就在 ServiceWorkerGlobalScope 环境中运行; 这是一个特殊类型的 worker 上下文运行环境,与主运行线程(执行脚本)相独立,同时也没有访问 DOM 的能力。
- service worker 现在可以处理事件了。
- 受 service worker 控制的页面打开后会尝试去安装 service worker。最先发送给 service worker 的事件是安装事件(在这个事件里可以开始进行填充 IndexDB和缓存站点资源)。这个流程同原生 APP 或者 Firefox OS APP 是一样的 — 让所有资源可离线访问。
- 当
oninstall
事件的处理程序执行完毕后,可以认为 service worker 安装完成了。 - 下一步是激活。当 service worker 安装完成后,会接收到一个激活事件(activate event)。
onactivate
主要用途是清理先前版本的 service worker 脚本中使用的资源。 - Service Worker 现在可以控制页面了,但仅是在
register()
成功后的打开的页面。也就是说,页面起始于有没有 service worker ,且在页面的接下来生命周期内维持这个状态。所以,页面不得不重新加载以让 service worker 获得完全的控制。
PWA 实现
一个简单的 PWA demo 很简单,新建项目目录,然后:
touch index.html
touch sw.js
npm install serve -g
之后进行简单的 html 和 sw.js 文件的编写:
/** login.html */
/** sw.js */
const CACHE_NAME = 'cache-v1';self.addEventListener('install', (event => {console.log('---------install-----------', event);// event.waitUntil(self.skipWaiting());event.waitUntil(caches.open(CACHE_NAME).then(cache => {// 资源列表,不要人工获取cache.addAll(['/','./index.css'])}));
}));self.addEventListener('activate', (event => {fetch('./userInfo.json', { method: 'post', body: { a: 1, b: 2 } })console.log('--------activate----------', event);// event.waitUntil(self.clients.claim());event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheName !== CACHE_NAME) {return cacheNames.delete(cacheName);}}))}))
}));self.addEventListener('fetch', (event => {console.log('--------fetch---------', event);event.respondWith(caches.open(CACHE_NAME).then(cache => {return cache.match(event.request).then(respOnse=> {if (response) {return response;}// https://stackoverflow.com/questions/68522967/failed-to-execute-put-on-cache-request-method-post-is-unsupported-pwa-sif ((event.request.url.indexOf('http') === 0)) {return fetch(event.request).then(respOnse=> {// Check if we received a valid responseif (!response || response.status !== 200 || response.type !== 'basic') {return response;}if (!event.request.url.endsWith('styles.css')) {// IMPORTANT: Clone the response. A response is a stream// and because we want the browser to consume the response// as well as the cache consuming the response, we need// to clone it so we have two streams.console.log('------event.request------', event.request)const respOnseToCache= response.clone();cache.put(event.request, responseToCache);}return response;})}})}))
}))self.addEventListener('push', event => {event.waitUntil(// Process the event and display a notification.self.registration.showNotification("Hey!"));
});self.addEventListener('notificationclick', event => {// Do something with the event event.notification.close();
});self.addEventListener('notificationclose', event => {// Do something with the event
});/** manifest.json */
{"name": "Progressive Web App","short_name": "PWA","description": "Progressive Web App.","icons": [{"src": "/icon.png","sizes": "288x288","type": "image/png"}],"start_url": "/","display": "standalone","theme_color": "#B12A34","background_color": "#B12A34"
}
App Shell 模型 是构建 Progressive Web App 的一种方式,这种应用能可靠且即时地加载到用户屏幕上,与本机应用相似。App“shell”是支持用户界面所需的最小的 HTML、CSS 和 Javascript,如果离线缓存,可确保在用户重复访问时提供即时、可靠的良好性能。这意味着并不是每次用户访问时都要从网络加载 App Shell,只需要从网络中加载必要的内容。对于使用包含大量 Javascript 的架构的单页应用来说,App Shell 是一种常用方法。这种方法依赖渐进式缓存 Shell(使用 Service Worker 线程)让应用运行,接下来,为使用 Javascript 的每个页面加载动态内容。App Shell 非常适合用于在没有网络的情况下将一些初始 HTML 快速加载到屏幕上。
PWA 安装后,就会出现在桌面/Chrome 应用里面:
支持卸载:
PWA 的优劣势:
尽管有上述的一些缺点,PWA 技术仍然有很多可以借鉴和使用的点:
- Service Worker 技术实现离线缓存,可以将一些不经常更改的静态文件放到缓存中,提升用户体验。
- Service Worker 实现消息推送,使用浏览器推送功能,吸引用户
- 渐进式开发,尽管一些浏览器暂时不支持,可以利用上述技术给使用支持浏览器的用户带来更好的体验。
总结