【问题标题】:Service-worker force update of new assetsService-worker 更新新资产
【发布时间】:2015-10-21 14:43:27
【问题描述】:

我一直在阅读html5rocks Introduction to service worker 文章,并创建了一个基本的服务工作者,它可以缓存页面、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 进行更改时,由于 service worker 正确地从缓存中返回了 CSS,因此该更改没有被采纳。

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

【问题讨论】:

    标签: service-worker


    【解决方案1】:

    一种选择是使用服务工作者的缓存作为备用,并始终尝试通过fetch() 转到network-first。但是,您会失去一些缓存优先策略提供的性能提升。

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

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

    实际上,您最终会得到一个使用性能友好的缓存优先策略的服务工作者,但缓存会在页面加载后“在后台”更新,以便下次访问它时,一切很新鲜。如果您愿意,可以向用户发送possible to display a message,让他们知道有可用的更新内容并提示他们重新加载。

    【讨论】:

    • 感谢您的回答。阅读了 Jake 的那篇博客文章后,我可以看到您可以在不同的情况下使用几种模式。我将决定我真正想做的事情并尝试使用其中一种模式来实现。
    • 我正在使用你提到的 sw-precache,但是我更新的内容没有在缓存中被替换,所以如果我用 ctrl+r 刷新它会得到一个旧版本,但如果我刷新它会得到新版本使用 ctrl+shift+r。一个原因可能是我的服务人员没有更新。 developers.google.com/web/fundamentals/getting-started/primers/… 表示如果更改了一个字节,它就会更新,但是如果我的新服务人员没有更改一个字节怎么办? (如果只有一个哈希发生了变化,就会发生这种情况)。如何强制更新服务工作?
    • 查看stackoverflow.com/questions/38843970/… 并确认您更新的service-worker.js 文件是从网络中获取的,而不是从浏览器缓存中获取的。在生成的service-worker.js 中对单个文件的哈希值进行任何更改都应该足以触发更新流程。
    • 如果你没有使用 Node,并且不能使用 sw-precache/Workbox,我写了一篇关于使用类似方法的详细帖​​子(在我的例子中使用运行在 Python 中的服务器):almarklein.org/pwa.html
    • 我使用 git 部署到 gh 页面,因此我使用 git diff --diff-filter=ACRM HEAD $lastRemoteCommit -- docs 建立要更新的文件列表,其中 lastRemoteCommit 是使用 git ls-files | grep refs/heads/main | awk '{ print $1 }' 获得的,这是从内存中写出来的,但你得到了精神。不过,仍在解决其他一些问题:)
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的回答。有意义的是,当新的 service-worker 被激活时,它将清除缓存并从网络重新加载资产。提到版本戳的原因是我认为这可能是一个潜在的解决方案。
    • 这种方法不会重新下载属于缓存的所有资源,而不是只下载更新的文件吗?使用相同的缓存名称但使用哈希更新文件名可以解决此问题。
    【解决方案3】:

    浏览器缓存问题

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

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

    要知道的一点是,每次您的代码脚本发生更改时,Service Worker 都会触发 install 事件,因此您无需使用版本标记或其他任何东西,只需保持相同的文件名没关系,甚至推荐。 There are other ways the browser will consider your service worker updated.

    1.重写你的 install 事件处理程序:

    我不使用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.处理缓存中的Expires 和Cache-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...
    }
    // ...
    

    【讨论】:

    • 为什么不总是使用相同的缓存名称,比如const cacheName = 'my-pwa.io',并在安装事件处理程序中使用caches.delete(cacheName)?那应该避免用旧数据填充新缓存吗?
    【解决方案4】:

    对我来说最简单:

    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...
    
    });
    

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

    【讨论】:

      【解决方案5】:

      在我的主页中,我使用一些 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([
      

      【讨论】:

      • 这不会因为版本每秒都在变化而不缓存文件吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      • 2023-04-07
      • 2018-06-27
      • 2020-06-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多