Service Worker实现离线应用
如何让网页离线之后还能访问?
前端缓存
简介
-
HTTP缓存,HTTP缓存都是第二次请求时开始的,主要通过设置请求头
- 强缓存:强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间,状态码200(from cache),直接从缓存中取,不会发起请求
- Expires
- Cache-Control
- 协商缓存:协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问状态码304(Not Modified)通过服务器来告知缓存是否可用
- Last-Modify/If-Modify-Since
- ETag/If-None-Match
- 强缓存:强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间,状态码200(from cache),直接从缓存中取,不会发起请求
-
浏览器缓存
- localStorage
- 设计一个API让localStorage可以实现指定时间过期?
- sessionStorage
- cookie
- httpOnly:true如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容
- Secure:true属性规定cookie只能在https协议下才能够发送到服务器
- domain:子域不同可以使用domain解决跨域问题
- withCredentials:true表示跨域请求时是否需要使用凭证,服务器才能拿到你的cookie
- localStorage
-
缓存位置
- service worker 自由控制缓存那些文件,如何匹配缓存,如何读取缓存,缓存秩序性的 注册,监听,https
- Memory cache 读取高效,缓存秩序短 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
- Disk cache 读取速度慢
- push cache 只在session中存在
-
用户行为影响
- 地址栏输入地址
- 查找disk cache中是否匹配,没有匹配则发起网络请求
- 普通刷新(F5)
- 优先使用Memory cache,其次Disk cache
- 强制刷新
- ctrl+F5 浏览器不使用缓存
- 地址栏输入地址
优点
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷
Service Worker
###1.简介 加上了Service Worker
没有Service Worker内容较少没有看出很明显的差异
-
打开web app的方式
- 浏览器书签、网址收藏
- 浏览器地址输入网址
- 搜索引擎
-
web app manifest是一段json文件
- 添加到主屏
- 启动动画、桌面icon、浏览器地址栏沉浸式体验
- main.m.taobao.com
- 添加到主屏
-
什么是Service Worker
- 是一种特殊的web worker,浏览器运行在后台与网页主线程独立的一个线程,通常是做一些耗费性能的计算,渲染和计算分开,从而避免了阻塞的情况,前身是Application Cache
-
Service Worker特性
- 不能直接访问操作dom
- 需要时直接唤醒,不需要时候自动休眠
- 离线缓存内容开发者可控
- 一旦被安装则永远存活,除非手动卸载
- 必须在HTTPS环境下工作(本地环境除外),安全的要求
- 广泛使用了promise
-
Service Worker作用域,同域下允许注册多个不同的scope的service worker
url注册地址 scope /static/sw.js /static/ /static/sw.js /static/child/ 错误的示范1.不是同源域2.超过了默认左右域的3旁级域
url注册地址 scope /static/sw.js other.com/ /static/sw.js /static/child/ /static/sw.js /assets
###2.使用条件 sw 是基于 HTTPS 的,因为service worker中涉及到请求拦截,所以必须使用HTTPS协议来保障安全。如果是本地调试的话,localhost是可以的 ###3.生命周期
生命周期www.jianshu.com/p/8770ebcb6…
- install --安装
- waiting -- 等待
- activate --激活
###4.调试方法
- offline 模拟断网操作
- update on reload 每次都会从新安装-激活,方便开发
- bypass for network 不经过service worker代理,跳过sw
- unregister解除sw
###5.使用方法
- 需要注意是sw.js这个文件不能被http缓存了,也就是需要设置这个文件cache-control:no-cache,不然会导致sw更新不到最新,另外,浏览器对service worker文件最多只能缓存24小时,避免毁灭影响
- 注意存储空间
- cacheStorage并不是无限缓存,需要手动管理缓存,及时清理缓存对动态内容做数量控制,缓存淘汰算法--LRU算法
通常的项目结构,前端运行npm run build之后生成dist目录,然后执行bash serviceWorker.sh替换CACHE_VERSION/CONFIG
project
dist
css
img
js
index.html
sw.js
public
index.html
sw.js
src
README.md
serviceWorker.sh
...
// sw.js
let VERSION = CACHE_VERSION; // 版本号
let CACHE_NAME = "cache_v" + VERSION;
let CACHE_URLS = [
CONFIG // 缓存的文件路径
];
/**
* 缓存到 cacheStorage 里
*
* @param {Request} req 请求对象
* @param {Response} res 响应对象
*/
function saveToCache(req, res) {
return caches.open(CACHE_NAME).then(cache => cache.put(req, res));
}
/**
* 预缓存
*
* @return {Promise} 缓存成功的promise
*/
function precache() {
return caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
});
}
/**
* 清除过期的 cache
*
* @return {Promise} promise
*/
function clearStaleCache() {
return caches.keys().then(keys => {
keys.forEach(key => {
if (CACHE_NAME !== key) {
caches.delete(key);
}
});
});
}
/**
* 请求并缓存内容
*
* @param {Request} req request
* @return {Promise}
*/
function fetchAndCache(req) {
return fetch(req).then(function (res) {
saveToCache(req, res.clone());
return res;
});
}
// 下载新的缓存
self.addEventListener("install", function (event) {
event.waitUntil(precache().then(self.skipWaiting));
});
// 删除就的缓存
self.addEventListener("activate", function (event) {
event.waitUntil(Promise.all([self.clients.claim(), clearStaleCache()]));
});
self.addEventListener("fetch", function (event) {
// 只对同源的资源走 sw,cdn 上的资源利用 http 缓存策略
if (new URL(event.request.url).origin !== self.origin) {
return;
}
if (event.request.url.includes("/api/movies")) {
event.respondWith(
fetchAndCache(event.request).catch(function () {
return caches.match(event.request);
})
);
return;
}
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request);
})
);
});
# serviceWorker.sh
#!/bin/bash
rm -f serviceWorker.txt
function readfile ()
{
#这里`为esc下面的按键符号
for file in `ls $1`
do
#这里的-d表示是一个directory,即目录/子文件夹
if [ -d $1"/"$file ]
then
#如果子文件夹则递归
readfile $1"/"$file
else
#否则就能够读取该文件的地址
/bin/echo -n \"$1"/"$file\"\, >> serviceWorker.txt
#读取该文件的文件名,basename是提取文件名的关键字
#echo `basename $file` >> fileName.txt
fi
done
}
#函数定义结束,这里用来运行函数
folder="./dist"
readfile $folder
sed -i '' 's/\.\/dist//g' serviceWorker.txt
sed -i '' '$s/,$//' serviceWorker.txt
filepath=$(cd "$(dirname "$0")"; pwd)
getFileName=$(cat "${filepath}/serviceWorker.txt")
#替换/dist/sw.js中CONFIG 缓存静态资源路径
sed -i '' "s:CONFIG:${getFileName}:g" ${filepath}/dist/sw.js
time=$(date "+%Y%m%d%H%M")
#替换/dist/sw.js中CACHE_VERSION 缓存版本号
sed -i '' "s:CACHE_VERSION:${time}:g" ${filepath}/dist/sw.js
###6.缓存策略
- staleWhileRevalidate
- 这种策略的意思是当请求的路由有对应的 Cache 缓存结果就直接返回, 在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略
- 缺点:还是会占用带宽
- networkFirst(网络优先)
- 这种策略就是当请求路由是被匹配的,就采用网络优先的策略,也就是优先尝试拿到网络请求的返回结果,如果拿到网络请求的结果,就将结果返回给客户端并且写入 Cache 缓存。如果网络请求失败,那最后被缓存的 Cache 缓存结果就会被返回到客户端,这种策略一般适用于返回结果不太固定或对实时性有要求的请求,为网络请求失败进行兜底
- cacheFirst(缓存优先)
- 这个策略的意思就是当匹配到请求之后直接从 Cache 缓存中取得结果,如果 Cache 缓存中没有结果,那就会发起网络请求,拿到网络请求结果并将结果更新至 Cache 缓存,并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求
- networkOnly(直接采用网络请求,不缓存)
- 比较直接的策略,直接强制使用正常的网络请求,并将结果返回给客户端,这种策略比较适合对实时性要求非常高的请求
- cacheOnly(直接采用缓存)
- 这个策略也比较直接,直接使用 Cache 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求
社区
- workbox是google公司出品的官方工具
- servier workermdn Api介绍及浏览器兼容性
- vue API文档文档利用service worker实现离线访问,vue API文档service-worker源码有兴趣的同学可以看看
- 语雀在线文档