Skip to main content

Advanced

Service workers

Edit this page on GitHub

Service Worker は、アプリ内部でネットワークリクエストを処理するプロキシサーバーとして機能します。これによりアプリをオフラインで動作させることが可能になります。もしオフラインサポートが不要な場合(または構築するアプリの種類によって現実的に実装できない場合)でも、ビルドした JS と CSS を事前にキャッシュしてナビゲーションを高速化するために Service Worker を使用する価値はあります。

SvelteKit では、src/service-worker.js ファイル (や src/service-worker/index.js) がある場合、バンドルされ、自動的に登録されます。必要に応じて、service worker の ロケーション を変更することができます。

service worker を独自のロジックで登録する必要がある場合や、その他のソリューションを使う場合は、自動登録を無効化 することができます。デフォルトの登録方法は次のようなものです:

ts
if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('./path/to/service-worker.js');
});
}

service worker の内部では

service worker の内部では、$service-worker モジュール にアクセスでき、これによって全ての静的なアセット、ビルドファイル、プリレンダリングページへのパスが提供されます。また、アプリのバージョン文字列 (一意なキャッシュ名を作成するのに使用できます) と、デプロイメントの base パスが提供されます。Vite の設定に define (グローバル変数の置換に使用) を指定している場合、それはサーバー/クライアントのビルドだけでなく、service worker にも適用されます。

次の例では、ビルドされたアプリと static にあるファイルをすぐに(eagerly)キャッシュし、その他全てのリクエストはそれらの発生時にキャッシュします。これにより、各ページは一度アクセスするとオフラインで動作するようになります。

ts
/// <reference types="@sveltejs/kit" />
import { build, files, version } from '$service-worker';
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
];
self.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
Property 'waitUntil' does not exist on type 'Event'.2339Property 'waitUntil' does not exist on type 'Event'.
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
Property 'waitUntil' does not exist on type 'Event'.2339Property 'waitUntil' does not exist on type 'Event'.
}
}
event.waitUntil(deleteOldCaches());
});
Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.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 (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.
}
if (response.status === 200) {
cache.put(event.request, response.clone());
}
Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
Property 'respondWith' does not exist on type 'Event'.2339Property 'respondWith' does not exist on type 'Event'.
}
}
event.respondWith(respond());
});

キャッシュにはご注意ください! 場合によっては、オフラインでは利用できないデータよりも古くなったデータのほうが悪いことがあります。ブラウザはキャッシュが一杯になると空にするため、ビデオファイルのような大きなアセットをキャッシュする場合にもご注意ください。

開発中は(During development)

service worker はプロダクション向けにはバンドルされますが、開発中はバンドルされません。そのため、modules in service workers をサポートするブラウザのみ、開発時にもそれを使用することができます。service worker を手動で登録する場合、開発時に { type: 'module' } オプションを渡す必要があります:

ts
import { dev } from '$app/environment';
navigator.serviceWorker.register('/service-worker.js', {
type: dev ? 'module' : 'classic'
});

buildprerendered は開発中は空配列です

型安全性(Type safety)

service worker に適切な型を設定するには、マニュアルで設定が必要です。service-worker.js の中で、ファイルの先頭に以下を追加してください:

ts
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
ts
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
const sw = self as unknown as ServiceWorkerGlobalScope;

これにより、HTMLElement のような service worker の中では使用できない DOM の型付けへのアクセスが無効になり、正しい global が初期化されます。selfsw に再代入することで、プロセス内で型をキャストすることができます (いくつか方法がありますが、これが追加のファイルを必要としない最も簡単な方法です)。ファイルの残りの部分では、self の代わりに sw を使用します。SvelteKit の型を参照することで、$service-worker import に適切な型定義があることを保証することができます。もし $env/static/public をインポートする場合は、そのインポートに // @ts-ignore を追加するか、/// <reference types="../.svelte-kit/ambient.d.ts" /> を reference types に追加する必要があります。

その他のソリューション

SvelteKit の service worker 実装は意図的に低レベル(low-level)です。より本格的な、よりこだわりが強い(opinionated)ソリューションが必要な場合は、Vite PWA plugin のようなソリューションをご覧になることをおすすめしております、こちらは Workbox を使用しています。service worker に関する一般的な情報をもっとお探しであれば、MDN web docs をおすすめします。