Service workers
Service Worker は、アプリ内部でネットワークリクエストを処理するプロキシサーバーとして機能します。これによりアプリをオフラインで動作させることが可能になります。もしオフラインサポートが不要な場合(または構築するアプリの種類によって現実的に実装できない場合)でも、ビルドした JS と CSS を事前にキャッシュしてナビゲーションを高速化するために Service Worker を使用する価値はあります。
In SvelteKit, if you have a src/service-worker.js file (or src/service-worker/index.js) it will be bundled and automatically registered.
service worker を独自のロジックで登録する必要がある場合や、その他のソリューションを使う場合は、自動登録を無効化 することができます。デフォルトの登録方法は次のようなものです:
if ('serviceWorker' in var navigator: NavigatorThe Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.
navigator) {
function addEventListener<"load">(type: "load", listener: (this: Window, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)addEventListener('load', function () {
var navigator: NavigatorThe Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.
navigator.Navigator.serviceWorker: ServiceWorkerContainerThe serviceWorker read-only property of the Navigator interface returns the ServiceWorkerContainer object for the associated document, which provides access to registration, removal, upgrade, and communication with the ServiceWorker.
Available only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>The register() method of the ServiceWorkerContainer interface creates or updates a ServiceWorkerRegistration for the given scope.
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: ServiceWorkerGlobalScopeself = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (module globalThisglobalThis.var self: WorkerGlobalScope & typeof globalThisThe self read-only property of the WorkerGlobalScope interface returns a reference to the WorkerGlobalScope itself.
self));
// 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.ServiceWorkerGlobalScope.addEventListener<"install">(type: "install", listener: (this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('install', (event: ExtendableEventevent) => {
// 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>The open() method of the the Cache object matching the cacheName.
open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: RequestInfo[]): Promise<void>The addAll() method of the Cache interface takes an array of URLs, retrieves them, and adds the resulting response objects to the given cache.
addAll(const ASSETS: string[]ASSETS);
}
event: ExtendableEventevent.ExtendableEvent.waitUntil(f: Promise<any>): voidThe ExtendableEvent.waitUntil() method tells the event dispatcher that work is ongoing.
waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: ServiceWorkerGlobalScopeself.ServiceWorkerGlobalScope.addEventListener<"activate">(type: "activate", listener: (this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('activate', (event: ExtendableEventevent) => {
// 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[]>The keys() method of the CacheStorage interface returns a Promise that will resolve with an array containing strings corresponding to all of the named Cache objects tracked by the CacheStorage object in the order they were created.
keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>The delete() method of the CacheStorage interface finds the Cache object matching the cacheName, and if found, deletes the Cache object and returns a Promise that resolves to true.
delete(const key: stringkey);
}
}
event: ExtendableEventevent.ExtendableEvent.waitUntil(f: Promise<any>): voidThe ExtendableEvent.waitUntil() method tells the event dispatcher that work is ongoing.
waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: ServiceWorkerGlobalScopeself.ServiceWorkerGlobalScope.addEventListener<"fetch">(type: "fetch", listener: (this: ServiceWorkerGlobalScope, ev: FetchEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('fetch', (event: FetchEventevent) => {
// ignore POST requests etc
if (event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request.Request.method: stringThe method read-only property of the POST, etc.) A String indicating the method of the 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 is used to parse, construct, normalize, and encode URL.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request.Request.url: stringThe url read-only property of the Request interface contains the URL of the request.
url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>The open() method of the the Cache object matching the cacheName.
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: stringThe pathname property of the URL interface represents a location in a hierarchical structure.
pathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>The match() method of the Cache interface returns a Promise that resolves to the Response associated with the first matching request in the Cache object.
match(const url: URLurl.URL.pathname: stringThe pathname property of the URL interface represents a location in a hierarchical structure.
pathname);
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 | Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
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;
}
The Response interface of the Fetch API 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: numberThe status read-only property of the Response interface contains the HTTP status codes of the response.
status === 200 && !const response: Responseresponse.Response.headers: HeadersThe headers read-only property of the with the response.
headers.Headers.get(name: string): string | nullThe get() method of the Headers interface returns a byte string of all the values of a header within a Headers object with a given name.
get('cache-control')?.String.includes(searchString: string, position?: number): booleanReturns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
includes('no-store')) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>The put() method of the Often, you will just want to Window/fetch one or more requests, then add the result straight to your cache.
put(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request, const response: Responseresponse.Response.clone(): ResponseThe clone() method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.
clone());
}
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>The match() method of the Cache interface returns a Promise that resolves to the Response associated with the first matching request in the Cache object.
match(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
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: FetchEventevent.FetchEvent.respondWith(r: Response | PromiseLike<Response>): voidThe respondWith() method of allows you to provide a promise for a Response yourself.
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: WorkerGlobalScope & typeof globalThisThe self read-only property of the WorkerGlobalScope interface returns a reference to the WorkerGlobalScope itself.
self as unknown as ServiceWorkerGlobalScope;
// 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.ServiceWorkerGlobalScope.addEventListener<"install">(type: "install", listener: (this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('install', (event: ExtendableEventevent) => {
// 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>The open() method of the the Cache object matching the cacheName.
open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: RequestInfo[]): Promise<void>The addAll() method of the Cache interface takes an array of URLs, retrieves them, and adds the resulting response objects to the given cache.
addAll(const ASSETS: string[]ASSETS);
}
event: ExtendableEventevent.ExtendableEvent.waitUntil(f: Promise<any>): voidThe ExtendableEvent.waitUntil() method tells the event dispatcher that work is ongoing.
waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: ServiceWorkerGlobalScopeself.ServiceWorkerGlobalScope.addEventListener<"activate">(type: "activate", listener: (this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('activate', (event: ExtendableEventevent) => {
// 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[]>The keys() method of the CacheStorage interface returns a Promise that will resolve with an array containing strings corresponding to all of the named Cache objects tracked by the CacheStorage object in the order they were created.
keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>The delete() method of the CacheStorage interface finds the Cache object matching the cacheName, and if found, deletes the Cache object and returns a Promise that resolves to true.
delete(const key: stringkey);
}
}
event: ExtendableEventevent.ExtendableEvent.waitUntil(f: Promise<any>): voidThe ExtendableEvent.waitUntil() method tells the event dispatcher that work is ongoing.
waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: ServiceWorkerGlobalScopeself.ServiceWorkerGlobalScope.addEventListener<"fetch">(type: "fetch", listener: (this: ServiceWorkerGlobalScope, ev: FetchEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('fetch', (event: FetchEventevent) => {
// ignore POST requests etc
if (event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request.Request.method: stringThe method read-only property of the POST, etc.) A String indicating the method of the 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 is used to parse, construct, normalize, and encode URL.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request.Request.url: stringThe url read-only property of the Request interface contains the URL of the request.
url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>The open() method of the the Cache object matching the cacheName.
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: stringThe pathname property of the URL interface represents a location in a hierarchical structure.
pathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>The match() method of the Cache interface returns a Promise that resolves to the Response associated with the first matching request in the Cache object.
match(const url: URLurl.URL.pathname: stringThe pathname property of the URL interface represents a location in a hierarchical structure.
pathname);
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 | Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
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;
}
The Response interface of the Fetch API 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: numberThe status read-only property of the Response interface contains the HTTP status codes of the response.
status === 200 && !const response: Responseresponse.Response.headers: HeadersThe headers read-only property of the with the response.
headers.Headers.get(name: string): string | nullThe get() method of the Headers interface returns a byte string of all the values of a header within a Headers object with a given name.
get('cache-control')?.String.includes(searchString: string, position?: number): booleanReturns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
includes('no-store')) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>The put() method of the Often, you will just want to Window/fetch one or more requests, then add the result straight to your cache.
put(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
request, const response: Responseresponse.Response.clone(): ResponseThe clone() method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.
clone());
}
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>The match() method of the Cache interface returns a Promise that resolves to the Response associated with the first matching request in the Cache object.
match(event: FetchEventevent.FetchEvent.request: RequestThe request read-only property of the the event handler.
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: FetchEventevent.FetchEvent.respondWith(r: Response | PromiseLike<Response>): voidThe respondWith() method of allows you to provide a promise for a Response yourself.
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: NavigatorThe Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.
navigator.Navigator.serviceWorker: ServiceWorkerContainerThe serviceWorker read-only property of the Navigator interface returns the ServiceWorkerContainer object for the associated document, which provides access to registration, removal, upgrade, and communication with the ServiceWorker.
Available only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>The register() method of the ServiceWorkerContainer interface creates or updates a ServiceWorkerRegistration for the given scope.
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