Сервис-воркеры
Сервис-воркеры действуют как прокси-серверы, которые обрабатывают сетевые запросы внутри вашего приложения. Это позволяет сделать приложение работающим в оффлайн-режиме, но даже если вам не нужна поддержка оффлайн (или реализовать её нереально из-за типа создаваемого приложения), часто имеет смысл использовать Сервис-воркеры для ускорения навигации за счёт предварительного кэширования собранных файлов JS и CSS.
В SvelteKit, если у вас есть файл src/service-worker.js (или src/service-worker/index.js), он будет автоматически собран и зарегистрирован.
Вы можете отключить автоматическую регистрацию, если вам нужно регистрировать сервис-воркер с собственной логикой или использовать другое решение. Регистрация по умолчанию выглядит примерно так:
if ('serviceWorker' in navigator) { addEventListener('load', function () { navigator.serviceWorker.register('./path/to/service-worker.js'); });}Внутри сервис-воркера
Заголовок раздела «Внутри сервис-воркера»Внутри сервис-воркера у вас есть доступ к модулю $service-worker, который предоставляет пути ко всем статическим ресурсам, файлам сборки и пререндеренным страницам. Также вам предоставляется строка версии приложения, которую можно использовать для создания уникального имени кэша, и базовый путь (base) развёртывания. Если в вашей конфигурации Vite указана опция define (используемая для глобальной замены переменных), она будет применена к сервис-воркерам так же, как и к вашим серверным и клиентским сборкам.
Следующий пример сразу кэширует собранное приложение и любые файлы из папки static, а все остальные запросы кэширует по мере их выполнения. Это позволит каждой странице работать в офлайн-режиме после того, как пользователь её посетит.
// Отключает доступ к типам DOM (таким как `HTMLElement`), которые недоступны// внутри service worker, и подключает правильные глобальные типы/// <reference no-default-lib="true"/>/// <reference lib="esnext" />/// <reference lib="webworker" />
// Гарантирует, что импорт `$service-worker` будет иметь правильные типы/// <reference types="@sveltejs/kit" />
// Нужно только если вы импортируете что-то из `$env/static/public`/// <reference types="../.svelte-kit/ambient.d.ts" />
import { build, files, version } from '$service-worker';
// Приводим `self` к правильному типуconst self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self));
// Создаём уникальное имя кэша для каждой сборки/развёртыванияconst CACHE = `cache-${version}`;
const ASSETS = [ ...build, // собранное приложение ...files // все файлы из папки static];
self.addEventListener('install', (event) => { // Создаём новый кэш и добавляем в него все важные файлы async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); }
event.waitUntil(addFilesToCache());});
self.addEventListener('activate', (event) => { // Удаляем старые кэши от предыдущих версий приложения async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } }
event.waitUntil(deleteOldCaches());});
self.addEventListener('fetch', (event) => { // Игнорируем не-GET запросы (POST, PUT и т. д.) if (event.request.method !== 'GET') return;
async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE);
// Статические активы (build + files) всегда стараемся брать из кэша if (ASSETS.includes(url.pathname)) { const response = await cache.match(url.pathname);
if (response) { return response; } }
// Для всего остального — стратегия Network First + fallback на кэш try { const response = await fetch(event.request);
// Иногда в оффлайн fetch возвращает не Response, а что-то странное // (особенно в старых браузерах) — проверяем if (!(response instanceof Response)) { throw new Error('invalid response from fetch'); }
if (response.status === 200) { cache.put(event.request, response.clone()); }
return response; } catch (err) { const response = await cache.match(event.request);
if (response) { return response; }
// Если и в кэше ничего нет — просто пробрасываем ошибку throw err; } }
event.respondWith(respond());});// Отключает доступ к типам DOM (таким как `HTMLElement`), которые недоступны// внутри service worker, и подключает правильные глобальные типы/// <reference no-default-lib="true"/>/// <reference lib="esnext" />/// <reference lib="webworker" />
// Гарантирует, что импорт `$service-worker` будет иметь правильные типы/// <reference types="@sveltejs/kit" />
// Нужно только если вы импортируете что-то из `$env/static/public`/// <reference types="../.svelte-kit/ambient.d.ts" />
import { build, files, version } from '$service-worker';
// Приводим `self` к правильному типуconst self = globalThis.self as unknown as ServiceWorkerGlobalScope;
// Создаём уникальное имя кэша для каждой сборки/развёртыванияconst CACHE = `cache-${version}`;
const ASSETS = [ ...build, // собранное приложение ...files // все файлы из папки static];
self.addEventListener('install', (event) => { // Создаём новый кэш и добавляем в него все важные файлы async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); }
event.waitUntil(addFilesToCache());});
self.addEventListener('activate', (event) => { // Удаляем старые кэши от предыдущих версий приложения async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } }
event.waitUntil(deleteOldCaches());});
self.addEventListener('fetch', (event) => { // Игнорируем не-GET запросы (POST, PUT и т. д.) if (event.request.method !== 'GET') return;
async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE);
// Статические активы (build + files) всегда стараемся брать из кэша if (ASSETS.includes(url.pathname)) { const response = await cache.match(url.pathname);
if (response) { return response; } }
// Для всего остального — стратегия Network First + fallback на кэш try { const response = await fetch(event.request);
// Иногда в оффлайн fetch возвращает не Response, а что-то странное // (особенно в старых браузерах) — проверяем if (!(response instanceof Response)) { throw new Error('invalid response from fetch'); }
if (response.status === 200) { cache.put(event.request, response.clone()); }
return response; } catch (err) { const response = await cache.match(event.request);
if (response) { return response; }
// Если и в кэше ничего нет — просто пробрасываем ошибку throw err; } }
event.respondWith(respond());});Во время разработки
Заголовок раздела «Во время разработки»Сервис-воркер включается в сборку для продакшена, но не во время разработки. По этой причине только браузеры, поддерживающие модули в сервис-воркерах, смогут использовать их на этапе разработки. Если вы регистрируете сервис-воркер вручную, вам нужно будет передать опцию { type: 'module' } в режиме разработки:
import { dev } from '$app/environment';
navigator.serviceWorker.register('/service-worker.js', { type: dev ? 'module' : 'classic'});Другие решения
Заголовок раздела «Другие решения»Реализация сервис-воркеров в SvelteKit спроектирована так, чтобы с ней было легко работать, и, вероятно, подойдет большинству пользователей. Однако за пределами SvelteKit многие PWA-приложения используют библиотеку Workbox. Если вы привыкли использовать Workbox, возможно, вы предпочтете плагин Vite PWA.
Для получения более общей информации о сервис-воркерах мы рекомендуем веб-документацию MDN.