Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update service-worker example to be type-safe when in ts + handle offline better #10617

Merged
merged 2 commits into from
Dec 13, 2023

Conversation

quentin-fox
Copy link
Contributor

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

When introducing a service-worker into my SvelteKit application, I encountered some issues after copying the code from the docs at https://kit.svelte.dev/docs/service-workers#inside-the-service-worker.

  1. When adding the code into a service-worker.ts file, with the recommended triple-slash directives the code was not entirely type-safe. The main issue was that the respond function could return Promise<Response | undefined> instead of Promise<Response>, which conflicts with the argument type of event.respondWith. See: https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith#syntax. The example is updated so that if the cache.match returns undefined, then it does not return that undefined value, and instead continues on to try and make the fetch. In the case there is some error thrown by fetch, then it will only return the response from cache.match if it is defined - otherwise, it will just re-throw the error.
  2. When running the service-worker in an offline mode, at least on Firefox, the fetch function did not throw - instead, it returned an NS_OK: object... this seems more like browser internals, and isn't well-documented, but passing this object back to event.respondWith triggered a bunch of errors, instead of hitting the cache logic. To handle this case, the response from fetch is only returned if it's an instanceof Request. Otherwise, it throws an error, and goes to the catch block.

If necessary, I can provide a sample project with the previous service-worker and this service-worker to show that the existing sample does not actually work in an offline context, and with the fixes provided in this PR, it does provide the offline caching behaviour as described. Hopefully the changes make sense in isolation though, and I have tested them extensively on my own application.

For reference, this is the service-worker.ts file I have in my own application which does work - this was the basis for the documentation changes in this PR:

/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

import { build, files, version } from '$service-worker';

// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;

const ASSETS = [
  ...build, // the app itself
  ...files, // everything in `static`
];

const sw = self as unknown as ServiceWorkerGlobalScope;

sw.addEventListener('install', event => {
  // Create a new cache and add all files to it
  async function addFilesToCache() {
    const cache = await caches.open(CACHE);
    await cache.addAll(ASSETS);
  }

  event.waitUntil(addFilesToCache());
});

sw.addEventListener('activate', event => {
  // Remove previous cached data from disk
  async function deleteOldCaches() {
    for (const key of await caches.keys()) {
      if (key !== CACHE) await caches.delete(key);
    }
  }

  event.waitUntil(deleteOldCaches());
});

// this callback has to be synchronous, so that we know if we let the service worker handle it or not
// by if the event.repsondWith was ever called after synchonously calling this event listener
// if cache misses, and there is an http error, then the fetch will fail with the http error
sw.addEventListener('fetch', event => {
  // ignore POST requests etc
  if (event.request.method !== 'GET') {
    return;
  }

  async function respond() {
    const url = new URL(event.request.url);
    const cache = await caches.open(CACHE);

    // `build`/`files` can always be served from the cache
    if (ASSETS.includes(url.pathname)) {
      const response = await cache.match(url.pathname);

      if (response) {
        return response;
      }
    }

    // for everything else, try the network first, but
    // fall back to the cache if we're offline
    try {
      const response = await fetch(event.request);

      // sometimes response isn't of type Response
      // e.g. when offline
      // need to catch those so we don't return the non-responses
      // to the service worker via respondWith
      if (response instanceof Response === false) {
        throw new Error('invalid response from fetch');
      }

      if (response.status === 200) {
        cache.put(event.request, response.clone());
      }

      return response;
    } catch (err) {
      const response = await cache.match(event.request);

      if (response) {
        return response;
      }

      // if no cache found, then just error out
      // as there's nothing we can do anyways
      throw err;
    }
  }

  event.respondWith(respond());
});

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

@changeset-bot
Copy link

changeset-bot bot commented Aug 24, 2023

⚠️ No Changeset found

Latest commit: d3b972a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Member

@dummdidumm dummdidumm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

documentation/docs/30-advanced/40-service-workers.md Outdated Show resolved Hide resolved
@dummdidumm dummdidumm merged commit d67425c into sveltejs:master Dec 13, 2023
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants