Skip to content

Add respond function to allow for SSR in Service Workers. #5690

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

Closed
wants to merge 0 commits into from
Closed

Add respond function to allow for SSR in Service Workers. #5690

wants to merge 0 commits into from

Conversation

thomasfosterau
Copy link

@thomasfosterau thomasfosterau commented Jul 24, 2022

This adds a respond function to $service-worker, which takes a Request and returns a Promise for a Response (thereby closing #3498). To make it easier to write Service Worker specific code within endpoints, layouts and pages, it also adds serviceWorker to $app/env, so endpoints, layouts and pages know if they’re being run in a Service Worker (analogous to the existing browser export).

As outlined in #3498, a current pain point with using SvelteKit with Service Workers is that it’s difficult (and often impossible) to handle browser requests to pages which haven’t been either prerendered or manually cached without making a network request. Having a respond function available makes it possible to run more complex SvelteKit apps offline. Service Workers also have access to the IndexedDB API, so it would be possible for endpoints to share a cache with client-side libraries (e.g., Houdini).

Here's a minimal example of using the respond function:

import { respond } from "$service-worker";

// "install" and "activate" event handlers and logic go here...

self.addEventListener("fetch", (event) => event.respondWith(handle_fetch(event)));

async function handle_fetch(event) {

  const cached = await self.caches.match(event.request);
  if (cached) return cached;

  return await respond(event.request);
}

I’m unfamiliar with the SvelteKit internals so I might have missed some easier ways to do things (e.g., I’m not sure how much output can be shared between the server and service worker builds), and I’m not sure how to approach writing tests for service workers and expanding the documentation — I’m happy to do it but I would appreciate some pointers or help!

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.

Tests

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

When running tests, I’m getting intermittent problems with tests that use node:crypto to generate random bytes. I think it’s because they are tests which generate streaming data in an endpoint and have been written to be Node-specific — is this an issue with the Vite config for the Service Worker build, or do the tests need to be rewritten?

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. All changesets should be patch until SvelteKit 1.0

@changeset-bot
Copy link

changeset-bot bot commented Jul 24, 2022

⚠️ No Changeset found

Latest commit: 8748873

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

@Rich-Harris
Copy link
Member

Thank you! Responding directly from the service worker is definitely on the agenda.

Just to manage expectations though: it's very likely that we'll need to spend some time whiteboarding some stuff before we can realistically merge this. The majority of apps need to get at least some of their data from a server, which means at least some pages/endpoints simply can't run in a service worker.

Currently in this PR the service worker sees the entire server-side route manifest. Ideally there'd be some way to annotate each function with information about where it can run, which would allow the service worker to handle certain requests internally, but defer to the server for other cases. This is actually a more general problem than it sounds — the same thing applies to some adapters (for example if you're using Vercel, you might want some of your functions to run on the edge, while others run in iad1 next to your data; some of those might be able to run on the fast-booting edge runtime, while others require Node and must therefore run on Lambda), and to whether or not certain functions are only suitable for use in prerendering.

Quite how we do all that annotation in a way that is consistent/understandable/future-proof etc is very much up in the air. For now our efforts are focused on implementing #5748, which means there's limited scope to make design changes to endpoints for the immediate future, but this is definitely something worth revisiting soon.

@thomasfosterau
Copy link
Author

@Rich-Harris thanks for taking a look at this and glad to hear it’s on the agenda! Having a look at #5748, it seems to me that any way of doing SSR in a service worker after those changes might need to start from scratch, so I’m happy to close this PR (or for it to be closed) unless it’s of any value left as a draft or turned into some sort of RFC or discussion.

The majority of apps need to get at least some of their data from a server, which means at least some pages/endpoints simply can't run in a service worker.

Currently in this PR the service worker sees the entire server-side route manifest. Ideally there'd be some way to annotate each function with information about where it can run, which would allow the service worker to handle certain requests internally, but defer to the server for other cases. This is actually a more general problem than it sounds — the same thing applies to some adapters [...], and to whether or not certain functions are only suitable for use in prerendering.

Quite how we do all that annotation in a way that is consistent/understandable/future-proof etc is very much up in the air.

I would argue that while the problem is quite general, the ideal solution for service workers probably does need to be somewhat unique because service workers live in a murky world where they’re both client and server. There’s some valid situations where both the in-page client code and server-side code (e.g., in the case of navigation preloads) could ideally behave differently if there’s a service worker available, but I feel like that’s different to anything an adapter-specific solution is going to do; the former needs some run-time support, the latter is probably all build-time.

For this PR, I added serviceWorker to $app/env because that seems to me to solve a large-ish subset of use cases: e.g., doing a bunch of data validation (early errors are dealt with by the service worker), but calling fetch rather than actually accessing a database if the endpoint is being called within a service worker. Obviously that doesn’t solve every situation perfectly... but it seems to me that the combination of a build-time boolean and if-statements is at least part of a good solution to the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
service worker Stuff related to service workers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants