-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Service workers #10
Comments
I sent an RFC to remove them, so my thoughts probably aren't a secret... 😄 But @pngwn suggested it's just the current template implementation that is poor and not service workers in general, so I'm hoping he can show us what a good one looks like or ideally update the one in the template to be a bit better default |
yeah, i think it's important that they are supported in some form, since they do enable useful features (add to home screen, offline support, etc) |
Users should always be able to add service workers to an app. I guess my question is whether things like add to home screen make sense in the default template. I'm not sure it's a representative of the types of web apps that are most commonly built |
As with other things like TypeScript support, this could be solved by having some kind of Wizard which is run when doing |
Something to consider: https://twitter.com/jeffposnick/status/1324373178043584514 It adds non-trivial complexity — e.g. it means that SSR code needs to be loaded (and cached) in the client |
Ok, been tinkering away for a bit and I have a proposal. First, some observations:
So I propose that we add an optional prod-mode-only service worker wrapper that, if present (i.e., if First stab at a default service worker: import { timestamp, files } from '$app/meta';
const app_name = `app-${timestamp}`;
const fallback_name = `fallback-${timestamp}`;
export async function install(event) {
const app = await caches.open(app_name);
app.addAll(files.filter((file) => file.immutable).map((file) => file.path));
const fallback = await caches.open(fallback_name);
fallback.addAll(files.filter((file) => !file.immutable).map((file) => file.path));
}
export async function activate(event) {
for (const key of await caches.keys()) {
if (key !== app_name && key !== fallback_name) {
await caches.delete(key);
}
}
}
export async function fetch({ request }) {
if (request.method !== 'GET' || request.headers.has('range')) return;
const url = new URL(request.url);
if (url.protocol === 'https:') {
// always serve immutable app files from service worker cache
const app = await caches.open(app_name);
let response = await app.match(request);
if (!response) {
const promise = fetch(request);
try {
// for everything else, try the network first...
response = await promise;
if (response.ok && response.type === 'basic') {
const fallback = await caches.open(fallback_name);
fallback.put(request, response.clone());
}
} catch {
// ...then fall back to the cache...
response = await caches.match(request);
}
// ...or return the failed fetch
return response || promise;
}
}
}
[
// built app files
{
path: '/path/to/_app/some-js-xyz123.js',
type: 'application/javascript',
size: 123,
immutable: true
},
...
// stuff in `static`
{
path: '/path/to/potato.jpg',
type: 'image/jpeg',
size: 789,
immutable: false
},
...
] Eager to hear thoughts on all of the above 🙏 |
export async function fetch({ request }) {
// ...
const promise = fetch(request); Wouldn't this just recursively call itself, since we've shadowed the |
hah good catch! this is what happens when you try to do this at 1am. two solutions spring to mind: export async function onInstall(event) {...}
export async function onActivate(event) {...}
export async function onFetch(event) {...} ( Alternatively: export default {
install: event => {...},
activate: event => {...},
fetch: event => {...}
}; I much prefer the first — it's more amenable to static analysis, and better matches things like Worth noting that by the very nature of how this would work (i.e. the Kit service worker runtime importing the user's module), the user could also do things the old fashioned way with |
I think |
Recapping discussion with @lukeed and @pngwn:
If the default is to only cache the app files on installation, it probably makes sense to separate them out from the contents of
import { timestamp, build, assets, onInstall, onActivate, onFetch } from '$service-worker';
const name = `cache-${timestamp}`;
onInstall(async () => {
const cache = await caches.open(name);
cache.addAll(build);
});
onActivate(async () => {
for (const key of await caches.keys()) {
if (!key.includes(timestamp)) caches.delete(key);
}
});
onFetch(async ({ request }) => {
if (request.method !== 'GET' || request.headers.has('range')) return;
const cached = await caches.match(request);
if (build.includes(request.url)) {
return cached;
}
if (url.protocol === 'https:') {
const promise = fetch(request);
try {
// try the network first...
const response = await promise;
if (response.ok && response.type === 'basic') {
const fallback = await caches.open(name);
fallback.put(request, response.clone());
}
return response;
} catch {
// ...then fallback to cached response,
// or return the failed fetch
return cached || promise;
}
}
}); |
Somewhat related thought - if the user having to export functions is magical, wouldn't that also apply to server routes (exporting |
I had no problem with the Would prefer exported functions of the two. |
I guess one distinction between these and Maybe I am overindexing on aesthetics though, and we should just make people use |
* lay groundwork for #10 * add missing setup/session test * implement service worker * update sample service worker * allow service worker to work with paths.base * changed mind about onFetch etc * remove unused code * make client entry points explicit * huh is this test flaky? * changeset
everyone's favourite topic
The text was updated successfully, but these errors were encountered: