Skip to content

Commit

Permalink
dev time service workers - closes #5479
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Nov 10, 2022
1 parent 8c480f6 commit 2be6fa6
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-dragons-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Run service worker during development
10 changes: 9 additions & 1 deletion documentation/docs/30-advanced/40-service-workers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker
Inside the service worker you have access to the [`$service-worker` module](/docs/modules#$service-worker). If your Vite config specifies `define`, this will be applied to service workers as well as your server/client builds.

Because it needs to be bundled (since browsers don't yet support `import` in this context), and depends on the client-side app's build manifest, **service workers only work in the production build, not in development**. To test it locally, use `vite preview`.
The service worker is bundled for production, but not during development. For that reason, only browsers that support [modules in service workers](https://web.dev/es-modules-in-sw) will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the `{ type: 'module' }` option in development:

```js
import { dev } from '$app/environment';

navigator.serviceWorker.register('/service-worker.js', {
type: dev ? 'module' : 'classic'
});
```
2 changes: 1 addition & 1 deletion packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function create_manifest_data({
/**
* @param {import('types').ValidatedConfig} config
*/
function create_assets(config) {
export function create_assets(config) {
return list_files(config.kit.files.assets).map((file) => ({
file,
size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
Expand Down
20 changes: 19 additions & 1 deletion packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,22 @@ export async function dev(vite, vite_config, svelte_config) {
);
}

if (decoded === '/service-worker.js') {
const resolved = resolve_entry(svelte_config.kit.files.serviceWorker);

if (resolved) {
res.writeHead(200, {
'content-type': 'application/javascript'
});
res.end(`import '${resolved}';`);
} else {
res.writeHead(404);
res.end('not found');
}

return;
}

const hooks_file = svelte_config.kit.files.hooks.server;
/** @type {Partial<import('types').ServerHooks>} */
const user_hooks = resolve_entry(hooks_file)
Expand Down Expand Up @@ -451,7 +467,9 @@ export async function dev(vite, vite_config, svelte_config) {
.replace(/%sveltekit\.status%/g, String(status))
.replace(/%sveltekit\.error\.message%/g, message);
},
service_worker: false,
service_worker:
svelte_config.kit.serviceWorker.register &&
!!resolve_entry(svelte_config.kit.files.serviceWorker),
trailing_slash: svelte_config.kit.trailingSlash
},
{
Expand Down
24 changes: 23 additions & 1 deletion packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { get_config_aliases, get_app_aliases, get_env } from './utils.js';
import { fileURLToPath } from 'node:url';
import { create_static_module, create_dynamic_module } from '../../core/env.js';
import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
import { create_assets } from '../../core/sync/create_manifest_data/index.js';

const cwd = process.cwd();

Expand Down Expand Up @@ -289,7 +290,7 @@ function kit() {

async resolveId(id) {
// treat $env/static/[public|private] as virtual
if (id.startsWith('$env/')) return `\0${id}`;
if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`;
},

async load(id, options) {
Expand Down Expand Up @@ -323,6 +324,8 @@ function kit() {
'public',
vite_config_env.command === 'serve' ? env.public : undefined
);
case '\0$service-worker':
return create_service_worker_module(svelte_config);
}
},

Expand Down Expand Up @@ -627,3 +630,22 @@ function find_overridden_config(config, resolved_config, enforced_config, path,

return out;
}

/**
* @param {import('types').ValidatedConfig} config
*/
const create_service_worker_module = (config) => `
if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
throw new Error('This module can only be imported inside a service worker');
}
export const build = [];
export const files = [
${create_assets(config)
.filter((asset) => config.kit.serviceWorker.files(asset.file))
.map((asset) => `${JSON.stringify(`${config.kit.paths.base}/${asset.file}`)}`)
.join(',\n\t\t\t\t')}
];
export const prerendered = [];
export const version = ${JSON.stringify(config.kit.version.name)};
`;
4 changes: 3 additions & 1 deletion packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,14 @@ export async function render_response({
}

if (options.service_worker) {
const opts = options.dev ? `, { type: 'module' }` : '';

// we use an anonymous function instead of an arrow function to support
// older browsers (https://github.com/sveltejs/kit/pull/5417)
const init_service_worker = `
if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('${prefixed('service-worker.js')}');
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
});
}
`;
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ declare module '$app/stores' {
declare module '$service-worker' {
/**
* An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
* During development, this is be an empty array.
*/
export const build: string[];
/**
Expand All @@ -350,6 +351,7 @@ declare module '$service-worker' {
export const files: string[];
/**
* An array of pathnames corresponding to prerendered pages and endpoints.
* During development, this is be an empty array.
*/
export const prerendered: string[];
/**
Expand Down

0 comments on commit 2be6fa6

Please sign in to comment.