一点说明
所谓的离线应用,不是指一直离线,一直没网的话,那还玩些什么呢?而是指一开始用户是有网络的,但是用户因为走进地铁,或者买了近两年的iPhone,突然就没网了,此时,让页面展示一些缓存的内容。
理清思路、抓住关键
实现一个离线Web应用的关键在于:拦截请求
和缓存资源
。
- “拦截请求”通过Service Worker实现。
- “缓存资源”通过CacheStorage实现。
在此假设读者了解Service Worker和CacheStorage的基础。
简单来讲,如果不考虑HTTP缓存的话,一个普通Web App获取静态资源是从服务器获取的。但是Service Worker相当于在浏览器的出口加了一层代理,有着拦截并处理请求的能力,这使得我们有机会选择资源是从服务器取还是从CacheStorage
中取。
CacheStorage
既然作为一种缓存,那大概就要遵从“如果命中缓存则返回缓存中的资源,否则从远处取”的基本策略。
具体到一个Web应用,静态资源可以粗略分为html和其它(js、css、图片等),html作为应用入口,有着它的特殊性。实现离线应用的关键在于缓存入口html
,这是实现离线应用的核心思路。设想一下,如果html保存在本地,那在无网络的情况下,浏览器也是可以打开的,至于html引用的其它静态资源,如果缓存过,那也是可以访问的。
代码
首先,初始化一个项目,在此以Vue项目为例,在入口html中注册service worker:
<script> if ('serviceWorker' in navigator) { window.addEventListener('load', function () { navigator.serviceWorker.register('/demo/service-worker.js').then(function (reg) { console.log('service-worker.js注册成功'); }, function (err) { console.log('service-worker.js注册失败'); }); }); } </script>
service-worker.js代码:
// 每次更新需要更新版本号 const cacheVersion = 'v1'; // service worker激活时的初始化操作,非关键代码,可以不看 self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all( cacheNames.map(function (cacheName) { // 如果当前版本和缓存版本不一致,则删除其它版本的cache if (cacheName !== cacheVersion) { return caches.delete(cacheName); } }) ); }) ); }); // 核心代码,最好看一看 self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { // 如匹配了cache,则直接返回cache中的资源 // 否则fetch资源并缓存下来 return response || fetch(event.request).then(function (r) { caches.open(cacheVersion).then(function (cache) { cache.put(event.request, r); }); return r.clone(); }); }) ); });
成果展示
代码部署好后,访问页面,刷新,可以看到:cacheStorage中的静态资源已经缓存成功:
然后我们断网,刷新,可以发现:页面还是可以访问的,静态资源的响应状态码是200,但标注了(from ServiceWorker)
:
总结
所以,如此少的代码,就可以实现一个离线Web应用,但是从Demo到实际应用,还有好长的路要走。无论如何,本文所述的是离线Web应用的基本思路和方法。