mcpField
B
bun-cloudflare-workers

bun-cloudflare-workers

github.com/secondsky云与 DevOps240406 次安装5 周前更新
代码仓库

关于 bun-cloudflare-workers

这是一份使用 Bun 开发和部署 Cloudflare Workers 的技能指南,涵盖本地开发、测试和构建,以及 KV、D1、Durable Objects 和 R2 等绑定。

主要功能

  1. 01使用 Bun 设置 Workers 项目
  2. 02通过 Wrangler 进行本地开发
  3. 03使用 bun:test 和 Miniflare 测试
  4. 04KV、D1、Durable Objects 和 R2 模式
  5. 05构建与部署指南

适用场景

  1. 01创建 Workers 项目脚手架
  2. 02在本地开发和测试 Worker
  3. 03使用 Cloudflare 存储及数据库绑定
  4. 04构建并部署 Worker

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill bun-cloudflare-workers

安装后在 Claude Code 中输入/bun-cloudflare-workers确认已加载。

详细介绍

Bun Cloudflare Workers

使用 Bun 进行开发,构建并部署 Cloudflare Workers。

快速开始

# Create new Workers project
bunx create-cloudflare my-worker
cd my-worker

# Install dependencies
bun install

# Development
bun run dev

# Deploy
bun run deploy

安全安装

bunx create-cloudflare 等脚手架工具会下载并执行远程代码。运行前,请遵循供应链安全最佳实践:

  • 阻止安装后脚本 — Bun 默认会禁用这些脚本;在 package.json 的 trustedDependencies 中允许特定软件包
  • 冷却期 — 在 bunfig.toml 中配置 minimumReleaseAge,等待新版本发布 7 天
  • 安装前审计 — 运行 socket package score npm <pkg>,或使用 socket npm install <pkg> 检查软件包

加载 dependency-upgrade 技能,获取完整的安全配置,包括 Socket CLI 集成、冷却期设置、锁文件验证和 CI 强制执行。

项目设置

package.json
{
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "build": "bun build src/index.ts --outdir=dist --target=browser"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20260408.0",
    "wrangler": "^4.81.0"
  }
}
wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# Use Bun for local dev
[dev]
local_protocol = "http"

# Bindings
[[kv_namespaces]]
binding = "KV"
id = "xxx"

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

基本 Worker

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

    if (url.pathname === "/") {
      return new Response("Hello from Cloudflare Workers!");
    }

    if (url.pathname === "/api/data") {
      return Response.json({ message: "Hello" });
    }

    return new Response("Not Found", { status: 404 });
  },
};

interface Env {
  KV: KVNamespace;
  DB: D1Database;
}

使用 Hono

// src/index.ts
import { Hono } from "hono";

type Bindings = {
  KV: KVNamespace;
  DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>();

app.get("/", (c) => c.text("Hello Hono!"));

app.get("/api/users", async (c) => {
  const users = await c.env.DB.prepare("SELECT * FROM users").all();
  return c.json(users.results);
});

app.post("/api/users", async (c) => {
  const { name } = await c.req.json();
  await c.env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind(name).run();
  return c.json({ success: true });
});

export default app;

KV 存储

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

    if (request.method === "GET" && key) {
      const value = await env.KV.get(key);
      return Response.json({ key, value });
    }

    if (request.method === "PUT" && key) {
      const value = await request.text();
      await env.KV.put(key, value, { expirationTtl: 3600 });
      return Response.json({ success: true });
    }

    return new Response("Bad Request", { status: 400 });
  },
};

D1 数据库

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Query
    const { results } = await env.DB.prepare(
      "SELECT * FROM users WHERE active = ?"
    ).bind(1).all();

    // Insert
    const info = await env.DB.prepare(
      "INSERT INTO users (name, email) VALUES (?, ?)"
    ).bind("Alice", "[email protected]").run();

    // Transaction
    const batch = await env.DB.batch([
      env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
      env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Charlie"),
    ]);

    return Response.json(results);
  },
};

Durable Objects

// src/counter.ts
export class Counter {
  private state: DurableObjectState;
  private value = 0;

  constructor(state: DurableObjectState) {
    this.state = state;
    this.state.blockConcurrencyWhile(async () => {
      this.value = (await this.state.storage.get("value")) || 0;
    });
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/increment") {
      this.value++;
      await this.state.storage.put("value", this.value);
    }

    return Response.json({ value: this.value });
  }
}

// src/index.ts
export { Counter } from "./counter";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName("global");
    const stub = env.COUNTER.get(id);
    return stub.fetch(request);
  },
};
# wrangler.toml
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_classes = ["Counter"]

R2 存储

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

    if (request.method === "GET") {
      const object = await env.BUCKET.get(key);
      if (!object) {
        return new Response("Not Found", { status: 404 });
      }
      return new Response(object.body, {
        headers: { "Content-Type": object.httpMetadata?.contentType || "application/octet-stream" },
      });
    }

    if (request.method === "PUT") {
      await env.BUCKET.put(key, request.body, {
        httpMetadata: { contentType: request.headers.get("Content-Type") || undefined },
      });
      return Response.json({ success: true });
    }

    return new Response("Method Not Allowed", { status: 405 });
  },
};

使用 Bun 进行开发

本地开发
# Run with wrangler (uses Bun for TypeScript)
bun run dev

# Or directly
bunx wrangler dev
使用 Bun 测试
// src/index.test.ts
import { describe, test, expect } from "bun:test";

// Mock worker
const worker = {
  async fetch(request: Request) {
    return new Response("Hello");
  },
};

describe("Worker", () => {
  test("returns hello", async () => {
    const request = new Request("http://localhost/");
    const response = await worker.fetch(request);
    expect(await response.text()).toBe("Hello");
  });
});
使用 Miniflare 进行测试
import { Miniflare } from "miniflare";

const mf = new Miniflare({
  script: await Bun.file("./dist/index.js").text(),
  kvNamespaces: ["KV"],
});

const response = await mf.dispatchFetch("http://localhost/");
console.log(await response.text());

生产构建

// build.ts
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser", // Workers use browser APIs
  minify: true,
  sourcemap: "external",
});
bun run build.ts
bunx wrangler deploy

环境变量

# wrangler.toml
[vars]
API_URL = "https://api.example.com"

# Secrets (set via CLI)
# wrangler secret put API_KEY
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    console.log(env.API_URL);    // From vars (non-secret, safe to log)
    // Access a Worker secret (NEVER log it — wrangler tail / Logpush persist logs)
    if (env.API_KEY) {
      // use env.API_KEY to call the upstream API
    }
    // ❌ NEVER: console.log(env.API_KEY) — secrets must not appear in logs
    return new Response("OK");
  },
};

定时 Worker(Cron)

export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    console.log("Cron triggered at:", event.scheduledTime);
    // Perform scheduled task
    await env.DB.prepare("DELETE FROM logs WHERE created_at < ?")
      .bind(Date.now() - 7 * 24 * 60 * 60 * 1000)
      .run();
  },

  async fetch(request: Request, env: Env): Promise<Response> {
    return new Response("OK");
  },
};
# wrangler.toml
[triggers]
crons = ["0 * * * *"]  # Every hour

常见错误

错误原因解决方法
Bun API not availableWorkers 使用 V8仅使用 Web API
Module not found构建问题检查打包器配置
Script too large超过 10MB优化打包结果
CPU time exceeded执行时间过长优化或使用队列

API 兼容性

Workers 支持 Web API,不支持 Bun 专属 API:

可用不可用
fetch()Bun.file()
ResponseBun.serve()
Requestbun:sqlite
URLbun:ffi
cryptofs
TextEncoderchild_process

何时加载参考资料

遇到以下情况时,加载 references/bindings.md:

  • 高级 KV/D1/R2 模式
  • 队列 Worker
  • 服务绑定

遇到以下情况时,加载 references/performance.md:

  • 打包优化
  • 减少冷启动时间
  • 缓存策略

评论

支持 Markdown