48

我一直在阅读html5rocks 服务工作者简介文章,并创建了一个基本的服务工作者来缓存页面、JS 和 CSS,它们按预期工作:

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/'
];

// Set the callback for the install step
self.addEventListener('install', function (event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // IMPORTANT: Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the response
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // IMPORTANT: Clone the response. A response is a stream
            // and because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have 2 stream.
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

当我对 CSS 进行更改时,由于服务人员正确地从缓存中返回 CSS,因此不会获取此更改。

我卡住的地方是如果我要更改 HTML、JS 或 CSS,我将如何确保服务人员从服务器加载更新版本(如果可以)而不是从缓存中加载?我尝试在 CSS 导入上使用版本标记,但这似乎不起作用。

4

5 回答 5

52

一种选择是仅使用 service worker的缓存作为后备,并始终尝试通过fetch(). 但是,您会失去一些缓存优先策略提供的性能提升。

另一种方法是使用sw-precache生成服务工作者脚本作为站点构建过程的一部分。

它生成的服务工作者将使用文件内容的哈希来检测更改,并在部署新版本时自动更新缓存。它还将使用缓存清除 URL 查询参数来确保您不会意外地使用 HTTP 缓存中的过时版本填充您的服务工作者缓存。

在实践中,您最终会得到一个使用性能友好的缓存优先策略的服务工作者,但缓存将在页面加载后“在后台”更新,以便下次访问时,一切都是新鲜的。如果需要,可以向用户显示一条消息,让他们知道有可用的更新内容并提示他们重新加载。

于 2015-10-21T18:06:51.733 回答
23

使缓存无效的一种方法是CACHE_NAME在您更改缓存文件中的任何内容时增加版本。由于该更改会更改service-worker.js浏览器将加载更新版本,您将有机会删除旧缓存并创建新缓存。您可以删除处理程序中的旧缓存activate。这是prefetch sample中描述的策略。如果您已经在 CSS 文件上使用了某种版本标记,请确保它们可以进入服务工作者脚本。

这当然不会改变 CSS 文件上的缓存头需要正确设置的事实。否则 service worker 只会加载已经缓存在浏览器缓存中的文件。

于 2015-10-21T15:53:03.980 回答
4

浏览器缓存问题

这里的主要问题是,当您的新 Service Worker 正在安装时,他会获取由之前的 Service Worker 处理的请求,并且很可能他正在从缓存中获取资源,因为这是您的缓存策略。然后即使你用新代码更新你的服务工作者,一个新的缓存名称,调用self.skipWaiting(),他仍然把缓存中的旧资源放入缓存中!

这就是我完全更新 Service Worker 的方式

要知道的一件事是,每次您的代码脚本更改时,服务工作者都会触发安装事件,因此您不需要使用版本标记或其他任何东西,只需保持相同的文件名即可,甚至推荐使用。浏览器还有其他方式会认为您的 service worker 已更新。

1.重写你的安装事件处理程序

我不使用cache.addAll,因为它坏了。实际上,如果无法获取您要缓存的一个且只有一个资源,则整个安装将失败,甚至不会将一个文件添加到缓存中。现在想象一下,您要缓存的文件列表是从存储桶自动生成的(这是我的情况),并且您的存储桶已更新并删除了一个文件,那么您的 PWA 将无法安装,它不应该安装。

sw.js

self.addEventListener('install', (event) => {
  // prevents the waiting, meaning the service worker activates
  // as soon as it's finished installing
  // NOTE: don't use this if you don't want your sw to control pages
  // that were loaded with an older version
  self.skipWaiting();

  event.waitUntil((async () => {
    try {
      // self.cacheName and self.contentToCache are imported via a script
      const cache = await caches.open(self.cacheName);
      const total = self.contentToCache.length;
      let installed = 0;

      await Promise.all(self.contentToCache.map(async (url) => {
        let controller;

        try {
          controller = new AbortController();
          const { signal } = controller;
          // the cache option set to reload will force the browser to
          // request any of these resources via the network,
          // which avoids caching older files again
          const req = new Request(url, { cache: 'reload' });
          const res = await fetch(req, { signal });

          if (res && res.status === 200) {
            await cache.put(req, res.clone());
            installed += 1;
          } else {
            console.info(`unable to fetch ${url} (${res.status})`);
          }
        } catch (e) {
          console.info(`unable to fetch ${url}, ${e.message}`);
          // abort request in any case
          controller.abort();
        }
      }));

      if (installed === total) {
        console.info(`application successfully installed (${installed}/${total} files added in cache)`);
      } else {
        console.info(`application partially installed (${installed}/${total} files added in cache)`);
      }
    } catch (e) {
      console.error(`unable to install application, ${e.message}`);
    }
  })());
});

2. 激活(新)Service Worker 时清理旧缓存:

sw.js

// remove old cache if any
self.addEventListener('activate', (event) => {
  event.waitUntil((async () => {
    const cacheNames = await caches.keys();

    await Promise.all(cacheNames.map(async (cacheName) => {
      if (self.cacheName !== cacheName) {
        await caches.delete(cacheName);
      }
    }));
  })());
});

3. 每次我更新我的资产时,我都会更新缓存名称:

sw.js

// this imported script has the newly generated cache name (self.cacheName)
// and a list of all the files on my bucket I want to be cached (self.contentToCache),
// and is automatically generated in Gitlab based on the tag version
self.importScripts('cache.js');

// the install event will be triggered if there's any update,
// a new cache will be created (see 1.) and the old one deleted (see 2.)

4. 缓存中的句柄ExpiresCache-Control响应头

我在服务工作者的fetch事件处理程序中使用这些标头来捕获它是否应该在资源过期/应该刷新时通过网络请求资源。

基本示例:

// ...

try {
  const cachedResponse = await caches.match(event.request);

  if (exists(cachedResponse)) {
    const expiredDate = new Date(cachedResponse.headers.get('Expires'));

    if (expiredDate.toString() !== 'Invalid Date' && new Date() <= expiredDate) {
      return cachedResponse.clone();
    }
  }

  // expired or not in cache, request via network...
} catch (e) {
  // do something...
}
// ...
于 2020-11-17T17:47:23.597 回答
1

对我来说最简单:

const cacheName = 'my-app-v1';

self.addEventListener('activate', async (event) => {

    const existingCaches = await caches.keys();
    const invalidCaches = existingCaches.filter(c => c !== cacheName);
    await Promise.all(invalidCaches.map(ic => caches.delete(ic)));

    // do whatever else you need to...

});

如果您有不止一次的缓存,您可以修改代码以使其具有选择性。

于 2021-01-06T20:33:32.330 回答
0

在我的主页中,我使用一些 PHP 从 mySQL 获取数据。

为了让 php 数据在您有互联网时始终保持新鲜,我使用以毫秒为单位的日期作为我的服务人员的版本。

在这种情况下,当您有互联网并重新加载页面时,兑现页面将始终更新。

//SET VERSION
const version = Date.now();
const staticCacheName = version + 'staticfiles';

//INSTALL
self.addEventListener('install', function(e) {
    e.waitUntil(
        caches.open(staticCacheName).then(function(cache) {
            return cache.addAll([
于 2021-03-24T14:10:46.763 回答