PWA 全称为 Progressive Web App,中文译为渐进式 Web APP。PWA本质上是 Web 应用,使用现代 API 构建和增强,以提供增强的功能、可靠性和可安装性,同时只需一个代码库就可以借助任何设备触及任何用户、任何地方,实现与原生 App 相近的用户体验。
一个 PWA 首先是一个网页, 可以通过 Web 技术编写出一个网页应用,随后添加上 App Manifest 实现添加至设备主屏幕, 通过 Service Worker 来实现离线缓存和消息推送等功能。
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 是一个可编程的 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 缓存优先的示意图:
Service workers 所支持的事件:
通常遵循以下基本步骤来使用 service workers:
oninstall
事件的处理程序执行完毕后,可以认为 service worker 安装完成了。onactivate
主要用途是清理先前版本的 service worker 脚本中使用的资源。register()
成功后的打开的页面。也就是说,页面起始于有没有 service worker ,且在页面的接下来生命周期内维持这个状态。所以,页面不得不重新加载以让 service worker 获得完全的控制。一个简单的 PWA demo 很简单,新建项目目录,然后:
touch index.html
touch sw.js
npm install serve -g
之后进行简单的 html 和 sw.js 文件的编写:
/** login.html */
PWA
/** 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-s
if ((event.request.url.indexOf('http') === 0)) {
return fetch(event.request).then(response => {
// Check if we received a valid response
if (!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 技术仍然有很多可以借鉴和使用的点: