サービスワーカーからESモジュールへの移行
このガイドでは、サービスワーカー ↗形式からESモジュール ↗形式にWorkersを移行する方法を示します。
WorkersをESモジュール形式に移行する理由はいくつかあります:
- Durable Objects、D1、Workers AI、Vectorizeなどのバインディングは、ESモジュールを使用するWorkersからのみ使用できます。
- Workerはより速く実行されます。サービスワーカーでは、バインディングはグローバルとして公開されます。これは、リクエストごとにWorkersランタイムが新しいJavaScript実行コンテキストを作成しなければならないことを意味し、オーバーヘッドと時間がかかります。ESモジュールを使用して書かれたWorkersは、複数のリクエスト間で同じ実行コンテキストを再利用できます。
- ESモジュール形式を使用すると、Workerへの変更を段階的にデプロイできます。
- ESモジュールを使用してWorkersを簡単に
npmに公開でき、コードベース内でWorkersをインポートして再利用できます。
以下の例は、すべての受信リクエストを301ステータスコードでURLにリダイレクトするWorkerを示しています。
サービスワーカー構文では、例のWorkerは次のようになります:
async function handler(request) { const base = 'https://example.com'; const statusCode = 301;
const destination = new URL(request.url, base); return Response.redirect(destination.toString(), statusCode);}
// Workerの初期化addEventListener('fetch', event => { event.respondWith(handler(event.request));});ESモジュール形式を使用するWorkersは、addEventListener構文をオブジェクト定義に置き換え、これはファイルのデフォルトエクスポート(export defaultを介して)でなければなりません。前の例のコードは次のようになります:
export default { fetch(request) { const base = "https://example.com"; const statusCode = 301;
const source = new URL(request.url); const destination = new URL(source.pathname, base); return Response.redirect(destination.toString(), statusCode); },};バインディングは、WorkersがCloudflare開発プラットフォーム上のリソースと対話することを可能にします。
ESモジュール形式を使用するWorkersは、グローバルバインディングに依存しません。ただし、サービスワーカー構文はグローバルスコープでバインディングにアクセスします。
バインディングを理解するには、次のTODO KV名前空間バインディングの例を参照してください。TODO KV名前空間バインディングを作成するには、次の手順を実行します:
My Tasksという名前のKV名前空間を作成し、バインディングで使用するIDを受け取ります。- Workerを作成します。
- Worker’s
wrangler.toml構成ファイルを見つけ、KV名前空間バインディングを追加します:
kv_namespaces = [ { binding = "TODO", id = "<ID>" }]次のセクションでは、サービスワーカー形式とESモジュール形式でバインディングを使用します。
サービスワーカー構文では、TODO KV名前空間バインディングはWorkerのグローバルスコープで定義されます。TODO KV名前空間バインディングは、Workerアプリケーションのコードのどこでも使用できます。
addEventListener("fetch", async (event) => { return await getTodos()});
async function getTodos() { // "to-do:123"キーの値を取得 // NOTE: "My Tasks"名前空間にマッピングされたTODO KVバインディングに依存します。 let value = await TODO.get("to-do:123");
// 値をそのままResponseとして返す event.respondWith(new Response(value));}ESモジュール形式では、バインディングはWorkerアプリケーションのエントリポイントで提供されるenvパラメータ内でのみ利用可能です。
Workerコード内でTODO KV名前空間バインディングにアクセスするには、fetchハンドラからgetTodos関数にenvパラメータを渡す必要があります。
import { getTodos } from './todos'
export default { async fetch(request, env, ctx) { // envパラメータを渡して、他の関数が // Workersアプリケーションで利用可能なバインディングを参照できるようにします return await getTodos(env) },};次のコードは、TODO KVバインディングのget関数を呼び出すgetTodos関数を表します。
async function getTodos(env) { // NOTE: `getTodos`関数のenvパラメータ内で提供された // TODO KVバインディングに依存します let value = await env.TODO.get("to-do:123"); return new Response(value);}
export { getTodos }環境変数は、ESモジュール形式で書かれたコードとサービスワーカー形式で書かれたコードで異なる方法でアクセスされます。
以下のwrangler.tomlの環境変数設定の例を確認してください:
name = "my-worker-dev"
# `[vars]`ブロックの下で# `key = "value"`形式を使用して# トップレベルの環境変数を定義します[vars]API_ACCOUNT_ID = "<EXAMPLE-ACCOUNT-ID>"サービスワーカー形式では、API_ACCOUNT_IDはWorkerアプリケーションのグローバルスコープで定義されます。API_ACCOUNT_ID環境変数は、Workerアプリケーションのコードのどこでも使用できます。
addEventListener("fetch", async (event) => { console.log(API_ACCOUNT_ID) // "<EXAMPLE-ACCOUNT-ID>"をログに出力 return new Response("Hello, world!")})ESモジュール形式では、環境変数はWorkerアプリケーションのエントリポイントで提供されるenvパラメータ内でのみ利用可能です。
export default { async fetch(request, env, ctx) { console.log(env.API_ACCOUNT_ID) // "<EXAMPLE-ACCOUNT-ID>"をログに出力 return new Response("Hello, world!") },};ESモジュール構文で書かれたWorkerでCron Triggerイベントを処理するには、scheduled()イベントハンドラを実装します。これは、サービスワーカー構文でscheduledイベントをリッスンするのと同等です。
この例のコード:
addEventListener("scheduled", (event) => { // ...});は次のようになります:
export default { async scheduled(event, env, ctx) { // ... },};Workersはしばしばrequestオブジェクトにないデータへのアクセスが必要です。たとえば、Workersは時々waitUntilを使用して実行を遅延させます。ESモジュール形式を使用するWorkersは、contextパラメータを介してwaitUntilにアクセスできます。詳細についてはESモジュールパラメータを参照してください。
この例のコード:
async function triggerEvent(event) { // データを取得 console.log('cron processed', event.scheduledTime);}
// Workerの初期化addEventListener('scheduled', event => { event.waitUntil(triggerEvent(event));});は次のようになります:
async function triggerEvent(event) { // データを取得 console.log('cron processed', event.scheduledTime);}
export default { async scheduled(event, env, ctx) { ctx.waitUntil(triggerEvent(event)); },};サービスワーカー構文で書かれたWorkerは、2つの部分で構成されています:
FetchEventsをリッスンするイベントリスナー。- イベントの
.respondWith()メソッドに渡されるResponseオブジェクトを返すイベントハンドラ。
Cloudflareのグローバルネットワークサーバーの1つでWorkerに一致するURLにリクエストが受信されると、CloudflareのサーバーはリクエストをWorkersランタイムに渡します。これにより、Workerが実行されているisolateでFetchEventがディスパッチされます。
addEventListener('fetch', event => { event.respondWith(handleRequest(event.request));});
async function handleRequest(request) { return new Response('Hello worker!', { headers: { 'content-type': 'text/plain' }, });}以下はリクエスト応答ワークフローの例です:
-
FetchEventのイベントリスナーは、Workerに来るリクエストをリッスンするようにスクリプトに指示します。イベントハンドラにはeventオブジェクトが渡され、これにはevent.request、FetchEventをトリガーしたHTTPリクエストを表すRequestオブジェクトが含まれます。 -
.respondWith()への呼び出しは、Workersランタイムがリクエストをインターセプトしてカスタム応答を返すことを可能にします(この例では、プレーンテキストの'Hello worker!')。
fetch()ハンドラのライフサイクルメソッドについて詳しく学ぶ。
-
event.typestring- イベントのタイプ。これは常に
"fetch"を返します。
- イベントのタイプ。これは常に
-
event.requestRequest- 受信したHTTPリクエスト。
-
event.respondWith(responseResponse|Promise): voidrespondWithを参照してください。
-
event.waitUntil(promisePromise): voidwaitUntilを参照してください。
-
event.passThroughOnException(): voidpassThroughOnExceptionを参照してください。
リクエストをインターセプトし、Workerがカスタム応答を送信できるようにします。
fetchイベントハンドラがrespondWithを呼び出さない場合、ランタイムはイベントを次の登録されたfetchイベントハンドラに渡します。言い換えれば、推奨されていませんが、Worker内に複数のfetchイベントハンドラを追加することが可能です。
fetchイベントハンドラがrespondWithを呼び出さない場合、ランタイムはリクエストをオリジンに転送します。つまり、Workerが存在しなかったかのように。
ただし、オリジンがない場合、またはWorker自体がオリジンサーバーである場合(これは常に*.workers.devドメインに当てはまります)、有効な応答のためにrespondWithを呼び出す必要があります。
// フォーマット: サービスワーカーaddEventListener('fetch', event => { let { pathname } = new URL(event.request.url);
// "/ignore/*" URLがオリジンに到達することを許可 if (pathname.startsWith('/ignore/')) return;
// それ以外の場合、何かで応答 event.respondWith(handler(event));});waitUntilコマンドは、"fetch"イベントのライフタイムを延長します。これは、Workersランタイムがハンドラが終了する前に実行するPromiseベースのタスクを受け入れますが、応答をブロックすることはありません。たとえば、これは応答のキャッシュやロギングの処理に最適です。
サービスワーカー形式では、waitUntilはevent内で利用可能です。これはネイティブのFetchEventプロパティです。
ESモジュール形式では、waitUntilは移動され、contextパラメータオブジェクトで利用可能です。
// フォーマット: サービスワーカーaddEventListener('fetch', event => { event.respondWith(handler(event));});
async function handler(event) { // 元のリクエストを転送/プロキシ let res = await fetch(event.request);
// カスタムヘッダーを追加 res = new Response(res.body, res); res.headers.set('x-foo', 'bar');
// 応答をキャッシュ // NOTE: ブロック/待機しません event.waitUntil(caches.default.put(event.request, res.clone()));
// 完了 return res;}passThroughOnExceptionメソッドは、Workerが未処理の例外をスローしたときにランタイムエラー応答を防ぎます。代わりに、スクリプトはオープンに失敗 ↗し、Workerが呼び出されなかったかのようにリクエストをオリジンサーバーにプロキシします。
未処理の例外によってリクエスト全体が失敗するのを防ぐために、passThroughOnException()はWorkersランタイムに制御をオリジンサーバーに渡させます。
サービスワーカー形式では、passThroughOnExceptionはFetchEventインターフェースに追加され、event内で利用可能です。
ESモジュール形式では、passThroughOnExceptionはcontextパラメータオブジェクトで利用可能です。
// フォーマット: サービスワーカーaddEventListener('fetch', event => { // 未処理/捕捉されていない例外でオリジンにプロキシ event.passThroughOnException(); throw new Error('Oops');});