function onFetch(event) {
  // For some reason, on a page load, we get hash parameters in the URL for this
  // event.  The hash should not be used when we do any of the lookups below.
  const url = event.request.url.split('#')[0];

  // Make sure this is a request we should be handling in the first place.
  // If it's not, it's important to leave it alone and not call respondWith.
  let useCache = false;
  for (const prefix of CACHEABLE_URL_PREFIXES) {
    if (url.startsWith(prefix)) {
      useCache = true;
      break;
    }
  }

  // Now we need to check our resource lists.  The list of prefixes above won't
  // cover everything that was installed initially, and those things still need
  // to be read from cache.  So we check if this request URL matches one of
  // those lists.
  if (!useCache) {
    if (CRITICAL_RESOURCES.includes(url) ||
        OPTIONAL_RESOURCES.includes(url)) {
      useCache = true;
    }
  }

  if (!useCache && url.startsWith(LOCAL_BASE)) {
    // If we have the correct resource lists above, then all local resources
    // should have useCache set to true by now.

    // Check to see if this request is coming from a compiled build of the demo,
    // and only log an error if this missing request is from a compiled build.

    // The check is async, and that's fine because we aren't handling this fetch
    // event anyway.
    (async () => {
      // This client represents the tab that made the request.
      const client = await clients.get(event.clientId);
      // This is the URL of that tab's main document.
      const urlHash = client.url.split('#')[1] || '';
      const hashParameters = urlHash.split(';');
      if (hashParameters.includes('build=compiled')) {
        console.error('Local resource missing!', url);
      }
    })();
  }

  if (useCache) {
    event.respondWith(fetchCacheableResource(event.request));
  }
}
This event fires when any resource is fetched.
This is where we can use the cache to respond offline.

@param {!FetchEvent} event