chaihongjun.me

serviceworker配置优化版

之前的文章介绍的缓存方案是白名单形式,只把需要缓存的内容列举出来进行缓存,这篇文章反向思考使用黑名单模式,黑名单里的不缓存,其他的都缓存,一般不缓存的都是第三方域名下的跨域的资源,也无法缓存。

e5871dd9d89130c4d42696576a40a84d.gif

延迟注册serviceworker(每个页面都要加载):

window.addEventListener('load', function() {
  if('serviceWorker' in navigator){
     // serviceworker.js 文件要放在网站发布的根目录
     navigator.serviceWorker.register('/serviceworker.js').then(function (registration) {
      console.log('Service Worker Registered,register script: serviceworker.js.');
    }).catch(function (error) {
      // registration failed
      console.log('Registration failed with ' + error);
    });
  }
});
// 控制台显示service worker缓存占用情况
  if ("storage" in navigator && "estimate" in navigator.storage) {
    // 检查浏览器是否支持 Web Storage 估计功能
    navigator.storage
      .estimate()
      .then((estimate) => {
        // 转换使用量和配额到 MB
        const usageMB = estimate.usage / 1024 / 1024;
        const quotaMB = estimate.quota / 1024 / 1024;

        // 计算使用比例
        const proportion = (estimate.usage / estimate.quota) * 100;

        // 打印结果
        console.log(
          `Using ${usageMB.toFixed(2)} out of ${quotaMB.toFixed(
            2
          )} MB. The proportion is ${proportion.toFixed(2)}%`
        );
      })
      .catch((error) => {
        // 处理可能的错误
        console.error("Error estimating storage:", error);
      });
  } else {
    // 如果不支持,可以在这里打印一条消息
    console.log("Browser does not support storage estimation.");
  }

这个和之前的一样没有什么大的变化。

然后是serviceworker.js一定是放在网站发布目录的根目录,保证它的作用域最大化。具体的配置发生了变化:

//serviceworker.js
'use strict';
const version = 'v20180608';   //缓存版本号,修改之后缓存会刷新
const __DEVELOPMENT__ = false;    //开发模式
const __DEBUG__ = false;       //调试模式
const offlineResources = [ //离线断网的时候提供友好界面
    '/',
    '/offline.html',
    '/offline.svg'
];
const ignoreFetch = [ //这里是需要忽略不缓存的的第三方的域名
    /chrome-extension:\/\//,                  // 建议加上忽略chrome插件的协议
    /https?:\/\/ae.bdstatic.com\//,
        /https?:\/\/msite.baidu.com\//,
        /https?:\/\/s.bdstatic.com\//,
        /https?:\/\/timg01.bdimg.com\//,
        /https?:\/\/zz.bdstatic.com\//,
        /https?:\/\/jspassport.ssl.qhimg.com\//,
        /https?:\/\/hm.baidu.com\//,
        /https?:\/\/sp0.baidu.com\//,
        /https?:\/\/s.360.cn\//,
        /https?:\/\/s.ssl.qhres.com\//,
        /https?:\/\/www.google-analytics.com\//,
        /https?:\/\/www.googletagmanager.com\//,
        /.php$/ 
];
//////////
// Install
//////////
function onInstall(event) {
    log('install event in progress.');
    event.waitUntil(updateStaticCache());
}

function updateStaticCache() {
    return caches
        .open(cacheKey('offline'))
        .then((cache) => {
            return cache.addAll(offlineResources);
        })
        .then(() => {
            log('installation complete!');
        });
}
////////
// Fetch
////////
function onFetch(event) {
    const request = event.request;
    if (shouldAlwaysFetch(request)) {
        event.respondWith(networkedOrOffline(request));
        return;
    }
    if (shouldFetchAndCache(request)) {
        event.respondWith(networkedOrCached(request));
        return;
    }
    event.respondWith(cachedOrNetworked(request));
}

function networkedOrCached(request) {
    return networkedAndCache(request)
        .catch(() => { return cachedOrOffline(request) });
}
// Stash response in cache as side-effect of network request
function networkedAndCache(request) {
    return fetch(request)
        .then((response) => {
            var copy = response.clone();
            caches.open(cacheKey('resources'))
                .then((cache) => {
                    cache.put(request, copy);
                });
            log("(network: cache write)", request.method, request.url);
            return response;
        });
}

function cachedOrNetworked(request) {
    return caches.match(request)
        .then((response) => {
            log(response ? '(cached)' : '(network: cache miss)', request.method, request.url);
            return response ||
                networkedAndCache(request)
                .catch(() => { return offlineResponse(request) });
        });
}

function networkedOrOffline(request) {
    return fetch(request)
        .then((response) => {
            log('(network)', request.method, request.url);
            return response;
        })
        .catch(() => {
            return offlineResponse(request);
        });
}

function cachedOrOffline(request) {
    return caches
        .match(request)
        .then((response) => {
            return response || offlineResponse(request);
        });
}

function offlineResponse(request) {
    log('(offline)', request.method, request.url);
    if (request.url.match(/.(jpg|png|gif|svg|jpeg)(?.*)?$/)) {
        return caches.match('/offline.svg');
    } else {
        return caches.match('/offline.html');
    }
}
///////////
// Activate
///////////
function onActivate(event) {
    log('activate event in progress.');
    event.waitUntil(removeOldCache());
}

function removeOldCache() {
    return caches
        .keys()
        .then((keys) => {
            return Promise.all( // We return a promise that settles when all outdated caches are deleted.
                keys
                .filter((key) => {
                    return !key.startsWith(version); // Filter by keys that don't start with the latest version prefix.
                })
                .map((key) => {
                    return caches.delete(key); // Return a promise that's fulfilled when each outdated cache is deleted.
                })
            );
        })
        .then(() => {
            log('removeOldCache completed.');
        });
}

function cacheKey() {
    return [version, ...arguments].join(':');
}

function log() {
    if (developmentMode()) {
        console.log("SW:", ...arguments);
    }
}

function shouldAlwaysFetch(request) {
    return __DEVELOPMENT__ ||
        request.method !== 'GET' ||
        ignoreFetch.some(regex => request.url.match(regex));
}

function shouldFetchAndCache(request) {
    return ~request.headers.get('Accept').indexOf('text/html');
}

function developmentMode() {
    return __DEVELOPMENT__ || __DEBUG__;
}
log("Hello from ServiceWorker land!", version);
self.addEventListener('install', onInstall);
self.addEventListener('fetch', onFetch);
self.addEventListener("activate", onActivate);

注意到上面的配置文件里面有提到一个离线文件offline.html,这里给出一个简单的版本:

<!DOCTYPE html>    
<html>    

<head>    
    <meta charset="utf-8">    
    <meta name="theme-color" content="#242628">    
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">    
    <meta http-equiv="X-UA-Compatible" content="ie=edge">    
    <title>网络异常 | Powered by chaihongjun.me</title>    
    <style>
    :root {
        --wrapper-width-percent: 80%;
        --wrapper-min-width: 600px;
        --wrapper-max-width: 1024px;
        --navbar-height: 48px;
        --area-gap-size: 16px;
        --text-gap-size: 4px;
        --line-height: 24px;
        --border-radius-size: 4px;
        --color-dark-1: #353739;
        --color-dark-2: #242628;
        --color-dark-3: #111517;
        --color-medium: #96999b;
        --color-light-1: #aaacaf;
        --color-light-2: #d6d7d9;
        --color-light-3: #f2f4f6;
        --color-orange: #f57c00;
        --color-blue: #2196f3;
        --color-green: #4caf50;
        --color-purple: #4a148c;
        --color-shadow: rgba(0, 0, 0, 0.3)
    }

    * {
        -webkit-tap-highlight-color: transparent;
        box-sizing: border-box;
        outline: none
    }

    @-ms-viewport {
        width: device-width;
    }

    ::selection {
        background: var(--color-orange);
        color: var(--color-light-3)
    }

    html,
    body {
        margin: 0;
        padding: 0;
        font-size: 16px;
        font-weight: 400;
        background: var(--color-dark-2)
    }

    body,
    button,
    input,
    textarea,
    select {
        border-radius: var(--border-radius-size);
        font-family: pingfang sc, helvetica neue, hiragino sans gb, segoe ui, microsoft yahei ui, 微软雅黑, sans-serif
    }

    button {
        background: var(--color-orange);
        box-shadow: 1px 2px 8px var(--color-shadow);
        border: none;
        color: var(--color-light-3);
        cursor: pointer;
        font-size: 16px;
        margin: 1rem 0;
        padding: calc(var(--text-gap-size) * 2);
        width: 15rem
    }

    button:hover {
        background: #ff9c20
    }

    button:active {
        background: #d55c00
    }

    :disabled {
        background: var(--color-light-1);
        cursor: no-drop
    }

    pre,
    pre *,
    code,
    var {
        font-family: source code variable, consolas, menlo, monaco, courier new, monospace !important
    }

    var,
    code {
        display: inline-block
    }

    b,
    strong {
        font-weight: 600
    }

    a {
        text-decoration: none;
        color: var(--color-blue)
    }

    body {
        background: #242628;
        color: #d6d7d9;
        padding: 0 1rem;
    }
    </style>    
</head>    

<body> 
    <h1>网络异常</h1>    
    <p>你的网络似乎遭遇了中断,无法从 <code>chaihongjun.me</code> 获取最新的数据。</p>    
    <p>由于 Service Worker 技术,你之前访问过的页面已被缓存,可以从当前设备上离线访问,但部分资源(图片、样式等)可能无法显示。</p>    
    <button onclick="window.history.back();">返回上一页</button>    
    <p>若已确认你的网络环境正常,可以点击下方的按钮或按浏览器刷新按钮,来重载当前页面。</p>    
    <button onclick="location.reload()">立即重载</button>    
</body>    

</html>


知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。作者:柴宏俊»