From 41e30ccc2311a66265e6c8e5e5ab6f6d04a9d894 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Nov 2022 09:57:04 -0500 Subject: [PATCH] [feat] dev time service workers (#7597) * dev time service workers - closes #5479 * fixes * dear Rich, meet your good friend Windows Co-authored-by: Simon Holthausen --- .changeset/kind-dragons-poke.md | 5 ++++ .../docs/30-advanced/40-service-workers.md | 10 +++++++- .../core/sync/create_manifest_data/index.js | 2 +- packages/kit/src/core/utils.js | 7 ++---- packages/kit/src/exports/vite/dev/index.js | 22 +++++++++++++++-- packages/kit/src/exports/vite/index.js | 24 ++++++++++++++++++- .../kit/src/runtime/server/page/render.js | 4 +++- packages/kit/src/utils/filesystem.js | 12 ++++++++++ packages/kit/types/ambient.d.ts | 2 ++ 9 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 .changeset/kind-dragons-poke.md diff --git a/.changeset/kind-dragons-poke.md b/.changeset/kind-dragons-poke.md new file mode 100644 index 000000000000..4407bb4663d5 --- /dev/null +++ b/.changeset/kind-dragons-poke.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Run service worker during development diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md index ee26eb59e215..0af6e9598397 100644 --- a/documentation/docs/30-advanced/40-service-workers.md +++ b/documentation/docs/30-advanced/40-service-workers.md @@ -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' +}); +``` \ No newline at end of file diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index 37b74741e1ff..72ee0afc4c11 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -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, diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index 89affc8d9bd7..82567f41f0c9 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -1,7 +1,7 @@ import path from 'path'; import colors from 'kleur'; import { fileURLToPath } from 'url'; -import { posixify } from '../utils/filesystem.js'; +import { posixify, to_fs } from '../utils/filesystem.js'; /** * Resolved path of the `runtime` directory @@ -23,10 +23,7 @@ export const runtime_prefix = posixify_path(runtime_directory); */ export const runtime_base = runtime_directory.startsWith(process.cwd()) ? `/${path.relative('.', runtime_directory)}` - : `/@fs${ - // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there - runtime_directory.startsWith('/') ? '' : '/' - }${runtime_directory}`; + : to_fs(runtime_directory); /** @param {string} str */ function posixify_path(str) { diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index a00afc67cf66..541547f6d5ed 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -6,7 +6,7 @@ import { URL } from 'url'; import { getRequest, setResponse } from '../../../exports/node/index.js'; import { installPolyfills } from '../../../exports/node/polyfills.js'; import { coalesce_to_error } from '../../../utils/error.js'; -import { posixify, resolve_entry } from '../../../utils/filesystem.js'; +import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; import { load_error_page, load_template } from '../../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; @@ -302,6 +302,22 @@ export async function dev(vite, vite_config, svelte_config) { ); } + if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { + const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); + + if (resolved) { + res.writeHead(200, { + 'content-type': 'application/javascript' + }); + res.end(`import '${to_fs(resolved)}';`); + } else { + res.writeHead(404); + res.end('not found'); + } + + return; + } + const hooks_file = svelte_config.kit.files.hooks.server; /** @type {Partial} */ const user_hooks = resolve_entry(hooks_file) @@ -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 }, { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 0a9214c096fd..179924b7111a 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -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(); @@ -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) { @@ -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); } }, @@ -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)}; +`; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 48fde85e4531..f946d0831c8e 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -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}); }); } `; diff --git a/packages/kit/src/utils/filesystem.js b/packages/kit/src/utils/filesystem.js index 1cd68818678c..f4030e80381f 100644 --- a/packages/kit/src/utils/filesystem.js +++ b/packages/kit/src/utils/filesystem.js @@ -112,6 +112,18 @@ export function posixify(str) { return str.replace(/\\/g, '/'); } +/** + * Prepend given path with `/@fs` prefix + * @param {string} str + */ +export function to_fs(str) { + str = posixify(str); + return `/@fs${ + // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there + str.startsWith('/') ? '' : '/' + }${str}`; +} + /** * Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js * @param {string} entry diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index 2f214d7d15fb..29fb4ce1b6c5 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -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[]; /** @@ -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[]; /**