Service Worker实现离线应用

609 阅读7分钟

Service Worker实现离线应用

如何让网页离线之后还能访问?

前端缓存

简介

  1. HTTP缓存,HTTP缓存都是第二次请求时开始的,主要通过设置请求头

    • 强缓存:强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间,状态码200(from cache),直接从缓存中取,不会发起请求
      • Expires
      • Cache-Control
    • 协商缓存:协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问状态码304(Not Modified)通过服务器来告知缓存是否可用
      • Last-Modify/If-Modify-Since
      • ETag/If-None-Match
  2. 浏览器缓存

    • localStorage
      • 设计一个API让localStorage可以实现指定时间过期?
    • sessionStorage
    • cookie
      • httpOnly:true如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容
      • Secure:true属性规定cookie只能在https协议下才能够发送到服务器
      • domain:子域不同可以使用domain解决跨域问题
      • withCredentials:true表示跨域请求时是否需要使用凭证,服务器才能拿到你的cookie
  3. 缓存位置

    • service worker 自由控制缓存那些文件,如何匹配缓存,如何读取缓存,缓存秩序性的 注册,监听,https
    • Memory cache 读取高效,缓存秩序短 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
    • Disk cache 读取速度慢
    • push cache 只在session中存在
  4. 用户行为影响

    • 地址栏输入地址
      • 查找disk cache中是否匹配,没有匹配则发起网络请求
    • 普通刷新(F5)
      • 优先使用Memory cache,其次Disk cache
    • 强制刷新
      • ctrl+F5 浏览器不使用缓存

优点

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷

Service Worker

###1.简介 加上了Service Worker

没有Service Worker内容较少没有看出很明显的差异

cache

  1. 打开web app的方式

    • 浏览器书签、网址收藏
    • 浏览器地址输入网址
    • 搜索引擎
  2. web app manifest是一段json文件

    • 添加到主屏

  3. 什么是Service Worker

    • 是一种特殊的web worker,浏览器运行在后台与网页主线程独立的一个线程,通常是做一些耗费性能的计算,渲染和计算分开,从而避免了阻塞的情况,前身是Application Cache
  4. Service Worker特性

    • 不能直接访问操作dom
    • 需要时直接唤醒,不需要时候自动休眠
    • 离线缓存内容开发者可控
    • 一旦被安装则永远存活,除非手动卸载
    • 必须在HTTPS环境下工作(本地环境除外),安全的要求
    • 广泛使用了promise
  5. Service Worker作用域,同域下允许注册多个不同的scope的service worker

    url注册地址scope
    /static/sw.js/static/
    /static/sw.js/static/child/

    错误的示范1.不是同源域2.超过了默认左右域的3旁级域

    url注册地址scope
    /static/sw.jsother.com/
    /static/sw.js/static/child/
    /static/sw.js/assets

###2.使用条件 sw 是基于 HTTPS 的,因为service worker中涉及到请求拦截,所以必须使用HTTPS协议来保障安全。如果是本地调试的话,localhost是可以的 ###3.生命周期

生命周期www.jianshu.com/p/8770ebcb6…

  1. install --安装
  2. waiting -- 等待
  3. activate --激活

###4.调试方法

  1. offline 模拟断网操作
  2. update on reload 每次都会从新安装-激活,方便开发
  3. bypass for network 不经过service worker代理,跳过sw
  4. unregister解除sw

###5.使用方法

  1. 需要注意是sw.js这个文件不能被http缓存了,也就是需要设置这个文件cache-control:no-cache,不然会导致sw更新不到最新,另外,浏览器对service worker文件最多只能缓存24小时,避免毁灭影响
  2. 注意存储空间
    • 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.缓存策略

  1. staleWhileRevalidate
    • 这种策略的意思是当请求的路由有对应的 Cache 缓存结果就直接返回, 在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略
    • 缺点:还是会占用带宽
  2. networkFirst(网络优先)
    • 这种策略就是当请求路由是被匹配的,就采用网络优先的策略,也就是优先尝试拿到网络请求的返回结果,如果拿到网络请求的结果,就将结果返回给客户端并且写入 Cache 缓存。如果网络请求失败,那最后被缓存的 Cache 缓存结果就会被返回到客户端,这种策略一般适用于返回结果不太固定或对实时性有要求的请求,为网络请求失败进行兜底
  3. cacheFirst(缓存优先)
    • 这个策略的意思就是当匹配到请求之后直接从 Cache 缓存中取得结果,如果 Cache 缓存中没有结果,那就会发起网络请求,拿到网络请求结果并将结果更新至 Cache 缓存,并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求
  4. networkOnly(直接采用网络请求,不缓存)
    • 比较直接的策略,直接强制使用正常的网络请求,并将结果返回给客户端,这种策略比较适合对实时性要求非常高的请求
  5. cacheOnly(直接采用缓存)
    • 这个策略也比较直接,直接使用 Cache 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求

社区

  1. workbox是google公司出品的官方工具
  2. servier workermdn Api介绍及浏览器兼容性
  3. vue API文档文档利用service worker实现离线访问,vue API文档service-worker源码有兴趣的同学可以看看
  4. 语雀在线文档