Service Worker 没有在没有尾部斜杠的情况下在主页上缓存资产

Service worker not caching assets on homepage without trailing slash

我正在尝试将服务工作者添加到我们在应用程序下运行的网站的一部分,并且除了没有尾部斜线的主页外,它都在工作 - 它会缓存页面但不会下载任何资产,除非我在 url 中添加一个斜杠(我不能这样做,因为实时环境将其删除)

我已将 header 允许的服务工作者添加到应用程序的站点:

<add name="Service-Worker-Allowed" value="/app-name" />

并更改范围以允许无斜杠 url:

// APP_NAME is global for app-name
navigator
  .serviceWorker
  .register(`/${APP_NAME}/service-worker.js`, { scope: `/${APP_NAME}` });

这有效,因为我可以离线浏览到 http://localhost/app-name,但它没有任何 css、js 或图像。

如果我浏览到 http://localhost/app-name/,我可以看到缓存已更新所有文件。有没有办法让 Service Worker 在下载主页文件时不带斜杠?

服务工作者代码:

(() => {
  'use strict';

  const APP_NAME = 'app-name'; // define this as not compiled with webpack so no global
  const version = 1;
  const cacheName = `${APP_NAME}-cache`
  const cacheNameWithVersion = `${cacheName}-${version}`
  const offlineUrl = `/${APP_NAME}/offline`;
  const urlsToCache = [
    `/${APP_NAME}`,
    offlineUrl,
  ];
  const urlsToIgnore = [
    'google-analytics.com',
    'googletagmanager.com',
  ];

  self.addEventListener('install', event => {
    event.waitUntil(
      caches.open(cacheNameWithVersion)
        .then(cache => {
          return cache.addAll(urlsToCache);
        })
        // Force the waiting service worker to become the active service worker.
        .then(self.skipWaiting())
    );
  });

  self.addEventListener('activate', event => {
    event.waitUntil(
      caches.keys()
        .then(keys => {
          // Remove caches whose name is no longer valid
          return Promise.all(keys
            .filter(key => {
              return key.indexOf(cacheName) === 0 && key.indexOf(cacheNameWithVersion) === -1;
            })
            .map(key => {
              return caches.delete(key);
            })
          );
        })
    );
  });

  self.addEventListener('fetch', event => {
    // don't load assets from these urls
    for (let i = 0; i < urlsToIgnore.length; i++) {
      if (event.request.url.indexOf(urlsToIgnore[i]) > -1) {
        return;
      }
    }

    if (event.request.method !== 'GET') {
      // Always fetch non-GET requests from the network
      event.respondWith(
        fetch(event.request)
          .catch(() => {
            return caches.match(offlineUrl);
          })
      );

      return;
    }

    // Only fetch non-GET requests offline
    if (event.request.url.match(/\.(jpe?g|png|gif|svg)$/)) {
      // handle images or show offline svg if image isn't cached
      event.respondWith(
        caches.match(event.request)
          .then(response => {
            return response || fetch(event.request)
              .then(response => {
                addToCache(event.request, response);
                return response || serveOfflineImage();
              })
              .catch(() => {
                return serveOfflineImage();
              });
          })
      );
    } else if (event.request.mode === 'navigate' || event.request.method === 'GET') {
      // handle pages and assets like js and css
      event.respondWith(
        fetch(event.request)
          .then(response => {
            // Stash a copy of this page in the cache
            addToCache(event.request, response);
            return response;
          })
          .catch(() => {
            return caches.match(event.request)
              .then(response => {
                return response || caches.match(offlineUrl);
              });
          })
      );
    }
  });

  function addToCache(request, response) {
    if (!response.ok && response.type !== 'opaque')
      return;

    const copy = response.clone();
    caches.open(cacheNameWithVersion)
      .then(cache => {
        if (!/^https?:$/i.test(new URL(request.url).protocol)) return;
        cache.put(request, copy);
      });
  }

  function serveOfflineImage() {
    return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" class="object-fit"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Roboto,Arial,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { headers: { 'Content-Type': 'image/svg+xml' } });
  }
})();

看起来您总是需要刷新注册服务工作者的页面才能下载该页面上的资产:

Service worker 现在将控制页面,但仅限于在 register() 成功后打开的页面。即文档在有或没有 Service worker 的情况下开始存在,并在其生命周期内保持该状态。因此必须重新加载文件才能真正控制。

Above text is point 7

我在activate函数中添加了self.clients.claim(),它允许service worker直接控制注册页面,这允许缓存一些延迟加载或在service worker之后加载的资产没有刷新,但其余仅在刷新后缓存

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys()
    .then(keys => {
      // Remove caches whose name is no longer valid
      return Promise.all(keys
        .filter(key => {
          return key.indexOf(cacheName) === 0 && key.indexOf(cacheNameWithVersion) === -1;
        })
        .map(key => {
          return caches.delete(key);
        })
      );
    })
    // Retrieve assets on page that registered service worker
    .then(self.clients.claim())
  );
});