コンテンツにスキップ

サービスワーカーからESモジュールへの移行

このガイドでは、サービスワーカー形式からESモジュール形式にWorkersを移行する方法を示します。

移行の利点

WorkersをESモジュール形式に移行する理由はいくつかあります:

  1. Durable ObjectsD1Workers AIVectorizeなどのバインディングは、ESモジュールを使用するWorkersからのみ使用できます。
  2. Workerはより速く実行されます。サービスワーカーでは、バインディングはグローバルとして公開されます。これは、リクエストごとにWorkersランタイムが新しいJavaScript実行コンテキストを作成しなければならないことを意味し、オーバーヘッドと時間がかかります。ESモジュールを使用して書かれたWorkersは、複数のリクエスト間で同じ実行コンテキストを再利用できます。
  3. ESモジュール形式を使用すると、Workerへの変更を段階的にデプロイできます。
  4. ESモジュールを使用してWorkersを簡単にnpmに公開でき、コードベース内でWorkersをインポートして再利用できます。

Workerの移行

以下の例は、すべての受信リクエストを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名前空間バインディングを作成するには、次の手順を実行します:

  1. My Tasksという名前のKV名前空間を作成し、バインディングで使用するIDを受け取ります。
  2. Workerを作成します。
  3. 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モジュール形式のバインディング

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モジュール形式の環境変数

ESモジュール形式では、環境変数はWorkerアプリケーションのエントリポイントで提供されるenvパラメータ内でのみ利用可能です。

export default {
async fetch(request, env, ctx) {
console.log(env.API_ACCOUNT_ID) // "<EXAMPLE-ACCOUNT-ID>"をログに出力
return new Response("Hello, world!")
},
};

Cronトリガー

ESモジュール構文で書かれたWorkerでCron Triggerイベントを処理するには、scheduled()イベントハンドラを実装します。これは、サービスワーカー構文でscheduledイベントをリッスンするのと同等です。

この例のコード:

addEventListener("scheduled", (event) => {
// ...
});

は次のようになります:

export default {
async scheduled(event, env, ctx) {
// ...
},
};

eventまたはcontextデータへのアクセス

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つの部分で構成されています:

  1. FetchEventsをリッスンするイベントリスナー。
  2. イベントの.respondWith()メソッドに渡されるResponseオブジェクトを返すイベントハンドラ。

Cloudflareのグローバルネットワークサーバーの1つでWorkerに一致するURLにリクエストが受信されると、CloudflareのサーバーはリクエストをWorkersランタイムに渡します。これにより、Workerが実行されているisolateFetchEventがディスパッチされます。

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
return new Response('Hello worker!', {
headers: { 'content-type': 'text/plain' },
});
}

以下はリクエスト応答ワークフローの例です:

  1. FetchEventのイベントリスナーは、Workerに来るリクエストをリッスンするようにスクリプトに指示します。イベントハンドラにはeventオブジェクトが渡され、これにはevent.requestFetchEventをトリガーしたHTTPリクエストを表すRequestオブジェクトが含まれます。

  2. .respondWith()への呼び出しは、Workersランタイムがリクエストをインターセプトしてカスタム応答を返すことを可能にします(この例では、プレーンテキストの'Hello worker!')。

    • FetchEventハンドラは通常、応答を決定するResponseまたはPromise<Response>.respondWith()メソッドへの呼び出しで culminates します。

    • FetchEventオブジェクトは、予期しない例外や応答が返された後に完了する可能性のある操作を処理するための他の2つのメソッドも提供します。

fetch()ハンドラのライフサイクルメソッドについて詳しく学ぶ。

サポートされているFetchEventプロパティ

  • event.type string

    • イベントのタイプ。これは常に"fetch"を返します。
  • event.request Request

    • 受信したHTTPリクエスト。
  • event.respondWith(responseResponse|Promise) : void

  • event.waitUntil(promisePromise) : void

  • event.passThroughOnException() : void

respondWith

リクエストをインターセプトし、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

waitUntilコマンドは、"fetch"イベントのライフタイムを延長します。これは、Workersランタイムがハンドラが終了する前に実行するPromiseベースのタスクを受け入れますが、応答をブロックすることはありません。たとえば、これは応答のキャッシュやロギングの処理に最適です。

サービスワーカー形式では、waitUntilevent内で利用可能です。これはネイティブの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

passThroughOnExceptionメソッドは、Workerが未処理の例外をスローしたときにランタイムエラー応答を防ぎます。代わりに、スクリプトはオープンに失敗し、Workerが呼び出されなかったかのようにリクエストをオリジンサーバーにプロキシします。

未処理の例外によってリクエスト全体が失敗するのを防ぐために、passThroughOnException()はWorkersランタイムに制御をオリジンサーバーに渡させます。

サービスワーカー形式では、passThroughOnExceptionFetchEventインターフェースに追加され、event内で利用可能です。

ESモジュール形式では、passThroughOnExceptioncontextパラメータオブジェクトで利用可能です。

// フォーマット: サービスワーカー
addEventListener('fetch', event => {
// 未処理/捕捉されていない例外でオリジンにプロキシ
event.passThroughOnException();
throw new Error('Oops');
});