このガイドでは、Workers を Service Worker ↗ 形式から ES modules ↗ 形式へ移行する手順を説明します。
Workers を ES modules 形式へ移行する理由はいくつかあります。
- Worker がより速く動きます。Service Worker では、バインディングはグローバルとして公開されます。そのためリクエストごとに、Workers Runtime は新しい JavaScript 実行コンテキストを作る必要があり、オーバーヘッドと時間がかかります。ES modules で書いた Worker は、複数のリクエストで同じ実行コンテキストを再利用できます。
- Durable Objects を実装するには、ES modules を使う Worker が必要です。
- D1、Workers AI、Vectorize、Workflows、Images のバインディングは、ES modules を使う Worker からのみ使えます。
- ES modules 形式を使うと、Worker への変更を段階的にデプロイ できます。
- ES modules を使う Worker を
npmに簡単に公開でき、コードベース内で Worker をインポートして再利用できます。
次の例は、受信したすべてのリクエストを 301 ステータスコードの URL へリダイレクトする Worker です。
Service 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);
}
// Initialize Worker
addEventListener('fetch', event => {
event.respondWith(handler(event.request));
});ES modules 形式の 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);
},
};バインディング を使うと、Worker が Cloudflare 開発者プラットフォーム上のリソースと連携できます。
ES modules 形式の Workers はグローバルなバインディングに依存しません。一方、Service Worker 構文はグローバルスコープ上のバインディングにアクセスします。
バインディングを理解するために、次の TODO KV 名前空間バインディングの例を見てください。TODO KV 名前空間バインディングを作る手順は次のとおりです。
My Tasksという名前の KV 名前空間を作成し、バインディングで使う ID を受け取ります。- Worker を作成します。
- Worker の Wrangler 設定ファイル を開き、KV 名前空間バインディングを追加します。
{
"kv_namespaces": [
{
"binding": "TODO",
"id": "<ID>"
}
]
}[[kv_namespaces]]
binding = "TODO"
id = "<ID>"以降のセクションでは、このバインディングを Service Worker 形式と ES modules 形式で使います。
Service Worker 構文では、TODO KV 名前空間バインディングは Worker のグローバルスコープで定義されます。TODO KV 名前空間バインディングは、Worker アプリケーションのコードのどこからでも使えます。
addEventListener("fetch", async (event) => {
return await getTodos()
});
async function getTodos() {
// Get the value for the "to-do:123" key
// NOTE: Relies on the TODO KV binding that maps to the "My Tasks" namespace.
let value = await TODO.get("to-do:123");
// Return the value, as is, for the Response
event.respondWith(new Response(value));
}ES modules 形式では、バインディングは Worker のエントリポイントで渡される env パラメーターの中でのみ使えます。
Worker コードで TODO KV 名前空間バインディングにアクセスするには、fetch ハンドラーから getTodos 関数へ env パラメーターを渡す必要があります。
import { getTodos } from './todos'
export default {
async fetch(request, env, ctx) {
// Passing the env parameter so other functions
// can reference the bindings available in the Workers application
return await getTodos(env)
},
};次のコードは、TODO KV バインディングの get 関数を呼び出す getTodos 関数です。
async function getTodos(env) {
// NOTE: Relies on the TODO KV binding which has been provided inside of
// the env parameter of the `getTodos` function
let value = await env.TODO.get("to-do:123");
return new Response(value);
}
export { getTodos }環境変数 へのアクセス方法は、ES modules 形式と Service Worker 形式で異なります。
Wrangler 設定ファイル の環境変数設定例は次のとおりです。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-worker-dev",
// Define top-level environment variables
// using the {"vars": "key": "value"} format
"vars": {
"API_ACCOUNT_ID": "<EXAMPLE-ACCOUNT-ID>"
}
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-worker-dev"
[vars]
API_ACCOUNT_ID = "<EXAMPLE-ACCOUNT-ID>"Service Worker 形式では、API_ACCOUNT_ID は Worker アプリケーションのグローバルスコープで定義されます。API_ACCOUNT_ID 環境変数は、Worker アプリケーションのコードのどこからでも使えます。
addEventListener("fetch", async (event) => {
console.log(API_ACCOUNT_ID) // Logs "<EXAMPLE-ACCOUNT-ID>"
return new Response("Hello, world!")
})ES modules 形式では、環境変数は Worker アプリケーションのエントリポイントで渡される env パラメーター経由で使えます。
export default {
async fetch(request, env, ctx) {
console.log(env.API_ACCOUNT_ID) // Logs "<EXAMPLE-ACCOUNT-ID>"
return new Response("Hello, world!")
},
};cloudflare:workers から env をインポートすれば、トップレベルを含むコードのどこからでも環境変数にアクセスできます。
import { env } from "cloudflare:workers";
// Access environment variables at the top level
const accountId = env.API_ACCOUNT_ID;
export default {
async fetch(request) {
console.log(accountId); // Logs "<EXAMPLE-ACCOUNT-ID>"
return new Response("Hello, world!");
},
};import { env } from "cloudflare:workers";
// Access environment variables at the top level
const accountId = env.API_ACCOUNT_ID;
export default {
async fetch(request: Request): Promise<Response> {
console.log(accountId) // Logs "<EXAMPLE-ACCOUNT-ID>"
return new Response("Hello, world!")
},
};この方法は、設定の初期化や、ネストの深い関数から env を毎回渡さずに環境変数へアクセスする場合に便利です。詳細は env をグローバルとしてインポートする を参照してください。
ES modules 構文で書いた Worker で Cron Trigger イベントを扱うには、scheduled() イベントハンドラー を実装します。これは Service Worker 構文で scheduled イベントを待ち受けることと同等です。
次のコードは:
addEventListener("scheduled", (event) => {
// ...
});次のようになります。
export default {
async scheduled(event, env, ctx) {
// ...
},
};Workers は、request オブジェクトにないデータへアクセスすることがよくあります。たとえば、実行を遅らせるために waitUntil を使うことがあります。ES modules 形式の Workers は、context パラメーター経由で waitUntil にアクセスできます。詳細は ES modules のパラメーター を参照してください。
次のコードは:
async function triggerEvent(event) {
// Fetch some data
console.log('cron processed', event.scheduledTime);
}
// Initialize Worker
addEventListener('scheduled', event => {
event.waitUntil(triggerEvent(event));
});次のようになります。
async function triggerEvent(event) {
// Fetch some data
console.log('cron processed', event.scheduledTime);
}
export default {
async scheduled(event, env, ctx) {
ctx.waitUntil(triggerEvent(event));
},
};Service Worker 構文で書いた Worker は、次の 2 つの部分で構成されます。
FetchEventsを待ち受けるイベントリスナー。- Response オブジェクトを返し、イベントの
.respondWith()メソッドに渡すイベントハンドラー。
Worker に一致する URL のリクエストを Cloudflare のグローバルネットワーク上のサーバーが受け取ると、Cloudflare のサーバーはそのリクエストを Workers Runtime に渡します。これにより、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 Runtime がリクエストを横取りし、カスタムレスポンス(この例ではプレーンテキストの'Hello worker!')を返せます。-
FetchEventハンドラーは通常、ResponseまたはPromise<Response>を引数に.respondWith()を呼び出して終わります。これがレスポンスを決めます。 -
FetchEventオブジェクトには、想定外の例外や、レスポンス返却後に完了する処理を扱う ほかの 2 つのメソッド もあります。
-
fetch() ハンドラーのライフサイクルメソッド について、さらに詳しく学べます。
-
event.typestring- イベントの種類です。常に
"fetch"を返します。
- イベントの種類です。常に
-
event.requestRequest- 受信した HTTP リクエストです。
-
event.respondWith(responseResponse|Promise): voidrespondWithを参照してください。
-
event.waitUntil(promisePromise): voidwaitUntilを参照してください。
-
event.passThroughOnException(): voidpassThroughOnExceptionを参照してください。
リクエストを横取りし、Worker がカスタムレスポンスを返せるようにします。
fetch イベントハンドラーが respondWith を呼ばない場合、ランタイムはそのイベントを次に登録された fetch イベントハンドラーへ渡します。つまり推奨はしませんが、1 つの Worker 内に複数の fetch イベントハンドラーを追加できます。
どの fetch イベントハンドラーも respondWith を呼ばない場合、ランタイムは Worker がなかったかのようにリクエストをオリジンへ転送します。ただしオリジンがない場合、または Worker 自体がオリジンサーバーである場合(*.workers.dev ドメインでは常にそうなります)は、有効なレスポンスのために respondWith を呼ぶ必要があります。
// Format: Service Worker
addEventListener('fetch', event => {
let { pathname } = new URL(event.request.url);
// Allow "/ignore/*" URLs to hit origin
if (pathname.startsWith('/ignore/')) return;
// Otherwise, respond with something
event.respondWith(handler(event));
});waitUntil コマンドは "fetch" イベントの生存期間を延ばします。Promise ベースのタスクを受け取り、Workers Runtime はハンドラーが終了する前にそれを実行しますが、レスポンスはブロックしません。たとえば レスポンスのキャッシュ やログ処理に向いています。
Service Worker 形式では、waitUntil はネイティブの FetchEvent プロパティなので event から使えます。
ES modules 形式では、waitUntil は移動し、context パラメーターオブジェクトから使えます。
// Format: Service Worker
addEventListener('fetch', event => {
event.respondWith(handler(event));
});
async function handler(event) {
// Forward / Proxy original request
let res = await fetch(event.request);
// Add custom header(s)
res = new Response(res.body, res);
res.headers.set('x-foo', 'bar');
// Cache the response
// NOTE: Does NOT block / wait
event.waitUntil(caches.default.put(event.request, res.clone()));
// Done
return res;
}passThroughOnException メソッドは、Worker が未処理の例外を投げてもランタイムエラーレスポンスにしないようにします。代わりにスクリプトは フェイルオープン ↗ し、Worker が呼び出されなかったかのようにリクエストをオリジンサーバーへプロキシします。
捕捉されない例外で JavaScript エラーがリクエスト全体を失敗させないように、passThroughOnException() は Workers Runtime に制御をオリジンサーバーへ渡させます。
Service Worker 形式では、passThroughOnException は FetchEvent インターフェイスに追加されており、event から使えます。
ES modules 形式では、passThroughOnException は context パラメーターオブジェクトから使えます。
// Format: Service Worker
addEventListener('fetch', event => {
// Proxy to origin on unhandled/uncaught exceptions
event.passThroughOnException();
throw new Error('Oops');
});