关于 bun-cloudflare-workers
这是一份使用 Bun 开发和部署 Cloudflare Workers 的技能指南,涵盖本地开发、测试和构建,以及 KV、D1、Durable Objects 和 R2 等绑定。
主要功能
- 01使用 Bun 设置 Workers 项目
- 02通过 Wrangler 进行本地开发
- 03使用 bun:test 和 Miniflare 测试
- 04KV、D1、Durable Objects 和 R2 模式
- 05构建与部署指南
适用场景
- 01创建 Workers 项目脚手架
- 02在本地开发和测试 Worker
- 03使用 Cloudflare 存储及数据库绑定
- 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 available | Workers 使用 V8 | 仅使用 Web API |
Module not found | 构建问题 | 检查打包器配置 |
Script too large | 超过 10MB | 优化打包结果 |
CPU time exceeded | 执行时间过长 | 优化或使用队列 |
API 兼容性
Workers 支持 Web API,不支持 Bun 专属 API:
| 可用 | 不可用 |
|---|---|
| fetch() | Bun.file() |
| Response | Bun.serve() |
| Request | bun:sqlite |
| URL | bun:ffi |
| crypto | fs |
| TextEncoder | child_process |
何时加载参考资料
遇到以下情况时,加载 references/bindings.md:
- 高级 KV/D1/R2 模式
- 队列 Worker
- 服务绑定
遇到以下情况时,加载 references/performance.md:
- 打包优化
- 减少冷启动时间
- 缓存策略
评论
支持 Markdown