什么是PWA?
PWA 指的是使用指定技术和标准模式来开发的 Web 应用,这同时赋予它们 Web 应用和原生应用的特性。
我的简单理解就是:通过一些新技术,来增强web app的用户交互体验。
你可以通过安装应用使得它在离线的状态下也可以运行;相较于使用浏览器访问,用户也更喜欢通过点击主页上的图标来访问它们喜爱的应用。
一个标准的PWA程序,必须包含3个部分:
- https服务器或者 http://localhost
- manifest.json
- service worker
PWA的优势
- 渐进式 - 适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的。
- 流畅 - 能够借助 Service Worker 在离线或者网络较差的情况下正常访问。
- 可安装 - 用户可以添加常用的 webapp 到桌面,免去去应用商店下载的麻烦。
- 原生体验:可以和app一样,拥有首屏加载动画,可以隐藏地址栏等沉浸式体验。
- 粘性 - 通过推送离线通知等,可以让用户回流。
PWA的核心内容
web app manifest
他是一个json文件,manifest 的目的是将Web应用程序安装到设备的主屏幕,为用户提供更快的访问和更丰富的体验。
配置一些添加到做面上的一些信息。
{ "name": "zh-llm-app", "shor_name": "zl", "start_url": "index.html", "icons": [ { "sizes": "添加到桌面上的图标宽高", "src": "添加到桌面上的图标", "type": "image/类型" } ], "background_color": "应用的背景", "theme_color": "导航背景", "display": "standalone" //fullscreen 全屏显示, 所有可用的显示区域都被使用, 并且不显示状态栏。 //standalone 让这个应用看起来像一个独立的应用程序,包括具有不同的窗口,在应用程序启动器中拥有自己的图标等。 // minimal-ui 该应用程序将看起来像一个独立的应用程序,但会有浏览器地址栏。 }
具体内容请访问mdn:developer.mozilla.org/zh-CN/docs/…
sevice worker
主要用来做持久的离线缓存(这个需要配合caches来使用)。Service Worker 是浏览器和网络之间的虚拟代理。Service Worker 运行在一个与页面 JavaScript 主线程独立的线程上,并且无权访问 DOM 结构。控制网络请求、修改网络请求、返回缓存的自定义响应,或者合成响应。是一种特殊的web worker。
什么是web worker
Web Worker 是脱离在主线程之外的,将一些复杂的耗时的活交给它干完成后通过 postMessage 方法告诉主线程。Web worker是一个独立的运行环境,不能操作DOM和BOM。
一个事例: 计算大量的累加。由于比较耗时,所以放在js主线程之外去做。
// work.js let total = 0; for (let i = 0; i < 100000000; i++) { total += i; } self.postMessage({ total })
console.log("start") const worker = new Worker("./work.js"); worker.addEventListener("message", e => { console.log("e", e.data) }) console.log("end") // 执行顺序:start end 打印e
具体的web worker的用法,请访问mdn: developer.mozilla.org/zh-CN/docs/…
现在我们就来看看强大的sevice worker如何工作的吧
在window.onload中注册service worker , 防止与其他资源竞争。
注册service worker navigator.serviceWorker.register(‘./sw.js’) ,返回一个promise对象。
注册完成后,sw.js 文件会自动下载、安装,然后激活。
<script> window.onload = function() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js') } } /* 当权限为default时,我们需要获取用户的授权。 */ if (Notification.permission === 'default') { Notification.requestPermission() } if (!navigator.onLine) { new Notification('提示', { body: '你当前没有网络,你访问的是缓存中的内容!' }) } // offline: 断线 window.addEventListener('online', () => { new Notification('提示', { body: '网路已连接,请刷新访问最新的数据!' }) }) </script>
Service worker生命周期事件
- install 。
在
install
的监听函数中, 我们可以初始化缓存并添加离线应用时所需的文件。
// sw.js // 定义一个cache_name变量,由于确定后续缓存文件的更新。 const CACHE_NAME = 'cache_v1'; // 监听install事件,用于缓存静态文件 self.addEventListener("install", async (e) => { console.log("install==========") // 定义一个缓存对象。 const cache = await caches.open(CACHE_NAME); // 向cacheStorage中缓存文件 await cache.addAll(['/', './01.jpg', './manifest.json', './index.css']) // 当上述代码执行完后,才跳出此service-worker的等待。 await self.skipWaiting() })
- activate
该事件会在service worker激活的时候触发,主要用于删除旧的资源。
service worker激活后,会在下一次刷新页面的时候生效,可以通过self.clients.claim()立即获取控制权。
// sw.js // 用于删除过期的缓存 self.addEventListener("activate", async e => { console.log("activate========") // 获取当前所有的缓存对象 const keys = await caches.keys(); keys.forEach(item => { if (item !== CACHE_NAME) { // 删除过期缓存 caches.delete(item) } }) await self.clients.claim(); })
- fetch
它在每次应用发起 HTTP 请求的时候被触发。这个事件对我们来说非常有用,它允许我们拦截请求并对请求作出自定义的响应。
在这里我们可以选择网路优先还是缓存优先。
// sw.js self.addEventListener('fetch', e => { // 1. 只缓存同源的内容 const req = e.request const url = new URL(req.url) if (url.origin !== self.origin) { return } if (req.url.includes('/api')) { // 这里是请求接口 e.respondWith(networkFirst(req)) } else { // 这里是请求静态文件,在缓存中取。 e.respondWith(cacheFirst(req)) } }) // cache优先, 一般适用于静态资源 async function cacheFirst(req) { const cache = await caches.open(CACHE_NAME) const cached = await cache.match(req) // 如果从缓存中得到了 if (cached) { return cached } else { // 这里的fetch可以接受一个url,还可以是request对象。 const data = await fetch(req) return data } } // 网络优先的数据,如果我们获取到了数据,应该往缓存中存一份 async function networkFirst(req) { const cache = await caches.open(CACHE_NAME) try { const data = await fetch(req) // 网络优先,获取到的数据,应该再次更新到缓存 // 把响应的备份存储到缓存中 cache.put(req, data.clone()) return data } catch (e) { const cached = await cache.match(req) return cached } }
具体请访问mdn: developer.mozilla.org/zh-CN/docs/…