AgentSkillsCN

workers-development

提供 Cloudflare Workers 的开发指南。涵盖架构、TypeScript/JavaScript 开发、绑定、Service Bindings、Cron Triggers、Workers Assets 等相关内容。当用户询问关于 Workers 开发、边缘函数、绑定、服务绑定、Cron 触发器,或 Workers 运行时时,可使用此技能。当用户说“Workers 开发”“边缘函数”“绑定”“服务联动”时,也可使用此技能。

SKILL.md
--- frontmatter
name: workers-development
description: Cloudflare Workers の開発ガイド。アーキテクチャ、TypeScript/JavaScript 開発、バインディング、Service Bindings、Cron Triggers、Workers Assets について提供。Use when user asks about Workers development, edge functions, bindings, service bindings, cron triggers, or Workers runtime. Also use when user says Workers 開発, エッジ関数, バインディング, サービス連携.
context: fork

Cloudflare Workers Development

概要

Cloudflare Workers は、世界330以上のデータセンターで動作するサーバーレスエッジコンピューティングプラットフォーム。 V8 Isolates ベースでコールドスタートがほぼゼロ、低レイテンシを実現。

アーキテクチャ

V8 Isolates

  • Chrome の V8 エンジンをベースにした軽量な実行環境
  • 各リクエストが独立した Isolate で実行
  • コンテナや VM より高速な起動

制限事項

項目Free プランPaid プラン
CPU 時間10ms50ms(バースト可)
メモリ128MB128MB
サブリクエスト50/リクエスト1000/リクエスト
スクリプトサイズ1MB10MB

基本構造

ES Modules 形式(推奨)

typescript
export interface Env {
  MY_KV: KVNamespace;
  DB: D1Database;
  MY_BUCKET: R2Bucket;
  AI: Ai;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/api/data") {
      const data = await env.MY_KV.get("key");
      return Response.json({ data });
    }

    return new Response("Hello Workers!");
  },

  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    // Cron ジョブ
    ctx.waitUntil(doBackgroundWork(env));
  },

  async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
    // Queue consumer
    for (const message of batch.messages) {
      console.log(message.body);
      message.ack();
    }
  },
};

Service Worker 形式(レガシー)

javascript
addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  return new Response("Hello!");
}

バインディング

Workers から他の Cloudflare サービスに接続するための設定。

wrangler.toml での設定

toml
# KV
[[kv_namespaces]]
binding = "MY_KV"
id = "xxx"

# R2
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"

# D1
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"

# Durable Objects
[durable_objects]
bindings = [
  { name = "COUNTER", class_name = "Counter" }
]

# AI
[ai]
binding = "AI"

# Vectorize
[[vectorize]]
binding = "VECTORIZE"
index_name = "my-index"

# Service Bindings
[[services]]
binding = "AUTH"
service = "auth-worker"

# Queues
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"

# Analytics Engine
[[analytics_engine_datasets]]
binding = "ANALYTICS"
dataset = "my-dataset"

TypeScript 型定義

typescript
export interface Env {
  // KV
  MY_KV: KVNamespace;

  // R2
  MY_BUCKET: R2Bucket;

  // D1
  DB: D1Database;

  // Durable Objects
  COUNTER: DurableObjectNamespace;

  // AI
  AI: Ai;

  // Vectorize
  VECTORIZE: VectorizeIndex;

  // Service Bindings
  AUTH: Fetcher;

  // Queues
  MY_QUEUE: Queue;

  // 環境変数
  API_URL: string;

  // シークレット
  API_KEY: string;
}

Service Bindings

Workers 間の直接通信。HTTP オーバーヘッドなし。

設定

toml
[[services]]
binding = "AUTH_SERVICE"
service = "auth-worker"
environment = "production"

使用例

typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 別の Worker を呼び出し
    const authResponse = await env.AUTH_SERVICE.fetch(
      new Request("https://auth/verify", {
        method: "POST",
        body: JSON.stringify({ token: "xxx" }),
      })
    );

    if (!authResponse.ok) {
      return new Response("Unauthorized", { status: 401 });
    }

    return new Response("Authorized!");
  },
};

Cron Triggers

定期実行ジョブ。

設定

toml
[triggers]
crons = [
  "0 * * * *",      # 毎時
  "30 8 * * 1-5",   # 平日 8:30
  "0 0 1 * *",      # 毎月1日
]

ハンドラー

typescript
export default {
  async scheduled(
    event: ScheduledEvent,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log(`Cron: ${event.cron}`);
    console.log(`Scheduled: ${new Date(event.scheduledTime)}`);

    // バックグラウンド処理
    ctx.waitUntil(
      env.DB.prepare("DELETE FROM logs WHERE created_at < ?")
        .bind(Date.now() - 7 * 24 * 60 * 60 * 1000)
        .run()
    );
  },
};

Workers Assets

静的ファイルの配信(Workers Sites の後継)。

設定

toml
[assets]
directory = "./public"
binding = "ASSETS"  # オプション

動作

  1. リクエスト URL が静的ファイルに一致 → 自動配信
  2. 一致しない → Worker スクリプト実行

プログラムからのアクセス

typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // アセットを取得
    const assetResponse = await env.ASSETS.fetch(request);

    if (assetResponse.status !== 404) {
      return assetResponse;
    }

    // カスタム404
    return new Response("Not Found", { status: 404 });
  },
};

ExecutionContext

waitUntil

レスポンス後もバックグラウンドで処理を継続。

typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // レスポンスを即座に返す
    const response = new Response("OK");

    // バックグラウンドでログ記録
    ctx.waitUntil(
      env.MY_KV.put("last_access", new Date().toISOString())
    );

    return response;
  },
};

passThroughOnException

エラー時にオリジンにフォールバック。

typescript
ctx.passThroughOnException();

Node.js 互換性

有効化

toml
compatibility_flags = ["nodejs_compat"]

使用可能な API

typescript
import { Buffer } from "node:buffer";
import { createHash } from "node:crypto";
import { EventEmitter } from "node:events";

const hash = createHash("sha256").update("data").digest("hex");

互換性のあるモジュール

  • node:buffer
  • node:crypto
  • node:events
  • node:stream
  • node:util
  • node:assert
  • node:path

fetch API

外部 API 呼び出し

typescript
const response = await fetch("https://api.example.com/data", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${env.API_KEY}`,
  },
  body: JSON.stringify({ key: "value" }),
});

const data = await response.json();

リクエスト変換

typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    url.hostname = "api.example.com";

    const modifiedRequest = new Request(url, {
      method: request.method,
      headers: request.headers,
      body: request.body,
    });

    return fetch(modifiedRequest);
  },
};

ミドルウェアパターン

typescript
type Middleware = (
  request: Request,
  env: Env,
  ctx: ExecutionContext,
  next: () => Promise<Response>
) => Promise<Response>;

const withAuth: Middleware = async (request, env, ctx, next) => {
  const token = request.headers.get("Authorization");
  if (!token) {
    return new Response("Unauthorized", { status: 401 });
  }
  return next();
};

const withCors: Middleware = async (request, env, ctx, next) => {
  const response = await next();
  response.headers.set("Access-Control-Allow-Origin", "*");
  return response;
};

// 使用
const middlewares = [withCors, withAuth];

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    let handler = async () => new Response("Hello!");

    for (const mw of middlewares.reverse()) {
      const next = handler;
      handler = () => mw(request, env, ctx, next);
    }

    return handler();
  },
};

テスト

Vitest(推奨)

typescript
import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
import { describe, it, expect } from "vitest";
import worker from "./index";

describe("Worker", () => {
  it("returns hello", async () => {
    const request = new Request("https://example.com/");
    const ctx = createExecutionContext();
    const response = await worker.fetch(request, env, ctx);
    await waitOnExecutionContext(ctx);

    expect(response.status).toBe(200);
    expect(await response.text()).toBe("Hello!");
  });
});

vitest.config.ts

typescript
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: "./wrangler.toml" },
      },
    },
  },
});

デバッグ

console.log

typescript
console.log("Debug:", { key: value });
console.error("Error:", error);
console.time("operation");
// ...
console.timeEnd("operation");

wrangler tail

bash
npx wrangler tail --format pretty

公式リソース