Service workers
Les service workers agissent comme des serveurs proxy qui gèrent les requêtes réseau au sein de votre application. Cela rend possible le fonctionnement hors-ligne de votre application, mais même si vous n’avez pas besoin de support hors-ligne (ou si vous ne pouvez pas réalistiquement l’implémenter à cause du type d’application que vous développez), il est souvent utile d’utiliser les service workers pour accélérer la navigation en pré-cachant vos JS et CSS compilés.
Dans SvelteKit, si vous avez un fichier src/service-worker.js (ou src/service-worker/index.js),
celui-ci sera compilé et automatiquement activé. Vous pouvez changer l’emplacement de votre
fichier de service worker si besoin.
Vous pouvez désactiver l’activation automatique si vous avez besoin d’activer le service worker selon votre propre logique ou si vous souhaitez utiliser une autre solution. L’activation par défaut ressemble à quelque chose comme ça :
if ('serviceWorker' in var navigator: Navigatornavigator) {
function addEventListener<"load">(type: "load", listener: (this: Window, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)addEventListener('load', function () {
var navigator: Navigatornavigator.Navigator.serviceWorker: ServiceWorkerContainerAvailable only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>register('./path/to/service-worker.js');
});
}Au sein du service worker
Au sein du service worker vous avez accès au module $service-worker, qui
vous fournit les chemins vers tous les assets statiques, fichiers compilés et pages prérendues. Vous
avez également accès à la chaîne de caractères représentant la version de l’application, que vous
pouvez utiliser pour créer un nom de cache unique, ainsi qu’à la base du chemin de déploiement. Si
votre configuration Vite précise l’option define (utilisé pour le remplacement de variables
globales), celle-ci sera appliquée aux service workers ainsi qu’à vos fichiers compilés de serveur
et de client.
L’exemple suivant met en cache l’application compilée et tout fichier dans le dossier static de
manière agressive, et met en cache toute autre requête lorsqu’elles se produisent. Ceci rend de
facto toute page visitée disponible en mode hors-ligne.
// Désactive l'accès au types du DOM comme `HTMLElement` qui ne sont pas disponibles
// dans un service worker et instancie les variables globales pertinentes
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Assure que l'import de `$service-worker` soit correctement typé
/// <reference types="@sveltejs/kit" />
// Uniquement nécessaire si vous avez un import depuis `$env/static/public`
/// <reference types="../.svelte-kit/ambient.d.ts" />
import { const build: 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.
build, const files: string[]An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets. You can customize which files are included from static directory using config.kit.serviceWorker.files
files, const version: stringSee config.kit.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.
version } from '$service-worker';
// Ceci donne à `self` le typage correct
const const self: Window & typeof globalThisself = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (module globalThisglobalThis.var self: Window & typeof globalThisself));
// Crée un nom de cache unique pour ce déploiement
const const CACHE: stringCACHE = `cache-${const version: stringSee config.kit.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.
version}`;
const const ASSETS: string[]ASSETS = [
...const build: 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.
build, // l'application elle-même
...const files: string[]An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets. You can customize which files are included from static directory using config.kit.serviceWorker.files
files // tout ce qui est dans `static`
];
const self: Window & typeof globalThisself.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('install', (event: Eventevent) => {
// Create a new cache and add all files to it
// Crée un nouveau cache et y ajoute tous les fichiers
async function function (local function) addFilesToCache(): Promise<void>addFilesToCache() {
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: Iterable<RequestInfo>): Promise<void> (+1 overload)addAll(const ASSETS: string[]ASSETS);
}
event: Eventevent.waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: Window & typeof globalThisself.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('activate', (event: Eventevent) => {
// Supprime du disque les données précédemment mises en cache
async function function (local function) deleteOldCaches(): Promise<void>deleteOldCaches() {
for (const const key: stringkey of await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.keys(): Promise<string[]>keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>delete(const key: stringkey);
}
}
event: Eventevent.waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: Window & typeof globalThisself.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('fetch', (event: Eventevent) => {
// ignore les requêtes POST, etc
if (event: Eventevent.request.method !== 'GET') return;
async function function (local function) respond(): Promise<Response>respond() {
const const url: URLurl = new var URL: new (url: string | URL, base?: string | URL) => URLThe URL interface represents an object providing static methods used for creating object URLs.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: Eventevent.request.url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
// les fichiers `build`/`files` peuvent toujours être servis depuis le cache
if (const ASSETS: string[]ASSETS.Array<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const url: URLurl.URL.pathname: stringpathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(const url: URLurl.URL.pathname: stringpathname);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
}
// pour tout le reste, essayer d'abord le réseau, mais
// se reporter au cache si le réseau est indisponible
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: Eventevent.request);
// si nous sommes hors-ligne, fetch peut renvoyer une valeur qui n'est pas une Response
// plutôt que de lever une erreur — and nous ne pouvons pas passer cette non-Response à
// respondWith
if (!(const response: Responseresponse instanceof var Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}
This Fetch API interface represents the response to a request.
Response)) {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('la réponse de fetch est invalide');
}
if (const response: Responseresponse.Response.status: numberstatus === 200) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>put(event: Eventevent.request, const response: Responseresponse.Response.clone(): Responseclone());
}
return const response: Responseresponse;
} catch (function (local var) err: unknownerr) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(event: Eventevent.request);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
// s'il n'y a pas de cache, lever une erreur
// puisqu'il n'y a rien à répondre à cette requête
throw function (local var) err: unknownerr;
}
}
event: Eventevent.respondWith(function (local function) respond(): Promise<Response>respond());
});// Désactive l'accès au types du DOM comme `HTMLElement` qui ne sont pas disponibles
// dans un service worker et instancie les variables globales pertinentes
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Assure que l'import de `$service-worker` soit correctement typé
/// <reference types="@sveltejs/kit" />
// Uniquement nécessaire si vous avez un import depuis `$env/static/public`
/// <reference types="../.svelte-kit/ambient.d.ts" />
import { const build: 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.
build, const files: string[]An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets. You can customize which files are included from static directory using config.kit.serviceWorker.files
files, const version: stringSee config.kit.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.
version } from '$service-worker';
// Ceci donne à `self` le typage correct
const const self: ServiceWorkerGlobalScopeself = module globalThisglobalThis.var self: Window & typeof globalThisself as unknown as type ServiceWorkerGlobalScope = /*unresolved*/ anyServiceWorkerGlobalScope;
// Crée un nom de cache unique pour ce déploiement
const const CACHE: stringCACHE = `cache-${const version: stringSee config.kit.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.
version}`;
const const ASSETS: string[]ASSETS = [
...const build: 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.
build, // l'application elle-même
...const files: string[]An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets. You can customize which files are included from static directory using config.kit.serviceWorker.files
files // tout ce qui est dans `static`
];
const self: ServiceWorkerGlobalScopeself.addEventListener('install', (event: anyevent) => {
// Create a new cache and add all files to it
// Crée un nouveau cache et y ajoute tous les fichiers
async function function (local function) addFilesToCache(): Promise<void>addFilesToCache() {
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: Iterable<RequestInfo>): Promise<void> (+1 overload)addAll(const ASSETS: string[]ASSETS);
}
event: anyevent.waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: ServiceWorkerGlobalScopeself.addEventListener('activate', (event: anyevent) => {
// Supprime du disque les données précédemment mises en cache
async function function (local function) deleteOldCaches(): Promise<void>deleteOldCaches() {
for (const const key: stringkey of await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.keys(): Promise<string[]>keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>delete(const key: stringkey);
}
}
event: anyevent.waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: ServiceWorkerGlobalScopeself.addEventListener('fetch', (event: anyevent) => {
// ignore les requêtes POST, etc
if (event: anyevent.request.method !== 'GET') return;
async function function (local function) respond(): Promise<Response>respond() {
const const url: URLurl = new var URL: new (url: string | URL, base?: string | URL) => URLThe URL interface represents an object providing static methods used for creating object URLs.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: anyevent.request.url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
// les fichiers `build`/`files` peuvent toujours être servis depuis le cache
if (const ASSETS: string[]ASSETS.Array<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const url: URLurl.URL.pathname: stringpathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(const url: URLurl.URL.pathname: stringpathname);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
}
// pour tout le reste, essayer d'abord le réseau, mais
// se reporter au cache si le réseau est indisponible
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: anyevent.request);
// si nous sommes hors-ligne, fetch peut renvoyer une valeur qui n'est pas une Response
// plutôt que de lever une erreur — and nous ne pouvons pas passer cette non-Response à
// respondWith
if (!(const response: Responseresponse instanceof var Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}
This Fetch API interface represents the response to a request.
Response)) {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('la réponse de fetch est invalide');
}
if (const response: Responseresponse.Response.status: numberstatus === 200) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>put(event: anyevent.request, const response: Responseresponse.Response.clone(): Responseclone());
}
return const response: Responseresponse;
} catch (function (local var) err: unknownerr) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(event: anyevent.request);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
// s'il n'y a pas de cache, lever une erreur
// puisqu'il n'y a rien à répondre à cette requête
throw function (local var) err: unknownerr;
}
}
event: anyevent.respondWith(function (local function) respond(): Promise<Response>respond());
});Faites attention lorsque vous mettez des choses en cache ! Dans certains cas, des données périmées peuvent être pires que pas de données du tout en mode hors-ligne. Puisque les navigateurs vont vider le cache s’il remplit trop, vous devriez également faire attention lorsque vous mettez en cache des assets de grande taille comme des fichiers vidéo.
Lors du développement
Le service worker est compilé pour la production, mais pas lors du développement. Pour cette raison,
seuls les navigateurs supportant les modules au sein des service
workers seront capables de les utiliser lors du développement. Si
vous activez manuellement votre service worker, vous aurez besoin de fournir l’option { type: 'module' } en mode développement :
import { const dev: booleanWhether the dev server is running. This is not guaranteed to correspond to NODE_ENV or MODE.
dev } from '$app/environment';
var navigator: Navigatornavigator.Navigator.serviceWorker: ServiceWorkerContainerAvailable only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>register('/service-worker.js', {
RegistrationOptions.type?: WorkerType | undefinedtype: const dev: booleanWhether the dev server is running. This is not guaranteed to correspond to NODE_ENV or MODE.
dev ? 'module' : 'classic'
});
buildetprerenderedsont des tableaux vides lors du développement.
Autres solutions
L’implémentation du service worker de SvelteKit est conçue pour être simple d’usage et est probablement une bonne solution pour la plupart des gens. Cependant, en dehors de SvelteKit, beaucoup d’applications PWA utilisent la librairie Workbox. Si vous êtes habitué•e à cette librairie, vous pourriez préférer l’usage du plugin PWA de Vite.
Références
Pour plus d’informations sur les service workers de manière générale, nous recommandons la documentation de MDN.
Modifier cette page sur Github llms.txt