Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active November 5, 2024 06:39
Show Gist options
  • Save Rich-Harris/fd6c3c73e6e707e312d7c5d7d0f3b2f9 to your computer and use it in GitHub Desktop.
Save Rich-Harris/fd6c3c73e6e707e312d7c5d7d0f3b2f9 to your computer and use it in GitHub Desktop.
Stuff I wish I'd known sooner about service workers

Stuff I wish I'd known sooner about service workers

I recently had several days of extremely frustrating experiences with service workers. Here are a few things I've since learned which would have made my life much easier but which isn't particularly obvious from most of the blog posts and videos I've seen.

I'll add to this list over time – suggested additions welcome in the comments or via twitter.com/rich_harris.

Use Canary for development instead of Chrome stable

Chrome 51 has some pretty wild behaviour related to console.log in service workers. Canary doesn't, and it has a load of really good service worker related stuff in devtools.

^ no longer necessary

Reloading the page doesn't behave as you'd expect

If you make a change to your service worker, then reloading the page won't kill the old one and activate the new one (it detects the change by requesting the service worker file each time and comparing the old and the new byte-for-byte), leaving you somewhat confused as to why your changes haven't taken effect. This is because the old window is never actually closed, meaning there's never a time to swap them out – you need to kill the tab completely then reopen it.

Or you can have the browser do that for you by going to the Application tab in devtools (in Canary, not stable Chrome yet), going to the Service Workers section, and checking 'Update on reload'.

The new service worker isn't fetched by the old one

For a while I thought that maybe the reason my changes weren't taking effect was because the old service worker was serving a cached version of itself, because it was intercepting all requests and caching them. So I was doing all sorts of daft stuff like registering service-worker.js?${Math.random()} in an attempt to 'fix' it.

Turns out that when you call navigator.serviceWorker.register('service-worker.js) the request for service-worker.js isn't intercepted by any service worker's fetch event handler.

navigator.serviceWorker.controller is the service worker that intercepts fetch requests

The first time you load a page, navigator.serviceWorker.controller === null. This continues to be true after a service worker has been successfully registered and installed.

(The relationship between navigator.serviceWorker.controller and the service workers you can get via navigator.serviceWorker.getRegistration() continues to be slightly confusing to me – particularly when it comes to knowing which service worker you're supposed to send messages to if you need to send messages. And don't get me started on self.skipWaiting() and self.clients.claim(), which on the face of it seem like a recipe for chaos. Though I'm sure it's just a matter of understanding when to use them.)

You can access self.caches in the browser as window.caches

If you're using service workers you're probably caching the resources that make up your app shell. Perhaps, like me, you want to give the user to separately download content, e.g. fetch large files while they're on WiFi so they don't chew through their data plan. If so, you probably want some kind of progress notification.

My first instinct was to have the service worker do all the background caching (and checking of which files are already cached, importantly) and broadcast messages to connected clients. That sucks because service worker messaging sucks. But it turns out it's not necessary, because you can do it directly in the client:

document.querySelector( '.fetch-content' ).addEventListener( 'click', () => {
  window.caches.open( myCache )
    .then( cache => cache.addAll( content ) )
    .then( () => alert( 'content is now available offline' ) )
    .catch( () => alert( 'oh noes! something went wrong' ) );
});

(Obviously you'd probably want a more granular strategy that made it possible to report download progress, but you get the idea.)

You're not alone

A lot of people have experienced the same frustrations you have and know how to fix them. In particular, Jake and other folks at Google and Mozilla involved in implementing this stuff are unreasonably helpful if you reach out to them (not that I encourage you to spam their mentions every time you get stuck, but if you really need help...).

I still think the API is a bit lumbering in several places (if the JS convention for the naming relationship between instances and classes is foo = new Foo(), why is navigator.serviceWorker an instance of ServiceWorkerContainer while navigator.serviceWorker.controller is an instance of ServiceWorker? And what the hell is a ServiceWorkerRegistration? Never mind all the many other interfaces we now need to learn), and I'm worried about how learnable this stuff is by people who don't have the time and inclination to study hard, but at the very least knowing the stuff above has made my experience with service workers much nicer.

Stuff I still don't understand

  • If I have code in my service worker that runs outside an event handler, when does it run?
  • Probably some other things I've forgotten for now
@m0ntana
Copy link

m0ntana commented Dec 22, 2023

You spent over an hour?
I spent around 4 hours trying to find this out. Thank you, this saved my day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment