A Bit of a Vision for Service Workers in SvelteKit #13784
thomasfosterau
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I think it’s fair to say that service workers are a bit of an afterthought in SvelteKit at the moment (I think this is true for just about every other web framework as well). While there’s a
$service-worker
module available, it really just provides arrays of URLs to files which can be cached within the service worker. For anything more advanced, the SvelteKit docs for service workers suggest using the Vite PWA plugin and Workbox, but fundamentally those two tools just provide a declarative framework for caching.I think SvelteKit can and should have a much better story for using service workers. Service workers aren’t a fancy programmable cache or proxy — they’re an embedded web server in the browser. There is virtually no functionality that SvelteKit needs to serve requests and render pages that service workers don’t provide (after all, Cloudflare Workers are based on service workers). In my very humble opinion, a great service worker story should be table stakes for anything wanting to be a web framework.
While there are some old issues about adding better service worker support to SvelteKit (#10, #299, #3498, etc.), as well as a few wanting less support (!), I think it would be great to sketch out a bit of a vision for service workers beyond what fits in a normal issue.
In my ideal world, here’s some of the functionality which would be provided out of the box by SvelteKit:
I’m not going to pretend like this is all easy though! Service workers have some features and behaviours which aren’t always intuitive. Some of the footguns which still exist:
event.waitUntil
andevent.respondWith
exist).import
s (for good reasons... but still a footgun).import
statements in service workers.Feature 1: Service Worker Rendering
As an initial step, something along the lines of a
respond(event: FetchEvent): Promise<Response>
function should be exported by$service-worker
. This function would make the following very basic service worker work:This functionality was first requested in #3498. The ability to render dynamic pages in a service worker is absolutely essential for any non-trivial offline-first web app, because it removes the requirement for the user to initially open the page while online (which SvelteKit currently has). It also has great benefits for cache use: instead of naïvely caching whole pages, the service worker could instead only cache the necessary data and instead generate pages dynamically.
Conceptually, this could probably be implemented as an internal adapter which uses a secondary server build which excludes
+server.js
,+page.server.js
,+layout.server.js
, and any other server-only modules. I think the biggest complexity of implementing this is deciding how to deduplicate work done to generate the client and server bundles — this probably needs input core contributors to make sure a poor decision isn’t made.Feature 2: Service Worker Endpoints
This feature would allow requests which would otherwise be handled by a
+server.js
,+page.server.js
or+layout.server.js
to instead be handled by a+worker.js
,+page.worker.js
, or+layout.worker.js
file when in a service worker (whether the pattern isworker
orsw
or whatever is bikesheddable). These endpoints would be able to attempt to respond to a request (e.g., a form submission or API call) using data stored in the browser (e.g., IndexedDB). These endpoints would be given aserver(): Promise<Response>
(or equivalent) function to fallback to the+server.js
or equivalent.Here’s a very rudimentary example of what a
+worker.js
file might look like:The benefit this provides is that the vast majority of fetching/caching/whatever logic in
+page.svelte
and+page.js
can instead be implemented within the service worker, leaving+page.svelte
or+page.js
to simply do some naïve fetching. This would particularly beneficial if asynchronous Svelte gets merged: some sort of naïve fetching can be done in components themselves, and the service worker takes care of all the complicated fetching/caching/retry/whatever logic instead.A more ambitious use case would be for the service worker to be able to handle form submissions (and POST, PATCH and DELETE requests) and handle them when the browser is offline by applying the changes to a local copy of data and then syncing the changes with the server later. Moving all of this logic into the service worker removes a lot of the complexity which comes from needing to coordinate these changes across tabs, because only one service worker will ever be handling these changes.
Feature 3: Service Worker Hooks (and Default Service Worker)
Finally, another beneficial feature would be a
hooks.worker.js
(again, name is bikesheddable) which includeshandleInstall
,handleActivate
, and ahandle
hooks at a minimum. Using these hooks, SvelteKit could likely generate a service worker is addresses the vast majority of typical use cases and handle the typical boilerplate needed for a service worker (event.waitUntil
,event.respondWith
, etc.).The
handleInstall
hook would correspond to theinstall
event on the service worker. Like the existinghandle
server hook, thehandleInstall
hook would be passed both the install event itself as well as aninstall
function. When called, theinstall
function would add all of the assets inbuild
andfiles
to a named cache (calledcache-${version}
or something). In the absence of ahandleInstall
hook, the default functionality would be for theinstall
function to be called.The
handleActivate
would likewise correspond to theactivate
event. The default behaviour here is more complicated — should the user have to implementhandleActivate
to avoid caches other thancache-${version}
getting deleted?The
handle
hook would correspond to thefetch
event handler. Like thehandle
server hook, it would get both theevent
and therespond
function.Other service worker APIs expose other events which SvelteKit could also provide hooks for:
sync
,periodicsync
,push
, etc.The degree to which the service worker and server can share hooks is unclear to me — probably the best option would be to
Addendum: Nice-to-Haves
$app/service-worker
module to replace$service-worker
to make them feel like a first-class citizen.serviceWorker
orservice_worker
(orregistration
?) state object gets added to$app/state
, which isnull
on the server and initially in the browser, and is updated to the currentServiceWorkerRegistration
in the browser when that is available.serviceWorker
orservice_worker
flag gets added to$app/environment
.Beta Was this translation helpful? Give feedback.
All reactions