Cloudflare WorkersでJavaScriptモジュールのサポートを開始します。JavaScriptで記述されたWorkerの例を見れば、過去数年間にインターネット上で出現するようになった次のコードスニペットに気付くかもしれません:

addEventListener("fetch", (event) => {
  event.respondWith(new Response("Hello Worker!"));
}

上記の構文は「Service Worker」APIとして知られており、Webブラウザで使用するために標準化することが提案されました。JavaScriptファイルをWebページにアタッチして、そのHTTP要求と応答を変更し、仮想エンドポイントのように動作させることができます。それはまさにWorkersにとって必要なものであり、 fetch()cachesなどの標準的なWeb APIともうまく統合されていました。

モジュールを導入する前に、サービスWorker APIが引き続きサポートされることを明確にしておきましょう。 「APIまたは機能が非推奨になっているため、あなたの書いたコードをあなた自身で書き直す必要がります」というメールを受け取りたりたい開発者は居ません。あなたは私たちからそれを知らされることはありません。私たちがこの決定を下した理由を知りたい場合は、Workersに向けた下位互換性への取り組みについてご確認ください。

JavaScriptモジュールとは

ECMAScript(略称「ES」)モジュールとも呼ばれるJavaScriptモジュールは、JavaScriptでコードをインポートおよびエクスポートするための標準APIです。これは、JavaScriptの「ES6」言語仕様によって導入され、ほとんどのWebブラウザ、Node.js、Deno、そして現在はCloudflare Workersによって実装されています。以下は、これがどのように機能するかを示す例です:

// filename: ./src/util.js
export function getDate(time) {
  return new Date(time).toISOString().split("T")[0]; // "YYYY-MM-DD"
}

「export」キーワードは、「getDate」関数を現在のモジュールからエクスポートすることを示します。次に、別のモジュールから「import」を使用して、その機能を使用できます。

// filename: ./src/index.js
import { getDate } from "./util.js"

console.log("Today’s date:", getDate());

以上は基本ですが、モジュールでできることは他にもたくさんあります。これにより、格調高い方法でコードの整理、保守、再利用を、簡単に行うことができます。ここでは、モジュールのすべて の側面について説明することはできませんが、詳細については、モジュールに関する MDNガイド またはLin Clarkによる技術的な詳細をご覧になることをお勧めします。

Workersでモジュールを使用するにはどうすればよいですか?

Workerを表すデフォルトのモジュールをエクスポートできます。「addEventListener」を使用する代わりに、各イベントハンドラーはそのモジュールの関数として定義されます。現在、cronトリガーでは、HTTPおよびWebSocketリクエストの「fetch」と、「scheduled」をサポートしています。

export default {
  async fetch(request, environment, context) {
    return new Response("I’m a module!");
  },
  async scheduled(controller, environment, context) {
    // await doATask();
  }
}

また、各イベントハンドラーのパラメーターなど、他のいくつかの違いにも気付くかもしれません。単一の「イベント」オブジェクトの代わりに、必要とされるパラメータ群はそれぞれ分散されています。最初のパラメータはイベントタイプに固有です。「fetch」の場合は リクエスト オブジェクトであり、「scheduled」の場合はcronスケジュールを含む コントーラー です。

2番目のパラメータは、環境変数を含むオブジェクトです(「バインディング」とも呼ばれます)。以前は、各変数はWorkerのグローバルスコープに挿入されていました。単純なソリューションではありますが、コードに変数を魔法のように表示させるのは混乱を招きます。これで、環境オブジェクトを使用して、環境変数にアクセスするモジュールとライブラリを制御できます。このメカニズムは、欠陥のある、または不要な挙動をするサードパーティライブラリがすべての変数または機密を列挙するのを防止することができるため、より安全です。

3番目のパラメータはコンテキストオブジェクトであり、  waitUntil() を使用してバックグラウンドタスクを登録できます。これは、イベントの実行をブロックしてはならないロギングやエラーレポートなどのタスクに役立ちます。

これらをすべてまとめると、複数のモジュールをインポートおよびエクスポートしたり、新しいイベントハンドラー構文を使用したりできます。

// filename: ./src/error.js
export async function logError(url, error) {
  await fetch(url, {
     method: "POST",
     body: error.stack
  })
}
// filename: ./src/worker.js
import { logError } from "./error.js"

export default {
  async fetch(request, environment, context) {
    try {
       return await fetch(request);
    } catch (error) {
       context.waitUntil(logError(environment.ERROR_URL, error));
       return new Response("Oops!", { status: 500 });
    }
  }
}

今週初めに一般提供された、クラスもエクスポートできるDurable Objectsをお忘れなく!Durable Objectクラスを定義する方法です。これは増加された値を返す「Counter」Durable Objectの別の例です。

// filename: ./src/counter.js
export class Counter {
  value = 0;
  fetch() {
    this.value++;
    return new Response(this.value.toString());
  }
}
// filename: ./src/worker.js
// We need to re-export the Durable Object class in the Worker module.
export { Counter } from "./counter.js"

export default {
  async fetch(request, environment) {
    const clientId = request.headers.get("cf-connecting-ip");
    const counterId = environment.Counter.idFromName(clientId);
    // Each IP address gets its own Counter.
    const counter = environment.Counter.get(counterId);
    return counter.fetch("https://counter.object/increment");
  }
}

JavaScript以外のモジュールはありますか?

はい!モジュールは主にJavaScript用ですが、他のモジュールタイプもサポートしています。ただし、一部はまだ標準化されていません。

たとえば、 WebAssemblyモジュールとしてインポートできます。以前、サービスWorker APIには、WebAssemblyがバインディングとして含まれていました。 WebAssemblyは外部リソースではなくコードとして表現する必要があるため、これは間違いだったと思います。モジュールを使用して、WebAssemblyをインポートする新しい方法は次のとおりです:

import module from "./lib/hello.wasm"

export default {
  async fetch(request) {
    const instance = await WebAssembly.instantiate(module);
    const result = instance.exports.hello();
    return new Response(result);
  }
}

現在はサポートされていませんが、この 提案で概説されているように、将来はWebAssemblyモジュールとJavaScriptモジュールをより緊密に統合できることを楽しみにしています。以下に示す人間工学的な改善は、WebAssemblyをJavaScriptエコシステムにさらに組み込むために大いに役立つ可能性があります。

import module from "./lib/hello.wasm"

export default {
  async fetch(request) {
    const instance = await WebAssembly.instantiate(module);
    const result = instance.exports.hello();
    return new Response(result);
  }
}

テキストモジュールとバイナリモジュールのサポートも追加しました。これにより、Stringと ArrayBufferをインポートできるようになりました。標準化されていませんが、HTMLファイルや画像などのリソースを簡単にインポートできるようになりました。

<!-- filename: ./public/index.html -->
<!DOCTYPE html>
<html><body>
<p>Hello!</p>
</body></html>
import html from "../public/index.html"

export default {
  fetch(request) {
    if (request.url.endsWith("/index.html") {
       return new Response(html, {
          headers: { "Content-Type": "text/html" }
       });
    }
    return fetch(request);
  }
}

開始方法

モジュールを使い始めるには多くの方法があります。

まず、 プレイグラウンド (アカウントは不要)またはダッシュボードの クイックエディタを使用して、ブラウザでモジュールを試すことができます。ブラウザは、モジュールを使用していることを自動的に検出しサービスWorker APIからシームレスに切り替えることができます。現在のところ、ブラウザで作成できるJavaScriptモジュールは1つだけですが、間もなく複数のモジュールの作成が可能になる予定です。

冒険心があり、モジュールを使用して新しい プロジェクトを始めてみたい方は、Workers向けの次世代コマンドラインインターフェイス(CLI)であるwrangler 2.0のベータリリースをお試しいただけます。

既存のプロジェクトでは、wrangler 1.0 (リリース1.17以降)を使用することをお勧めします。モジュールを有効にするには、「wrangler.toml」構成を次の例に合わせます:

name = "my-worker"
type = "javascript"
workers_dev = true

[build.upload]
format = "modules"
dir = "./src"
main = "./worker.js" # becomes "./src/worker.js"

[[build.upload.rules]]
type = "ESModule"
globs = ["**/*.js"]

# Uncomment if you have a build script.
# [build]
# command = "npm run build"

モジュールの詳細を説明するためにドキュメントを更新しました。ただし、両方の形式を紹介するように移行するため、一部では引き続きサービスWorker APIを使用します。(おまけとしてTypeScriptもあります!)

モジュールで何か問題が発生したり、お気づきの点がある場合は、私たちにお知らせください。確認させていただきます。コーディングをお楽しみいただき、あなたがモジュールを使用して作成されるものを楽しみにしています!