Skip to main content

Using the Serwist API

Introduction

“A progressive web app (PWA) is an app that’s built using web platform technologies, but that provides a user experience like that of a platform-specific app. Like a website, a PWA can run on multiple platforms and devices from a single codebase. Like a platform-specific app, it can be installed on the device, can operate while offline and in the background, and can integrate with the device and with other installed apps” - MDN Web Docs (source)

A service worker is what give a PWA features found in platform-specific apps, such as pushing notifications, operating without network connectivity, deferring tasks when users are offline, etc. (more on What PWA Can Do Today). The native Service Worker API, unfortunately, requires developers to spend much effort to get started and maintain, and that is where Serwist shines: it makes creating service workers a breeze for everyone, with it requiring as few as ten lines of code to initialize a project and still being efficient.

Basic usage

To get started with Serwist, you only need as much code as the following snippet:

// Where you import this depends on your stack.
import { defaultCache } from "@serwist/vite/worker";
import { type PrecacheEntry, Serwist } from "serwist";

declare global {
  interface WorkerGlobalScope {
    __SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
  }
}

declare const self: ServiceWorkerGlobalScope;

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  runtimeCaching: defaultCache,
});

serwist.addEventListeners();

Customizing the behaviour

However, in most cases, the defaults probably do not suit you. For example, you may want Serwist to precache some URLs, sometimes concurrently as well; clean up outdated caches; or claim all available clients on activation. The Serwist class provides a set of options for you to configure its behaviour.

const serwist = new Serwist({
  // A list of URLs that should be cached. Usually, you don't generate
  // this list yourself; rather, you'd rely on a Serwist build tool/your framework
  // to do it for you. In this example, it is generated by `@serwist/vite`.
  precacheEntries: self.__SW_MANIFEST,
  // Options to customize how Serwist precaches the URLs.
  precacheOptions: {
    // Whether outdated caches should be removed.
    cleanupOutdatedCaches: true,
    concurrency: 10,
    ignoreURLParametersMatching: [],
  },
  // Whether the service worker should skip waiting and become the active one.
  skipWaiting: true,
  // Whether the service worker should claim any currently available clients.
  clientsClaim: true,
  // Whether navigation preloading should be used.
  navigationPreload: false,
  // Whether Serwist should log in development mode.
  disableDevLogs: true,
  // A list of runtime caching entries. When a request is made and its URL match
  // any of the entries, the response to it will be cached according to the matching
  // entry's `handler`. This does not apply to precached URLs.
  runtimeCaching: defaultCache,
  // Other options...
  // See https://serwist.pages.dev/docs/serwist/core/serwist
});

serwist.addEventListeners();

Advanced usage

Interoperating with the Service Worker API

The Serwist API is designed so that you can use it with the native API anytime you want. For example, to add your own event listeners:

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  skipWaiting: false,
  clientsClaim: false,
});

self.skipWaiting();

self.addEventListener("install", serwist.handleInstall);

self.addEventListener("activate", (event) => {
  self.clients.claim();
  serwist.handleActivate(event);
});

self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (url.origin === location.origin && url.pathname === "/") {
    const cacheKey = serwist.getPrecacheKeyForUrl("/legacy/index.html");
    if (cacheKey !== undefined) {
      event.respondWith(
        (async () => {
          const cachedResponse = await caches.match(cacheKey);
          if (cachedResponse !== undefined) {
            return cachedResponse;
          }
          return Response.error();
        })(),
      );
    }
  }
  serwist.handleFetch(event);
});

self.addEventListener("message", serwist.handleCache);

Dynamically registering a Route

You can also dynamically register a Route after initializing Serwist:

// Again, where you import this depends on your stack.
import { defaultCache } from "@serwist/vite/worker";
import { NetworkOnly, RegExpRoute } from "serwist";

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  // Side note: `runtimeCaching` is just a syntactic sugar
  // for `registerRoute`.
  runtimeCaching: defaultCache,
});

const routes = {
  alwaysOnline: new RegExpRoute(/\/always-online\/.*/, new NetworkOnly()),
};

type RouteKey = keyof typeof routes;

// Note: this is type faith. Please check if `routeKey` is
// actually of type `RouteKey`.
type MyCustomMessage =
  | {
      message: "UNREGISTER_ROUTE";
      routeKey: RouteKey;
    }
  | {
      message: "REGISTER_ROUTE";
      routeKey: RouteKey;
    };

self.addEventListener("message", (event) => {
  if (event.data && event.data.type === "MY_CUSTOM_EVENT") {
    const data: MyCustomMessage = event.data.myCustomMessage;
    switch (data.message) {
      case "UNREGISTER_ROUTE":
        serwist.unregisterRoute(routes[data.routeKey]);
        break;
      case "REGISTER_ROUTE":
        serwist.registerRoute(routes[data.routeKey]);
        break;
      default:
        throw new Error("Message not valid.");
    }
  }
});

serwist.addEventListeners();

More resources

Here is a list of resources you can read to learn more about the core of Serwist: