diff --git a/.changeset/great-toes-wash.md b/.changeset/great-toes-wash.md
new file mode 100644
index 000000000000..f94b452f817a
--- /dev/null
+++ b/.changeset/great-toes-wash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: expose `base` via `$service-worker`, make paths relative
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index 92da584e5184..6fb57ca5b6a8 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -18,7 +18,7 @@ if ('serviceWorker' in navigator) {
## Inside the service worker
-Inside the service worker you have access to the [`$service-worker` module](modules#$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. 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.
+Inside the service worker you have access to the [`$service-worker` module](modules#$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.
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.
diff --git a/packages/kit/src/exports/vite/build/build_service_worker.js b/packages/kit/src/exports/vite/build/build_service_worker.js
index 285ce62eba51..faf4e31cb305 100644
--- a/packages/kit/src/exports/vite/build/build_service_worker.js
+++ b/packages/kit/src/exports/vite/build/build_service_worker.js
@@ -33,24 +33,30 @@ export async function build_service_worker(
const service_worker = `${kit.outDir}/generated/service-worker.js`;
+ // 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('/')`;
+
fs.writeFileSync(
service_worker,
dedent`
+ export const base = /*@__PURE__*/ ${base};
+
export const build = [
${Array.from(build)
- .map((file) => `${s(`${kit.paths.base}/${file}`)}`)
+ .map((file) => `base + ${s(`/${file}`)}`)
.join(',\n')}
];
export const files = [
${manifest_data.assets
.filter((asset) => kit.serviceWorker.files(asset.file))
- .map((asset) => `${s(`${kit.paths.base}/${asset.file}`)}`)
+ .map((asset) => `base + ${s(`/${asset.file}`)}`)
.join(',\n')}
];
export const prerendered = [
- ${prerendered.paths.map((path) => s(path)).join(',\n')}
+ ${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
];
export const version = ${s(kit.version.name)};
diff --git a/packages/kit/test/apps/options-2/src/routes/hello/+page.js b/packages/kit/test/apps/options-2/src/routes/hello/+page.js
new file mode 100644
index 000000000000..189f71e2e1b3
--- /dev/null
+++ b/packages/kit/test/apps/options-2/src/routes/hello/+page.js
@@ -0,0 +1 @@
+export const prerender = true;
diff --git a/packages/kit/test/apps/options-2/src/routes/hello/+page.svelte b/packages/kit/test/apps/options-2/src/routes/hello/+page.svelte
new file mode 100644
index 000000000000..a994afef288b
--- /dev/null
+++ b/packages/kit/test/apps/options-2/src/routes/hello/+page.svelte
@@ -0,0 +1 @@
+
Prerendered
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 9b804c43b3fc..e1a6d3831696 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,7 @@
-import { build, version } from '$service-worker';
+import { base, build, files, prerendered, version } from '$service-worker';
+
+self.base = base;
+self.build = build;
const name = `cache-${version}`;
diff --git a/packages/kit/test/apps/options-2/test/test.js b/packages/kit/test/apps/options-2/test/test.js
index 31520020b8f0..cbd51d45e1cd 100644
--- a/packages/kit/test/apps/options-2/test/test.js
+++ b/packages/kit/test/apps/options-2/test/test.js
@@ -46,7 +46,20 @@ test.describe('Service worker', () => {
const response = await request.get('/basepath/service-worker.js');
const content = await response.text();
- expect(content).toMatch(/\/_app\/immutable\/entry\/start\.[a-z0-9]+\.js/);
+ const fn = new Function('self', 'location', content);
+
+ const self = {
+ addEventListener: () => {},
+ base: null,
+ build: null
+ };
+
+ fn(self, {
+ pathname: '/basepath/service-worker.js'
+ });
+
+ expect(self.base).toBe('/basepath');
+ expect(self.build[0]).toMatch(/\/basepath\/_app\/immutable\/entry\/start\.[a-z0-9]+\.js/);
});
test('does not register /basepath/service-worker.js', async ({ page }) => {
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 722746c08c53..74677a278c22 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -326,6 +326,11 @@ declare module '$app/stores' {
* This module is only available to [service workers](https://kit.svelte.dev/docs/service-workers).
*/
declare module '$service-worker' {
+ /**
+ * The `base` path of the deployment. Typically this is equivalent to `config.kit.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.kit.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.