大家都知道,如今开发和推出一个 APP 变得越来越困难。因此,选择开发具有 APP 体验的 Web 应用是一个不错的选择。渐进式网络应用(Progressive Web App,简称 PWA)是一种结合了网页和传统移动应用的开发技术。它利用了现代的 Web 技术,如 Service Workers 和 Web App Manifest 等,使得网页应用能够拥有类似原生应用的功能和用户体验。具体来说,PWA 可以创建快捷方式到用户的主屏幕上,并且支持离线访问数据和接收推送通知等功能,为用户提供了更加流畅、可靠的应用体验。
PWA 的优点:
- 跨平台兼容性:PWA 可以在各种设备和操作系统上运行,无需为不同平台开发单独的应用,大大减少了开发和维护成本。
- 可发现性:PWA 可以通过 Web 搜索引擎被搜索到,使得应用更容易被用户发现和访问。
- 离线访问:通过使用 Service Workers,PWA 可以缓存应用的核心文件和数据,使得用户在离线状态下也能够访问应用内容。
- 快速加载:PWA 利用缓存机制和增量更新,使得应用可以快速加载和响应用户操作,提供更好的性能体验。
- 推送通知:PWA 可以向用户发送推送通知,增强用户参与度和留存率。
PWA 的缺点:
- 功能受限:由于依赖于 Web 技术,PWA 在某些功能上可能无法与原生应用相媲美,如访问设备硬件等。
- 兼容性问题:虽然 PWA 在现代浏览器上有较好的支持,但在一些旧版本浏览器上可能存在兼容性问题。
- 学习成本:相对于传统的 Web 开发,PWA 需要开发人员熟悉新的技术和开发模式,可能存在一定的学习曲线。
三个要素
要使你的网站转换成 PWA,需要以下三个关键要素:
Web App Manifest
:实现将 PWA 网页应用添加到桌面的功能。Service Worker
:用于缓存应用的核心文件和数据,实现离线访问和快速加载等功能。HTTPS
:HTTP 是 PWA 强制要求的,用来保证程序的安全性,本地调试可以使用 http://localhost。
Web App Manifest
接下来我将使用 vite 构建 react 应用模板并转换成 PWA,先创建项目。
然后在 public 目录下创建 manifest.json 文件和 icons 目录放置 icon 图片。为什么要在 public 目录下?因为 public 目录下的文件打包时会直接复制到 dist 目录下。
manifest.json
{
"name": "Dodo Reader",
"short_name": "Dodo Reader",
"description": "Dodo Reader是一个支持阅读PDF、EPUB和TXT文件格式的Web应用程序",
"icons": [
{
"src": "icons/icon-32.png",
"sizes": "32x32",
"type": "image/png"
},
// ...
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "fullscreen",
"theme_color": "#FFFFFF",
"background_color": "#FFFFFF"
}
这些字段看名称几乎都能知道它的作用,其中display
包含fullscreen
、standalone
、minimal-ui
和browser
。然后在根目录下的 index.html 中 head 内添加如下代码:
<link rel="manifest" href="manifest.json" />
完成之后启动服务,如果在浏览器上能看到添加应用图标,表示配置成功。
Service Worker
Service Worker 可以缓存资源,实现离线时的资源访问,并在离线状态下提供更好的用户体验。首先在 public 目录下添加文件 service-worker.js,并添加如下代码:
const deleteCache = async (key) => {
await caches.delete(key)
}
const deleteOldCaches = async () => {
const cacheKeepList = ['v1']
const keyList = await caches.keys()
const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key))
await Promise.all(cachesToDelete.map(deleteCache))
}
self.addEventListener('activate', (event) => {
// 删除旧版本资源缓存
event.waitUntil(deleteOldCaches())
})
const putInCache = async (request, response) => {
const cache = await caches.open('v1')
await cache.put(request, response)
}
const cacheFirst = async (request) => {
// 判断是否有缓存
const responseFromCache = await caches.match(request)
if (responseFromCache) {
return responseFromCache
}
// 如没有缓存,请求并缓存
const responseFromNetwork = await fetch(request)
putInCache(request, responseFromNetwork.clone())
return responseFromNetwork
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
// 判断是否当前站点资源请求
if (url.origin === location.origin) {
event.respondWith(cacheFirst(event.request))
}
})
然后在 src/main.tsx 文件添加注册代码:
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/service-worker.js')
.then(function () {
console.log('Service worker installed')
})
.catch(function (error) {
console.log('Service Worker registration failed:', error)
})
}
添加完成可以验证一下是否挂载成功,到开发者工具中应用程序面板查看缓存存储,如果有内容表示成功缓存。
这个时候即使断开网络也是可以访问的。
HTTPS
HTTP 不用多说,上线之后一定要配置,不然无法生效。
下面是我在 PC 桌面端添加的样例。