Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Service Worker から ES Modules へ移行する

最終更新 Markdown で表示Agent セットアップ

このガイドでは、Workers を Service Worker 形式から ES modules 形式へ移行する手順を説明します。

移行する利点

Workers を ES modules 形式へ移行する理由はいくつかあります。

  1. Worker がより速く動きます。Service Worker では、バインディングはグローバルとして公開されます。そのためリクエストごとに、Workers Runtime は新しい JavaScript 実行コンテキストを作る必要があり、オーバーヘッドと時間がかかります。ES modules で書いた Worker は、複数のリクエストで同じ実行コンテキストを再利用できます。
  2. Durable Objects を実装するには、ES modules を使う Worker が必要です。
  3. D1Workers AIVectorizeWorkflowsImages のバインディングは、ES modules を使う Worker からのみ使えます。
  4. ES modules 形式を使うと、Worker への変更を段階的にデプロイ できます。
  5. ES modules を使う Worker を npm に簡単に公開でき、コードベース内で Worker をインポートして再利用できます。

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 名前空間バインディングを作る手順は次のとおりです。

  1. My Tasks という名前の KV 名前空間を作成し、バインディングで使う ID を受け取ります。
  2. Worker を作成します。
  3. Worker の Wrangler 設定ファイル を開き、KV 名前空間バインディングを追加します。
{
	"kv_namespaces": [
		{
			"binding": "TODO",
			"id": "<ID>"
		}
	]
}
[[kv_namespaces]]
binding = "TODO"
id = "<ID>"

以降のセクションでは、このバインディングを Service Worker 形式と ES modules 形式で使います。

Service Worker 形式のバインディング

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 形式のバインディング

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 形式の環境変数

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 形式の環境変数

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 をグローバルとしてインポートする を参照してください。

Cron Triggers

ES modules 構文で書いた Worker で Cron Trigger イベントを扱うには、scheduled() イベントハンドラー を実装します。これは Service Worker 構文で scheduled イベントを待ち受けることと同等です。

次のコードは:

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

次のようになります。

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

event または context のデータにアクセスする

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 構文

Service Worker 構文で書いた Worker は、次の 2 つの部分で構成されます。

  1. FetchEvents を待ち受けるイベントリスナー。
  2. 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' },
  });
}

リクエストとレスポンスの流れは次のとおりです。

  1. FetchEvent のイベントリスナーは、Worker に届くリクエストを待ち受けるようスクリプトに指示します。イベントハンドラーには event オブジェクトが渡されます。これには event.request が含まれ、FetchEvent を引き起こした HTTP リクエストを表す Request オブジェクトです。

  2. .respondWith() を呼び出すと、Workers Runtime がリクエストを横取りし、カスタムレスポンス(この例ではプレーンテキストの 'Hello worker!')を返せます。

    • FetchEvent ハンドラーは通常、Response または Promise<Response> を引数に .respondWith() を呼び出して終わります。これがレスポンスを決めます。

    • 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 イベントハンドラーへ渡します。つまり推奨はしませんが、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

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

passThroughOnException メソッドは、Worker が未処理の例外を投げてもランタイムエラーレスポンスにしないようにします。代わりにスクリプトは フェイルオープン し、Worker が呼び出されなかったかのようにリクエストをオリジンサーバーへプロキシします。

捕捉されない例外で JavaScript エラーがリクエスト全体を失敗させないように、passThroughOnException() は Workers Runtime に制御をオリジンサーバーへ渡させます。

Service Worker 形式では、passThroughOnExceptionFetchEvent インターフェイスに追加されており、event から使えます。

ES modules 形式では、passThroughOnExceptioncontext パラメーターオブジェクトから使えます。

// Format: Service Worker
addEventListener('fetch', event => {
  // Proxy to origin on unhandled/uncaught exceptions
  event.passThroughOnException();
  throw new Error('Oops');
});

役に立ちましたか?