diff --git a/.changeset/khaki-lights-design.md b/.changeset/khaki-lights-design.md
new file mode 100644
index 000000000000..d406cd9ef060
--- /dev/null
+++ b/.changeset/khaki-lights-design.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: delete `$service-worker` module
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index 9aee347145f3..75a3c0edeef5 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -4,40 +4,40 @@ title: Service workers
Service workers act as proxy servers that handle network requests inside your app. This makes it possible to make your app work offline, but even if you don't need offline support (or can't realistically implement it because of the type of app you're building), it's often worth using service workers to speed up navigation by precaching your built JS and CSS.
-In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker/index.js`) it will be bundled and automatically registered.
+In SvelteKit, if you have a `src/service-worker/index.ts` file it will be bundled and automatically registered.
+
+> [!NOTE] `src/service-worker.ts` or `.js` is also valid, but see the section on [type safety](#type-safety) below
## Inside the service worker
-Inside the service worker you have access to the [`$service-worker` module]($service-worker), which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string, which you can use for creating a unique cache name, and the deployment's `base` path. If your Vite config specifies `define` (used for global variable replacements), this will be applied to service workers as well as your server/client builds.
+For the service worker to do anything useful, you will likely need to import some stuff:
+
+- [`$app/service-worker`]($app-service-worker) exports `self` which is just `globalThis` typed as [`ServiceWorkerGlobalScope`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope) (provided you follow [these steps](#type-safety)), so that your `fetch` events are typed correctly
+- [`$app/env`]($app-env) exports `version`, which is useful for creating deployment-scoped caches
+- [`$app/manifest`]($app-manifest) exports `immutable` build files, your `assets`, and any `prerendered` content, allowing you to populate your caches
-The following example caches the built app and any files in `static` eagerly, and caches all other requests as they happen. This would make each page work offline once visited.
+A typical service worker might look like this:
```js
-// @errors: 2688
/// file: src/service-worker.js
-// Disables access to DOM typings like `HTMLElement` which are not available
-// inside a service worker and instantiates the correct globals
///
///
///
-
-// Ensures that the `$service-worker` import has proper type definitions
-///
-
-// Only necessary if you have an import from `$app/env/*`
-///
-
-import { build, files, version } from '$service-worker';
-
-// This gives `self` the correct types
-const self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self));
+// ---cut---
+import { self } from '$app/service-worker';
+import { version } from '$app/env';
+import { immutable, assets } from '$app/manifest';
+import { resolve } from '$app/paths';
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
+// `immutable`/`assets` paths from `$app/manifest` are relative to the
+// base path, so resolve them to absolute pathnames that can be matched
+// against `url.pathname` in the `fetch` handler
const ASSETS = [
- ...build, // the app itself
- ...files // everything in `static`
+ ...immutable.map((asset) => resolve(asset.path)), // the Vite output
+ ...assets.map((asset) => resolve(asset.path)) // everything in `static`
];
self.addEventListener('install', (event) => {
@@ -69,7 +69,7 @@ self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
- // `build`/`files` can always be served from the cache
+ // `immutable`/`assets` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
@@ -78,32 +78,26 @@ self.addEventListener('fetch', (event) => {
}
}
- // for everything else, try the network first, but
- // fall back to the cache if we're offline
+ // for everything else, try the network first...
try {
const response = await fetch(event.request);
- // if we're offline, fetch can return a value that is not a Response
- // instead of throwing - and we can't pass this non-Response to respondWith
- if (!(response instanceof Response)) {
- throw new Error('invalid response from fetch');
- }
-
if (response.status === 200 && !response.headers.get('cache-control')?.includes('no-store')) {
- cache.put(event.request, response.clone());
+ // ...and cache responses in the background for next time....
+ void cache.put(event.request, response.clone());
}
return response;
- } catch (err) {
+ } catch (error) {
+ // ...otherwise fall back to previously cached data if it exists...
const response = await cache.match(event.request);
if (response) {
return response;
}
- // if there's no cache, then just error out
- // as there is nothing we can do to respond to this request
- throw err;
+ // ...or throw the error
+ throw error;
}
}
@@ -113,18 +107,42 @@ self.addEventListener('fetch', (event) => {
> [!NOTE] Be careful when caching! In some cases, stale data might be worse than data that's unavailable while offline. Since browsers will empty caches if they get too full, you should also be careful about caching large assets like video files.
-> [!NOTE] `build` and `prerendered` are empty arrays during development
+## Type safety
+
+Service workers run in a different context to the rest of your app. As such, they needs different types. You should ensure that your project's root `tsconfig.json` excludes your service worker code...
+
+```json
+/// file: tsconfig.json
+{
+ "extends": "$app/tsconfig",
+ "include": ["src", "test"],
+ "exclude": ["src/service-worker"]
+}
+```
+
+...and that your `src/service-worker/index.ts` file sits alongside a separate `tsconfig.json`, which should set up the correct types by extending [`$app/tsconfig/service-worker`]($app-tsconfig-service-worker):
+
+```json
+/// file: src/service-worker/tsconfig.json
+{
+ "extends": "$app/tsconfig/service-worker"
+}
+```
## Manual registration
-You can [disable automatic registration](configuration#serviceWorker) if you need to register the service worker with your own logic. The default registration looks something like this:
+You can [disable automatic registration](configuration#serviceWorker) if you need to register the service worker with your own logic. The default registration, which is injected into server-rendered HTML, looks something like this:
```js
if ('serviceWorker' in navigator) {
+ const script_url = './service-worker.js';
+ const policy = globalThis?.window?.trustedTypes?.createPolicy(
+ 'sveltekit-trusted-url',
+ { createScriptURL(url) { return url; } }
+ );
+ const sanitised = policy?.createScriptURL(script_url) ?? script_url;
addEventListener('load', function () {
- navigator.serviceWorker.register('./path/to/service-worker.js', {
- type: 'module'
- });
+ navigator.serviceWorker.register(sanitised, { type: 'module' });
});
}
```
diff --git a/documentation/docs/98-reference/27-$service-worker.md b/documentation/docs/98-reference/27-$service-worker.md
deleted file mode 100644
index a8f7388ba7a9..000000000000
--- a/documentation/docs/98-reference/27-$service-worker.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: $service-worker
----
-
-> MODULE: $service-worker
diff --git a/documentation/docs/99-legacy-reference/10-$service-worker.md b/documentation/docs/99-legacy-reference/10-$service-worker.md
new file mode 100644
index 000000000000..5902a19b6f41
--- /dev/null
+++ b/documentation/docs/99-legacy-reference/10-$service-worker.md
@@ -0,0 +1,26 @@
+---
+title: $service-worker
+---
+
+The `service-worker` module existed in SvelteKit 2, and provided access to the following exports:
+
+## base
+
+A root-relative path representing the application's base path. Use [`resolve(...)`]($app-paths#resolve) from `$app/paths` instead.
+
+## build
+
+A `string[]` array of files generated by Vite. Empty during development. Use [`immutable`]($app-manifest#immutable) from `$app/manifest` instead.
+
+## files
+
+A `string[]` array of files in your `static` directory. Use [`assets`]($app-manifest#assets) from `$app/manifest` instead.
+
+
+## prerendered
+
+A `string[]` array of prerendered pages. Empty during dev. Use [`prerendered`]($app-manifest#prerendered) from `$app/manifest` instead.
+
+## version
+
+The value of [`config.version.name`](configuration#version), used for populating caches. Use [`version`]($app/env#version) from `$app/env` instead.
diff --git a/documentation/docs/99-legacy-reference/index.md b/documentation/docs/99-legacy-reference/index.md
new file mode 100644
index 000000000000..e30833e4fd86
--- /dev/null
+++ b/documentation/docs/99-legacy-reference/index.md
@@ -0,0 +1,3 @@
+---
+title: Legacy
+---
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index 2123a7c5d140..a7b87b27b655 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -150,8 +150,6 @@ const get_defaults = (prefix = '') => ({
test('fills in defaults', () => {
const validated = validate_config({});
- assert.equal(validated.kit.serviceWorker.files(''), true);
-
remove_keys(validated, ([, v]) => typeof v === 'function');
const defaults = get_defaults();
@@ -212,8 +210,6 @@ test('fills in partial blanks', () => {
}
});
- assert.equal(validated.kit.serviceWorker.files(''), true);
-
remove_keys(validated, ([, v]) => typeof v === 'function');
const config = get_defaults();
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index 40ec63d6392d..6a921d9e7ee2 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -275,11 +275,11 @@ export const validate_kit_options = object({
}),
serviceWorker: object({
+ files: removed(),
register: boolean(true),
// options could be undefined but if it is defined we only validate that
// it's an object since the type comes from the browser itself
- options: validate(undefined, object({}, true)),
- files: fun((filename) => !/\.DS_Store/.test(filename))
+ options: validate(undefined, object({}, true))
}),
tracing: object({
diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js
index 12c3b437bcd0..00703266a046 100644
--- a/packages/kit/src/core/env.js
+++ b/packages/kit/src/core/env.js
@@ -265,7 +265,11 @@ export function create_sveltekit_env_service_worker(
return dedent`
import { env } from '${base}/${app_dir}/env.js';
- ${global} = { env, version: ${JSON.stringify(version)} };
+ ${global} = {
+ base: location.pathname.split('/').slice(0, -1).join('/'),
+ env,
+ version: ${JSON.stringify(version)}
+ };
`;
}
@@ -294,6 +298,7 @@ export function create_sveltekit_env_service_worker_dev(variables, env, version,
return dedent`
${global} = {
+ base: location.pathname.split('/').slice(0, -1).join('/'),
env: {
${properties.join(',\n\t\t') || '// empty'}
},
diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts
index e92f0aa0ccad..c193488a1b62 100644
--- a/packages/kit/src/exports/public.d.ts
+++ b/packages/kit/src/exports/public.d.ts
@@ -824,13 +824,7 @@ export interface KitConfig {
*/
resolution?: 'client' | 'server';
};
- serviceWorker?: {
- /**
- * Determine which files in your `static` directory will be available in `$service-worker.files`.
- * @default (filename) => !/\.DS_Store/.test(filename)
- */
- files?: (file: string) => boolean;
- } & (
+ serviceWorker?:
| {
/**
* Whether to automatically register the service worker, if it exists.
@@ -848,8 +842,7 @@ export interface KitConfig {
* @default true
*/
register?: false;
- }
- );
+ };
/**
* Options for enabling [OpenTelemetry](https://opentelemetry.io/) tracing for SvelteKit operations.
* @default { server: false }
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 179264fec0cb..cb483943409a 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -24,7 +24,6 @@ import {
create_exported_declarations
} from '../../core/env.js';
import * as sync from '../../core/sync/sync.js';
-import { create_assets } from '../../core/sync/create_manifest_data/index.js';
import { load_and_validate_params } from '../../utils/params.js';
import { runtime_directory, logger } from '../../core/utils.js';
import { generate_manifest } from '../../core/generate_manifest/index.js';
@@ -38,8 +37,7 @@ import {
normalize_id,
remote_module_pattern,
server_only_directory_pattern,
- server_only_module_pattern,
- strip_virtual_prefix
+ server_only_module_pattern
} from './utils.js';
import { stackless } from '../../utils/error.js';
import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
@@ -52,7 +50,6 @@ import { get_import_aliases, get_hash_import_keys } from '../../utils/imports.js
import {
app_env_private,
app_server,
- service_worker,
sveltekit_env,
sveltekit_env_private,
sveltekit_env_service_worker,
@@ -104,8 +101,7 @@ const enforced_config = {
resolve: {
alias: {
$app: true,
- $env: true,
- '$service-worker': true
+ $env: true
}
}
};
@@ -305,9 +301,6 @@ function kit({ svelte_config }) {
/** @type {string | null} */
let service_worker_entry_file;
- /** @type {import('node:path').ParsedPath} */
- let parsed_service_worker;
-
/** @type {string} */
let normalized_cwd;
/** @type {Array<{ alias: string, path: string }>} */
@@ -635,18 +628,13 @@ function kit({ svelte_config }) {
resolveId: {
filter: {
- id: [exactRegex('$service-worker'), prefixRegex('__sveltekit/')]
+ id: [prefixRegex('__sveltekit/')]
},
handler(id) {
if (id === '__sveltekit/manifest') {
return `${out_dir}/generated/client-optimized/app.js`;
}
- if (id === '$service-worker') {
- // ids with :$ don't work with reverse proxies like nginx
- return `\0virtual:${id.substring(1)}`;
- }
-
if (id === '__sveltekit/remote') {
return `${runtime_directory}/client/remote-functions/index.js`;
}
@@ -658,7 +646,6 @@ function kit({ svelte_config }) {
load: {
filter: {
id: [
- exactRegex(service_worker),
exactRegex(sveltekit_env),
exactRegex(sveltekit_env_private),
exactRegex(sveltekit_env_public_client),
@@ -669,9 +656,6 @@ function kit({ svelte_config }) {
},
handler(id) {
switch (id) {
- case service_worker:
- return create_service_worker_module(svelte_config);
-
case sveltekit_manifest_data:
return create_manifest_data_module(is_build, manifest_data);
@@ -733,7 +717,7 @@ function kit({ svelte_config }) {
applyToEnvironment(environment) {
// the import map is only read for client-side violations in `load`, so skip other environments
- return environment.config.consumer === 'client' && environment.name !== 'serviceWorker';
+ return environment.config.consumer === 'client';
},
resolveId: {
@@ -804,6 +788,10 @@ function kit({ svelte_config }) {
if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client);
if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal);
+ if (service_worker_entry_file) {
+ entrypoints.add(posixify(path.relative(root, service_worker_entry_file)));
+ }
+
// Walk up the import graph from the server-only module, looking for a chain
// that leads back to a client entrypoint. We search all candidates (not just
// the first) because a module can be imported by both server and client code,
@@ -1054,8 +1042,6 @@ function kit({ svelte_config }) {
/** @type {Set} */
let build_files;
/** @type {string} */
- let service_worker_code;
- /** @type {string} */
let manifest_data_code;
/**
@@ -1067,7 +1053,6 @@ function kit({ svelte_config }) {
config(config) {
service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
- parsed_service_worker = path.parse(kit.files.serviceWorker);
if (!service_worker_entry_file) return;
@@ -1122,47 +1107,23 @@ function kit({ svelte_config }) {
return environment.name === 'serviceWorker';
},
- resolveId(id, importer) {
- // If importing from a service-worker, only allow certain modules.
- // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly.
- // Transitive imports will be caught during the build.
- if (importer) {
- const parsed_importer = path.parse(importer);
-
- const importer_is_service_worker =
- parsed_importer.dir === parsed_service_worker.dir &&
- parsed_importer.name === parsed_service_worker.name;
-
- if (
- importer_is_service_worker &&
- id !== '$service-worker' &&
- id !== '$app/manifest' &&
- id !== 'virtual:$app/env/public' &&
- id !== '__sveltekit/env/service-worker'
- ) {
- throw new Error(
- `Cannot import ${normalize_id(
- id,
- normalized_aliases,
- normalized_cwd
- )} into service-worker code. Only the modules $service-worker, $app/manifest and $app/env/public are available in service workers.`
- );
- }
- }
-
- if (id.startsWith('$app/') || id === '$service-worker') {
- // ids with :$ don't work with reverse proxies like nginx
- return `\0virtual:${id.substring(1)}`;
- }
-
- if (id.startsWith('__sveltekit')) {
+ resolveId: {
+ filter: {
+ id: [prefixRegex('__sveltekit/')]
+ },
+ handler(id) {
return `\0virtual:${id}`;
}
},
load: {
filter: {
- id: [prefixRegex('\0virtual:')]
+ id: [
+ exactRegex('\0virtual:app/manifest'),
+ exactRegex(sveltekit_manifest_data),
+ exactRegex(sveltekit_env_service_worker),
+ exactRegex(sveltekit_env_public_client)
+ ]
},
handler(id) {
if (!build_files) {
@@ -1185,33 +1146,6 @@ function kit({ svelte_config }) {
}
}
- // in a service worker, `location` is the location of the service worker itself,
- // which is guaranteed to be `/service-worker.js`
- const base = "location.pathname.split('/').slice(0, -1).join('/')";
-
- service_worker_code = dedent`
- export const base = /*@__PURE__*/ ${base};
-
- export const build = [
- ${Array.from(build_files)
- .map((file) => `base + ${s(`/${file}`)}`)
- .join(',\n')}
- ];
-
- export const files = [
- ${manifest_data.assets
- .filter((asset) => kit.serviceWorker.files(asset.file))
- .map((asset) => `base + ${s(`/${asset.file}`)}`)
- .join(',\n')}
- ];
-
- export const prerendered = [
- ${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
- ];
-
- export const version = ${s(kit.version.name)};
- `;
-
manifest_data_code = dedent`
export const immutable = [
${Array.from(build_files)
@@ -1233,10 +1167,6 @@ function kit({ svelte_config }) {
`;
}
- if (id === service_worker) {
- return service_worker_code;
- }
-
if (id === '\0virtual:app/manifest') {
return `export { immutable, assets, prerendered, routes } from '__sveltekit/manifest-data';`;
}
@@ -1270,16 +1200,31 @@ function kit({ svelte_config }) {
`const env = ${kit_global}.env;`
);
}
+ }
+ },
+
+ generateBundle(_, bundle) {
+ const invalid_modules = new Set();
+ const modules = new Map([
+ [`${runtime_directory}/app/forms.js`, '$app/forms'],
+ [`${runtime_directory}/app/navigation.js`, '$app/navigation'],
+ [`${runtime_directory}/app/state/index.js`, '$app/state']
+ ]);
+
+ for (const output of Object.values(bundle)) {
+ if (output.type !== 'chunk') continue;
+
+ for (const id of output.moduleIds) {
+ const module = modules.get(id);
+ if (module) invalid_modules.add(module);
+ }
+ }
- const sw_normalized_cwd = vite.normalizePath(vite_config.root);
- const sw_normalized_aliases = get_import_aliases(
- vite_config.root,
- vite.normalizePath.bind(vite)
- );
- const relative = normalize_id(id, sw_normalized_aliases, sw_normalized_cwd);
- const stripped = strip_virtual_prefix(relative);
+ if (invalid_modules.size > 0) {
throw new Error(
- `Cannot import ${stripped} into service-worker code. Only the modules $service-worker, $app/manifest and $app/env/public are available in service workers.`
+ `Cannot import ${Array.from(modules.values())
+ .filter((module) => invalid_modules.has(module))
+ .join(', ')} into service-worker code.`
);
}
}
@@ -2171,26 +2116,6 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
return out;
}
-/**
- * @param {ValidatedConfig} config
- */
-const create_service_worker_module = (config) => dedent`
- if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
- throw new Error('This module can only be imported inside a service worker');
- }
-
- export const base = location.pathname.split('/').slice(0, -1).join('/');
- export const build = [];
- export const files = [
- ${create_assets(config)
- .filter((asset) => config.kit.serviceWorker.files(asset.file))
- .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
- .join(',\n')}
- ];
- export const prerendered = [];
- export const version = ${s(config.kit.version.name)};
-`;
-
/**
* Creates the `$app/manifest` data module. During development, real values
* are emitted for `assets` and `routes` (the only data known at that point).
diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js
index a76858690b75..a297a0b220be 100644
--- a/packages/kit/src/exports/vite/module_ids.js
+++ b/packages/kit/src/exports/vite/module_ids.js
@@ -6,7 +6,7 @@ export const sveltekit_env_public_client = '\0virtual:__sveltekit/env/public/cli
export const sveltekit_env_public_server = '\0virtual:__sveltekit/env/public/server';
export const sveltekit_env_private = '\0virtual:__sveltekit/env/private';
export const sveltekit_env_service_worker = '\0virtual:__sveltekit/env/service-worker';
-
+export const sveltekit_server = '\0virtual:__sveltekit/server';
export const service_worker = '\0virtual:service-worker';
export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data';
diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js
index 320a176efdee..37a201aa402b 100644
--- a/packages/kit/src/exports/vite/utils.js
+++ b/packages/kit/src/exports/vite/utils.js
@@ -4,12 +4,7 @@ import { negotiate } from '../../utils/http.js';
import { escape_html } from '../../utils/escape.js';
import { stackless } from '../../utils/error.js';
import { dedent } from '../../core/sync/utils.js';
-import {
- app_server,
- app_env_private,
- service_worker,
- sveltekit_env_private
-} from './module_ids.js';
+import { app_server, app_env_private, sveltekit_env_private } from './module_ids.js';
import { styleText } from 'node:util';
/**
@@ -143,10 +138,6 @@ export function normalize_id(id, aliases, cwd) {
return '$app/env/private';
}
- if (id === service_worker) {
- return '$service-worker';
- }
-
return posixify(id);
}
diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts
index 41dcb45ecd1a..c2368b4787cd 100644
--- a/packages/kit/src/types/ambient.d.ts
+++ b/packages/kit/src/types/ambient.d.ts
@@ -83,35 +83,6 @@ declare module '$app/manifest' {
export const routes: Array<{ id: import('$app/types').RouteId }>;
}
-/**
- * This module is only available to [service workers](https://svelte.dev/docs/kit/service-workers).
- */
-declare module '$service-worker' {
- /**
- * The `base` path of the deployment. Typically this is equivalent to `config.paths.base`, but it is calculated from `location.pathname` meaning that it will continue to work correctly if the site is deployed to a subdirectory.
- * Note that there is a `base` but no `assets`, since service workers cannot be used if `config.paths.assets` is specified.
- */
- export const base: string;
- /**
- * An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
- * During development, this is an empty array.
- */
- export const build: string[];
- /**
- * An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.files.assets`. You can customize which files are included from `static` directory using [`config.serviceWorker.files`](https://svelte.dev/docs/kit/configuration#serviceWorker)
- */
- export const files: string[];
- /**
- * An array of pathnames corresponding to prerendered pages and endpoints.
- * During development, this is an empty array.
- */
- export const prerendered: string[];
- /**
- * See [`config.version`](https://svelte.dev/docs/kit/configuration#version). It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
- */
- export const version: string;
-}
-
/**
* This module contains generated types for the routes in your app.
*/
diff --git a/packages/kit/test/apps/basics/src/service-worker.js b/packages/kit/test/apps/basics/src/service-worker.js
index 1b14c0b8db6d..3b5708569079 100644
--- a/packages/kit/test/apps/basics/src/service-worker.js
+++ b/packages/kit/test/apps/basics/src/service-worker.js
@@ -1,4 +1,4 @@
-import { build } from '$service-worker';
+import { immutable } from '$app/manifest';
import { version } from '$app/env';
import { PUBLIC_STATIC } from '$app/env/public';
@@ -6,7 +6,7 @@ const name = `cache-${version}-${PUBLIC_STATIC}`;
self.addEventListener('install', (event) => {
// @ts-expect-error
- event.waitUntil(caches.open(name).then((cache) => cache.addAll(build)));
+ event.waitUntil(caches.open(name).then((cache) => cache.addAll(immutable.map((x) => x.path))));
});
self.addEventListener('activate', (event) => {
@@ -29,7 +29,7 @@ self.addEventListener('fetch', (event) => {
const url = new URL(request.url);
const cached = caches.match(request);
- if (url.origin === location.origin && build.includes(url.pathname)) {
+ if (url.origin === location.origin && immutable.some((a) => url.pathname.includes(a.path))) {
// always return build files from cache
// @ts-expect-error
event.respondWith(cached);
diff --git a/packages/kit/test/apps/options-2/src/service-worker.js b/packages/kit/test/apps/options-2/src/service-worker.js
index 60b251b45259..cb93eb9e5948 100644
--- a/packages/kit/test/apps/options-2/src/service-worker.js
+++ b/packages/kit/test/apps/options-2/src/service-worker.js
@@ -1,4 +1,4 @@
-import { build } from '$service-worker';
+import { immutable } from '$app/manifest';
import { version } from '$app/env';
import { MESSAGE } from '$app/env/public';
import { resolve } from '$app/paths';
@@ -7,7 +7,7 @@ import src from './image.jpg?url';
//@ts-ignore
self.base = resolve('');
//@ts-ignore
-self.build = build;
+self.immutable = immutable;
//@ts-ignore
self.image_src = src;
@@ -15,7 +15,7 @@ const name = `cache-${version}`;
self.addEventListener('install', (event) => {
// @ts-expect-error
- event.waitUntil(caches.open(name).then((cache) => cache.addAll(build)));
+ event.waitUntil(caches.open(name).then((cache) => cache.addAll(immutable.map((a) => a.path))));
console.log(MESSAGE);
});
@@ -39,7 +39,7 @@ self.addEventListener('fetch', (event) => {
const url = new URL(request.url);
const cached = caches.match(request);
- if (url.origin === location.origin && build.includes(url.pathname)) {
+ if (url.origin === location.origin && immutable.some((a) => url.pathname.includes(a.path))) {
// always return build files from cache
// @ts-expect-error
event.respondWith(cached);
diff --git a/packages/kit/test/apps/options-2/test/service-worker.test.js b/packages/kit/test/apps/options-2/test/service-worker.test.js
index aee7b569e85c..fd3e69c3626c 100644
--- a/packages/kit/test/apps/options-2/test/service-worker.test.js
+++ b/packages/kit/test/apps/options-2/test/service-worker.test.js
@@ -28,7 +28,8 @@ test('build /basepath/service-worker.js', async ({ baseURL, request }) => {
const self = {
addEventListener: () => {},
base: null,
- build: null,
+ /** @type {null | Array<{ path: string }>} */
+ immutable: null,
image_src: undefined
};
@@ -40,7 +41,7 @@ test('build /basepath/service-worker.js', async ({ baseURL, request }) => {
});
expect(self.base).toBe('/basepath/');
- expect(self.build?.[0]).toMatch(/\/basepath\/_app\/immutable\/bundle\.[\w-]+\.js/);
+ expect(self.immutable?.[0].path).toMatch(/^_app\/immutable\/bundle\.[\w-]+\.js/);
expect(self.image_src).toMatch(/\/basepath\/_app\/immutable\/assets\/image\.[\w-]+\.jpg/);
});
diff --git a/packages/kit/test/apps/options-3/src/service-worker.js b/packages/kit/test/apps/options-3/src/service-worker.js
index 56ed827f32d0..a6024b5e165e 100644
--- a/packages/kit/test/apps/options-3/src/service-worker.js
+++ b/packages/kit/test/apps/options-3/src/service-worker.js
@@ -1,3 +1,3 @@
-import { build } from '$service-worker';
+import { immutable } from '$app/manifest';
-console.log(build);
+console.log(immutable);
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/.gitignore b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/.gitignore
new file mode 100644
index 000000000000..522bcaa8b893
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/.gitignore
@@ -0,0 +1,20 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+*** Add File: /Users/rich/Development/SVELTE/KIT/kit/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/package.json
+{
+ "name": "service-worker-invalid-app-imports",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "build": "vite build"
+ },
+ "devDependencies": {
+ "@sveltejs/kit": "workspace:^",
+ "@sveltejs/vite-plugin-svelte": "catalog:",
+ "svelte": "catalog:",
+ "vite": "catalog:"
+ },
+ "type": "module"
+}
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/package.json b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/package.json
new file mode 100644
index 000000000000..62464b3e6e22
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "service-worker-invalid-app-imports",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "build": "vite build"
+ },
+ "devDependencies": {
+ "@sveltejs/kit": "workspace:^",
+ "@sveltejs/vite-plugin-svelte": "catalog:",
+ "svelte": "catalog:",
+ "vite": "catalog:"
+ },
+ "type": "module"
+}
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/app.html b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/app.html
new file mode 100644
index 000000000000..d533c5e31716
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/app.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/navigation.js b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/navigation.js
new file mode 100644
index 000000000000..a6ef6739bc52
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/navigation.js
@@ -0,0 +1 @@
+export { goto } from '$app/navigation';
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/routes/+page.svelte b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/routes/+page.svelte
new file mode 100644
index 000000000000..cae41f739e76
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/routes/+page.svelte
@@ -0,0 +1 @@
+Bonjour
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/service-worker.js b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/service-worker.js
new file mode 100644
index 000000000000..4839aeb534f7
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/src/service-worker.js
@@ -0,0 +1,5 @@
+import { deserialize } from '$app/forms';
+import { page } from '$app/state';
+import { goto } from './navigation.js';
+
+console.log(deserialize, page, goto);
diff --git a/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/vite.config.js b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/vite.config.js
new file mode 100644
index 000000000000..713f67a63de9
--- /dev/null
+++ b/packages/kit/test/build-errors/apps/service-worker-invalid-app-imports/vite.config.js
@@ -0,0 +1,14 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import path from 'node:path';
+
+/** @type {import('vite').UserConfig} */
+const config = {
+ plugins: [sveltekit()],
+ server: {
+ fs: {
+ allow: [path.resolve('../../../../src')]
+ }
+ }
+};
+
+export default config;
diff --git a/packages/kit/test/build-errors/env.spec.js b/packages/kit/test/build-errors/env.spec.js
index c0bb9a64782a..7ccd0b11707c 100644
--- a/packages/kit/test/build-errors/env.spec.js
+++ b/packages/kit/test/build-errors/env.spec.js
@@ -44,6 +44,23 @@ test('$app/env/private is not importable from the service worker', { timeout },
timeout,
env
}),
- /.*Cannot import \$app\/env\/private into service-worker code.*/gs
+ /.*Cannot import \$app\/env\/private into code that runs in the browser.*/gs
);
});
+
+test(
+ '$app/forms, $app/navigation and $app/state are not importable from the service worker',
+ { timeout },
+ () => {
+ assert.throws(
+ () =>
+ execSync('pnpm build', {
+ cwd: path.join(import.meta.dirname, 'apps/service-worker-invalid-app-imports'),
+ stdio: 'pipe',
+ timeout,
+ env
+ }),
+ /.*Cannot import \$app\/forms, \$app\/navigation, \$app\/state into service-worker code.*/gs
+ );
+ }
+);
diff --git a/packages/kit/test/prerendering/basics/src/service-worker.js b/packages/kit/test/prerendering/basics/src/service-worker.js
index 8ccabc616961..4cc62ffea494 100644
--- a/packages/kit/test/prerendering/basics/src/service-worker.js
+++ b/packages/kit/test/prerendering/basics/src/service-worker.js
@@ -1,5 +1,5 @@
import process from 'node:process';
-import { prerendered } from '$service-worker';
+import { prerendered } from '$app/manifest';
console.log(prerendered);
diff --git a/packages/kit/test/prerendering/basics/test/tests.spec.js b/packages/kit/test/prerendering/basics/test/tests.spec.js
index c19c23c97b8b..5df056d50bb8 100644
--- a/packages/kit/test/prerendering/basics/test/tests.spec.js
+++ b/packages/kit/test/prerendering/basics/test/tests.spec.js
@@ -258,9 +258,9 @@ test('prerendered.paths omits trailing slashes for endpoints', () => {
const content = read('service-worker.js');
for (const path of [
- '/trailing-slash/page/',
- '/trailing-slash/page/__data.json',
- '/trailing-slash/standalone-endpoint.json'
+ 'trailing-slash/page/',
+ 'trailing-slash/page/__data.json',
+ 'trailing-slash/standalone-endpoint.json'
]) {
expect(content, `Missing ${path}`).toMatch(`"${path}"`);
}
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 35bc4f2f2d8b..c96374f3ae39 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -796,13 +796,7 @@ declare module '@sveltejs/kit' {
*/
resolution?: 'client' | 'server';
};
- serviceWorker?: {
- /**
- * Determine which files in your `static` directory will be available in `$service-worker.files`.
- * @default (filename) => !/\.DS_Store/.test(filename)
- */
- files?: (file: string) => boolean;
- } & (
+ serviceWorker?:
| {
/**
* Whether to automatically register the service worker, if it exists.
@@ -820,8 +814,7 @@ declare module '@sveltejs/kit' {
* @default true
*/
register?: false;
- }
- );
+ };
/**
* Options for enabling [OpenTelemetry](https://opentelemetry.io/) tracing for SvelteKit operations.
* @default { server: false }
@@ -3730,35 +3723,6 @@ declare module '$app/manifest' {
export const routes: Array<{ id: import('$app/types').RouteId }>;
}
-/**
- * This module is only available to [service workers](https://svelte.dev/docs/kit/service-workers).
- */
-declare module '$service-worker' {
- /**
- * The `base` path of the deployment. Typically this is equivalent to `config.paths.base`, but it is calculated from `location.pathname` meaning that it will continue to work correctly if the site is deployed to a subdirectory.
- * Note that there is a `base` but no `assets`, since service workers cannot be used if `config.paths.assets` is specified.
- */
- export const base: string;
- /**
- * An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
- * During development, this is an empty array.
- */
- export const build: string[];
- /**
- * An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.files.assets`. You can customize which files are included from `static` directory using [`config.serviceWorker.files`](https://svelte.dev/docs/kit/configuration#serviceWorker)
- */
- export const files: string[];
- /**
- * An array of pathnames corresponding to prerendered pages and endpoints.
- * During development, this is an empty array.
- */
- export const prerendered: string[];
- /**
- * See [`config.version`](https://svelte.dev/docs/kit/configuration#version). It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
- */
- export const version: string;
-}
-
/**
* This module contains generated types for the routes in your app.
*/
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0d296f27ddb2..a514a18187b0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1302,6 +1302,21 @@ importers:
specifier: 'catalog:'
version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)
+ packages/kit/test/build-errors/apps/service-worker-invalid-app-imports:
+ devDependencies:
+ '@sveltejs/kit':
+ specifier: workspace:^
+ version: link:../../../..
+ '@sveltejs/vite-plugin-svelte':
+ specifier: 'catalog:'
+ version: 7.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))
+ svelte:
+ specifier: 'catalog:'
+ version: 5.56.3(@typescript-eslint/types@8.61.1)
+ vite:
+ specifier: 'catalog:'
+ version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)
+
packages/kit/test/build-errors/apps/syntax-error:
devDependencies:
'@sveltejs/kit':