Service workers
Service Worker は、アプリ内部でネットワークリクエストを処理するプロキシサーバーとして機能します。これによりアプリをオフラインで動作させることが可能になります。もしオフラインサポートが不要な場合(または構築するアプリの種類によって現実的に実装できない場合)でも、ビルドした JS と CSS を事前にキャッシュしてナビゲーションを高速化するために Service Worker を使用する価値はあります。
SvelteKit では、src/service-worker.js ファイル (や src/service-worker/index.js) がある場合、バンドルされ、自動的に登録されます。必要に応じて、service worker の ロケーション を変更することができます。
service worker を独自のロジックで登録する必要がある場合や、その他のソリューションを使う場合は、自動登録を無効化 することができます。デフォルトの登録方法は次のようなものです:
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');
});
}service worker の内部では
service worker の内部では、$service-worker モジュール にアクセスでき、これによって全ての静的なアセット、ビルドファイル、プリレンダリングページへのパスが提供されます。また、アプリのバージョン文字列 (一意なキャッシュ名を作成するのに使用できます) と、デプロイメントの base パスが提供されます。Vite の設定に define (グローバル変数の置換に使用) を指定している場合、それはサーバー/クライアントのビルドだけでなく、service worker にも適用されます。
次の例では、ビルドされたアプリと static にあるファイルをすぐに(eagerly)キャッシュし、その他全てのリクエストはそれらの発生時にキャッシュします。これにより、各ページは一度アクセスするとオフラインで動作するようになります。
// Disables access to DOM typings like `HTMLElement` which are not available
// inside a service worker and instantiates the correct globals
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Ensures that the `$service-worker` import has proper type definitions
/// <reference types="@sveltejs/kit" />
// Only necessary if you have an import from `$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';
// This gives `self` the correct types
const const self: Window & typeof globalThisself = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (module globalThisglobalThis.var self: Window & typeof globalThisself));
// Create a unique cache name for this deployment
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, // the app itself
...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 // everything in `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
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) => {
// Remove previous cached data from disk
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 POST requests 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);
// `build`/`files` can always be served from the 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;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: Eventevent.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 (!(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('invalid response from fetch');
}
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;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw function (local var) err: unknownerr;
}
}
event: Eventevent.respondWith(function (local function) respond(): Promise<Response>respond());
});// Disables access to DOM typings like `HTMLElement` which are not available
// inside a service worker and instantiates the correct globals
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Ensures that the `$service-worker` import has proper type definitions
/// <reference types="@sveltejs/kit" />
// Only necessary if you have an import from `$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';
// This gives `self` the correct types
const const self: ServiceWorkerGlobalScopeself = module globalThisglobalThis.var self: Window & typeof globalThisself as unknown as type ServiceWorkerGlobalScope = /*unresolved*/ anyServiceWorkerGlobalScope;
// Create a unique cache name for this deployment
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, // the app itself
...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 // everything in `static`
];
const self: ServiceWorkerGlobalScopeself.addEventListener('install', (event: anyevent) => {
// Create a new cache and add all files to it
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) => {
// Remove previous cached data from disk
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 POST requests 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);
// `build`/`files` can always be served from the 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;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: anyevent.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 (!(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('invalid response from fetch');
}
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;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw function (local var) err: unknownerr;
}
}
event: anyevent.respondWith(function (local function) respond(): Promise<Response>respond());
});キャッシュにはご注意ください! 場合によっては、オフラインでは利用できないデータよりも古くなったデータのほうが悪いことがあります。ブラウザはキャッシュが一杯になると空にするため、ビデオファイルのような大きなアセットをキャッシュする場合にもご注意ください。
開発中は(During development)
service worker はプロダクション向けにはバンドルされますが、開発中はバンドルされません。そのため、modules in service workers をサポートするブラウザのみ、開発時にもそれを使用することができます。service worker を手動で登録する場合、開発時に { type: 'module' } オプションを渡す必要があります:
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'
});
buildとprerenderedは開発中は空配列です
その他のソリューション
SvelteKit の service worker 実装は、簡単に動作するように設計されており、ほとんどのユーザーにとって良いソリューションでしょう。しかし、SvelteKit 以外の多くの PWA アプリケーションは Workbox というライブラリを活用しています。Workbox に慣れている方は Vite PWA plugin の方が好まれるかもしれません。
References
For more general information on service workers, we recommend the MDN web docs.
Edit this page on GitHub llms.txt