如何正確地卸載Service Worker?

如何正確地卸載Service Worker?

以下鏈接, Google Developers Service Worker工作原理:
https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#updates

但是假如某一天你網站不需要 Service Worker,如何正確地卸載呢?

以下以 create-react-app 爲例子:

  • 千萬不要直接從服務器幹掉 service-worker.js (sw.js),或者去掉 sw-precache-webpack-plugin 插件直接build。
  1. 假如你服務器是增量更新的,
    image

那麼你的服務器文件service-worker.js永遠都在,假如用戶之前訪問了你的網站,並且用戶不清緩存的話,cache就永遠都在了,你怎麼更新,用戶還是訪問舊的版本。

  1. 你說把你服務器的 service-worker.js 幹掉,那麼假如用戶之前訪問過呢?本地有一份service-worker.js,那麼按照service-worker工作原理,本地一直生效,不管你網站怎麼更新,用戶看到的依舊是舊的內容。

service-worker.js

'use strict';

var precacheConfig = [
["/index.html","a16310808c31e9e89b8d72aa2ddb058c"],
["/plugin.dll.0cf858ac.js","7268282b6a4415b541c4658c1478febc"],
["/vendor.dll.830d2c27.js","097dfeec5dda4f277752cb36b5d548ee"]
];
var cacheName = 'sw-precache-v3-sw-precache-webpack-plugin-' + (self.registration ? self.registration.scope : '');


var ignoreUrlParametersMatching = [/^utm_/];



var addDirectoryIndex = function (originalUrl, index) {
    var url = new URL(originalUrl);
    if (url.pathname.slice(-1) === '/') {
      url.pathname += index;
    }
    return url.toString();
  };

var cleanResponse = function (originalResponse) {
    // If this is not a redirected response, then we don't have to do anything.
    if (!originalResponse.redirected) {
      return Promise.resolve(originalResponse);
    }

    // Firefox 50 and below doesn't support the Response.body stream, so we may
    // need to read the entire body to memory as a Blob.
    var bodyPromise = 'body' in originalResponse ?
      Promise.resolve(originalResponse.body) :
      originalResponse.blob();

    return bodyPromise.then(function(body) {
      // new Response() is happy when passed either a stream or a Blob.
      return new Response(body, {
        headers: originalResponse.headers,
        status: originalResponse.status,
        statusText: originalResponse.statusText
      });
    });
  };

var createCacheKey = function (originalUrl, paramName, paramValue,
                           dontCacheBustUrlsMatching) {
    // Create a new URL object to avoid modifying originalUrl.
    var url = new URL(originalUrl);

    // If dontCacheBustUrlsMatching is not set, or if we don't have a match,
    // then add in the extra cache-busting URL parameter.
    if (!dontCacheBustUrlsMatching ||
        !(url.pathname.match(dontCacheBustUrlsMatching))) {
      url.search += (url.search ? '&' : '') +
        encodeURIComponent(paramName) + '=' + encodeURIComponent(paramValue);
    }

    return url.toString();
  };

var isPathWhitelisted = function (whitelist, absoluteUrlString) {
    // If the whitelist is empty, then consider all URLs to be whitelisted.
    if (whitelist.length === 0) {
      return true;
    }

    // Otherwise compare each path regex to the path of the URL passed in.
    var path = (new URL(absoluteUrlString)).pathname;
    return whitelist.some(function(whitelistedPathRegex) {
      return path.match(whitelistedPathRegex);
    });
  };

var stripIgnoredUrlParameters = function (originalUrl,
    ignoreUrlParametersMatching) {
    var url = new URL(originalUrl);
    // Remove the hash; see https://github.com/GoogleChrome/sw-precache/issues/290
    url.hash = '';

    url.search = url.search.slice(1) // Exclude initial '?'
      .split('&') // Split into an array of 'key=value' strings
      .map(function(kv) {
        return kv.split('='); // Split each 'key=value' string into a [key, value] array
      })
      .filter(function(kv) {
        return ignoreUrlParametersMatching.every(function(ignoredRegex) {
          return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes.
        });
      })
      .map(function(kv) {
        return kv.join('='); // Join each [key, value] array into a 'key=value' string
      })
      .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each

    return url.toString();
  };


var hashParamName = '_sw-precache';
var urlsToCacheKeys = new Map(
  precacheConfig.map(function(item) {
    var relativeUrl = item[0];
    var hash = item[1];
    var absoluteUrl = new URL(relativeUrl, self.location);
    var cacheKey = createCacheKey(absoluteUrl, hashParamName, hash, /\.\w{8}\./);
    return [absoluteUrl.toString(), cacheKey];
  })
);

function setOfCachedUrls(cache) {
  return cache.keys().then(function(requests) {
    return requests.map(function(request) {
      return request.url;
    });
  }).then(function(urls) {
    return new Set(urls);
  });
}

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return setOfCachedUrls(cache).then(function(cachedUrls) {
        return Promise.all(
          Array.from(urlsToCacheKeys.values()).map(function(cacheKey) {
            // If we don't have a key matching url in the cache already, add it.
            if (!cachedUrls.has(cacheKey)) {
              var request = new Request(cacheKey, {credentials: 'same-origin'});
              return fetch(request).then(function(response) {
                // Bail out of installation unless we get back a 200 OK for
                // every request.
                if (!response.ok) {
                  throw new Error('Request for ' + cacheKey + ' returned a ' +
                    'response with status ' + response.status);
                }

                return cleanResponse(response).then(function(responseToCache) {
                  return cache.put(cacheKey, responseToCache);
                });
              });
            }
          })
        );
      });
    }).then(function() {
      
      // Force the SW to transition from installing -> active state
      return self.skipWaiting();
      
    })
  );
});

self.addEventListener('activate', function(event) {
  var setOfExpectedUrls = new Set(urlsToCacheKeys.values());

  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.keys().then(function(existingRequests) {
        return Promise.all(
          existingRequests.map(function(existingRequest) {
            if (!setOfExpectedUrls.has(existingRequest.url)) {
              return cache.delete(existingRequest);
            }
          })
        );
      });
    }).then(function() {
      
      return self.clients.claim();
      
    })
  );
});


self.addEventListener('fetch', function(event) {
  if (event.request.method === 'GET') {
    // Should we call event.respondWith() inside this fetch event handler?
    // This needs to be determined synchronously, which will give other fetch
    // handlers a chance to handle the request if need be.
    var shouldRespond;

    // First, remove all the ignored parameters and hash fragment, and see if we
    // have that URL in our cache. If so, great! shouldRespond will be true.
    var url = stripIgnoredUrlParameters(event.request.url, ignoreUrlParametersMatching);
    shouldRespond = urlsToCacheKeys.has(url);

    // If shouldRespond is false, check again, this time with 'index.html'
    // (or whatever the directoryIndex option is set to) at the end.
    var directoryIndex = 'index.html';
    if (!shouldRespond && directoryIndex) {
      url = addDirectoryIndex(url, directoryIndex);
      shouldRespond = urlsToCacheKeys.has(url);
    }

    // If shouldRespond is still false, check to see if this is a navigation
    // request, and if so, whether the URL matches navigateFallbackWhitelist.
    var navigateFallback = '/index.html';
    if (!shouldRespond &&
        navigateFallback &&
        (event.request.mode === 'navigate') &&
        isPathWhitelisted(["^(?!\\/__).*"], event.request.url)) {
      url = new URL(navigateFallback, self.location).toString();
      shouldRespond = urlsToCacheKeys.has(url);
    }

    // If shouldRespond was set to true at any point, then call
    // event.respondWith(), using the appropriate cache key.
    if (shouldRespond) {
      event.respondWith(
        caches.open(cacheName).then(function(cache) {
          return cache.match(urlsToCacheKeys.get(url)).then(function(response) {
            if (response) {
              return response;
            }
            throw Error('The cached response that was expected is missing.');
          });
        }).catch(function(e) {
          // Fall back to just fetch()ing the request if some unexpected error
          // prevented the cached response from being valid.
          console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
          return fetch(event.request);
        })
      );
    }
  }
});

index.js 入口文件

https://github.com/facebook/create-react-app/blob/next/packages/react-scripts/template/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
//----vs-----
serviceWorker.register({
    onUpdate: async (registration) => {
        await registration.update();
        message.info("網站更新完成, 請刷新頁面: " + moment().format('YYYY-MM-DD HH:mm:ss'), 0.5, () => {
            window.location.reload();
        });
    },
    onSuccess: () => {}
});


假如你引入了serviceWorker文件, 併發布了,

https://github.com/facebook/create-react-app/blob/next/packages/react-scripts/template/src/serviceWorker.js

正確做法是:

serviceWorker.register(); 改成 serviceWorker.unregister();
但是同時千萬要記住 要保留 sw-precache-webpack-plugin 去做webpack 構建(目的是爲了生成新的service-worker.js,觸發更新)。按照人的既定思維,既然不要了,那麼當然要移除。

假如移除了 sw-precache-webpack-plugin, 你怎麼 生成新版本的 service-worker.js,還有,沒有新版本 service-worker.js 又怎麼會更新你的代碼了,這裏似乎出現雙重陷阱,但是當你理解了service-worker.js 生命週期原理後,一切都可以理解。

最後總結:

在入口加入:

serviceWorker.unregister();

service-worker.js 文件 依舊需要更新。

假如真的不想引入 sw-precache-webpack-plugin 做webpack構建的話,請把服務器上面的
service-worker.js precacheConfig 清空

var precacheConfig = [
];

https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#opting-out-of-caching

If you would prefer not to enable service workers prior to your initial production deployment, then remove the call to registerServiceWorker() from src/index.js.

If you had previously enabled service workers in your production deployment and have decided that you would like to disable them for all your existing users, you can swap out the call to registerServiceWorker() in src/index.js first by modifying the service worker import:

import { unregister } from './registerServiceWorker';

and then call unregister() instead. After the user visits a page that has unregister(), the service worker will be uninstalled. Note that depending on how /service-worker.js is served, it may take up to 24 hours for the cache to be invalidated.

create-react-app 提示的測試服務器
image
對service-worker.js會有HTTP緩存,部署簡單nginx 服務器進行測試

    server {
       listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

       location / {
           root   D:\yourproject\build;
           index  index.html index.htm;
           # proxy_no_cache 1;
           add_header Cache-Control "no-cache";
           try_files $uri $uri/ /index.html;
       }

       location /service\-worker\.js {
            expires -1;
            add_header Pragma "no-cache";
       }
    }

serviceWorker.register({
    onUpdate: async (registration) => {
        await registration.update(); // 這裏很重要
        message.info("網站更新完成, 請刷新頁面: " + moment().format('YYYY-MM-DD HH:mm:ss'), 0.5, () => {
            window.location.reload();
        });
    },
    onSuccess: () => {}
});

延伸閱讀:

https://lavas.baidu.com/guide/v2/advanced/service-worker#%E6%B3%A8%E5%86%8C-service-worker-%E6%89%A9%E5%B1%95
註冊 Service Worker (擴展)

提示:這部分內容由 Lavas 內部處理,並不需要開發者進行參與,僅僅作爲解答開發者疑問的擴展閱讀存在。

Service Worker 編寫完成後,還需要進行註冊才能真正生效。常規的註冊代碼能夠在各類 Service Worker 教程或文章中找到,但在實際項目中有一個不得不考慮的問題,使得我們必須對註冊代碼進行一些改動,那就是 Service Worker 更新 的問題。

https://github.com/lavas-project/sw-register-webpack-plugin

離線指南
https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network

假如熟悉Service Worker 緩存機制的話,那麼爲什麼要卸載呢 ?

-EOF-

 

更新請訪問

http://blog.w3cub.com/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章