跳到主内容
快讯直播
AI智模界
教程

从 Deno 迁移到 Cloudflare Workers 实操

Deno 与 Cloudflare 的关系逐渐紧密之后,把跑在 Deno Deploy 或自建 Deno 服务上的项目挪到 Cloudflare Workers,成了一件值得提前演练的事。下面按“先盘点、再改写、最后上线”的顺序走一遍完整流程。

适用场景

手里已经有一个用 Deno 写的 HTTP 服务(Deno Deploy、Fly.io、自建容器都算),API 以 Deno.serve 或 std/http 的 serve 为入口,数据主要存在 Deno KV 或 Redis 里,现在希望换到 Workers 上跑,拿到边缘分发、内置 KV / R2 / Durable Objects 这些能力。本教程解决三件事:入口函数怎么改、Deno 专有 API 怎么替换、KV 数据怎么迁移并验证。

如果项目重度依赖 Deno 的本地文件系统、Deno.run 起子进程、原生 TCP 长连接,迁移成本会明显变高,需要单独评估。

环境与前置条件

  • 操作系统:macOS、Linux、WSL2 均可,Windows 原生环境下 wrangler 的部分命令可能有差异。
  • 运行时:Node.js 18 或更高(wrangler 的运行依赖,具体最低版本以官方文档当前版本为准);包管理器用 npm 或 pnpm 都行。
  • Deno:本地保留一份用于对照原文,版本以项目 deno.json / deno.lock 锁定的为准。
  • 账号:Cloudflare 账号,并且已经开通 Workers 与 KV。免费计划可以完成本教程,但免费版的 KV 写入次数、CPU 时间限制更紧,生产前先看官方配额页面。
  • 资源:本地开发占用内存通常在几百 MB 量级,磁盘几百 MB 足够;本方案不涉及 GPU 与显存。
  • 网络:能访问 Cloudflare API,wrangler login 会拉起浏览器授权。

分步骤部署

第 1 步:盘点项目,列出需要替换的 API

先把 Deno 专有调用扫一遍,这决定了后面的工作量:

```bash

grep -rn "Deno\." src/ --include="*.ts" --include="*.js"

```

重点看这几类:

  • Deno.serve / serve():入口,必须改写。
  • Deno.env.get():读环境变量,Workers 里改成从 env 参数取。
  • Deno.openKv():数据层,改成 Workers KV 或 Durable Objects。
  • Deno.readTextFile / Deno.writeFile:运行时文件读写,Workers 没有可写文件系统,需要改成 KV、R2 或打包进产物。
  • Deno.cron():定时任务,改成 Cron Triggers。
  • Deno.Command / Deno.run:起子进程,Workers 完全不支持,需要拆成外部服务。

把这份清单记下来,后面逐条勾掉。

第 2 步:创建 Workers 项目骨架

在项目根目录初始化 wrangler:

```bash

npm install -D wrangler

npx wrangler login

```

wrangler login 成功会打印当前账号信息。也可以直接用脚手架 npm create cloudflare@latest,按提示选 TypeScript 模板,效果一样。

目录建议保持:

```

.

├── src/index.ts

├── wrangler.toml

└── package.json

```

第 3 步:配置 wrangler 与兼容标志

新建 wrangler.toml:

```toml

name = "deno-to-workers"

main = "src/index.ts"

compatibility_date = "2025-01-01" # 换成本地官方文档建议的近期日期

compatibility_flags = ["nodejs_compat"]

[vars]

APP_ENV = "production"

[[kv_namespaces]]

binding = "APP_KV"

id = "<第 5 步生成的 namespace id>"

```

compatibility_flags = ["nodejs_compat"] 是兼容层的关键:它让代码里 import 的 node:* 模块、Buffer、process.env 之类能被解析。Deno 项目常见的 npm: 依赖里如果有 node: 前缀,多半靠它救场。具体支持范围和推荐搭配的 compatibility date,以 Cloudflare 官方文档当前版本为准。

第 4 步:改写入口函数

Deno 的写法:

```ts

// Deno

Deno.serve({ port: 8000 }, (req) => {

return new Response("hello");

});

```

Workers 的模块化写法:

```ts

// src/index.ts

export default {

async fetch(request: Request, env: Env, ctx: ExecutionContext) {

const url = new URL(request.url);

if (url.pathname === "/health") {

return Response.json({ ok: true, env: env.APP_ENV });

}

return new Response("hello");

},

} satisfies ExportedHandler<Env>;

export interface Env {

APP_ENV: string;

APP_KV: KVNamespace;

API_TOKEN: string;

}

```

对照关系:Deno.serve 的回调变成 fetch 方法;Deno.env.get("API_TOKEN") 变成 env.API_TOKEN;ctx 用来做 ctx.waitUntil() 之类的后台任务。

第 5 步:创建 KV 并写入绑定

```bash

npx wrangler kv namespace create APP_KV

```

输出里会带一段 [[kv_namespaces]] 配置和一个 id,把它贴回 wrangler.toml。注意新版 wrangler 用空格分隔的 kv namespace create,老版本是 kv:namespace create,报 “unknown command” 时换个写法即可。

本地开发用的 KV 是模拟的,要操作线上数据需要加 --remote:

```bash

npx wrangler kv key put --binding=APP_KV "hello" "world" --remote

npx wrangler kv key get --binding=APP_KV "hello" --remote

```

第 6 步:把 Deno KV 的读写换成 Workers KV

Deno KV 用数组做键,Workers KV 只支持字符串键,因此需要把键拼接起来。

```ts

// Deno

const kv = await Deno.openKv();

await kv.set(["user", userId], { name: "Alice" });

const entry = await kv.get(["user", userId]);

console.log(entry.value); // { name: "Alice" }

```

```ts

// Workers

await env.APP_KV.put(user:${userId}, JSON.stringify({ name: "Alice" }));

const user = await env.APP_KV.get(user:${userId}, "json");

```

需要元数据时:

```ts

await env.APP_KV.put(user:${userId}, JSON.stringify(user), {

metadata: { updatedAt: Date.now() },

});

const { value, metadata } = await env.APP_KV.getWithMetadata(user:${userId}, "json");

```

需要“按前缀列出来”时:

```ts

const list = await env.APP_KV.list({ prefix: "user:" });

for (const key of list.keys) {

console.log(key.name, key.metadata);

}

```

有两个语义差异必须提前知道:Workers KV 是最终一致的,一次写在全球生效需要一点时间;Workers KV 没有事务,Deno KV 的 kv.atomic() 在 Workers 里要用 Durable Objects 重写,把计数、库存扣减这类需要强一致的逻辑搬进单个 DO 实例。

设置过期时间用 expirationTtl(秒),例如会话缓存:

```ts

await env.APP_KV.put(sess:${sid}, token, { expirationTtl: 3600 });

```

第 7 步:路由改写

如果原来是手写 if (url.pathname === ...),可以原样保留,Workers 的 Request / URL 都是标准 Web API,逻辑基本不用动。

如果原来用的是 Hono、Oak 这类框架,Hono 在两边都能跑,迁移时通常只改入口:

```ts

import { Hono } from "hono";

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

app.get("/api/users/:id", async (c) => {

const id = c.req.param("id");

const user = await c.env.APP_KV.get(user:${id}, "json");

if (!user) return c.json({ error: "not found" }, 404);

return c.json(user);

});

app.get("/health", (c) => c.json({ ok: true }));

export default app;

```

Oak 在 Workers 上适配度一般,建议换成 Hono 或手写路由。

静态资源方面,Deno Deploy 直接托管目录,Workers 需要显式声明:

```toml

[assets]

directory = "./public"

```

第 8 步:定时任务与密钥

Deno.cron() 换成 Cron Triggers。先在配置里声明:

```toml

[triggers]

crons = ["0 * * * *"]

```

再在入口里加 scheduled:

```ts

export default {

async fetch(request, env, ctx) { /* ... */ },

async scheduled(event: ScheduledController, env: Env, ctx: ExecutionContext) {

ctx.waitUntil(cleanup(env));

},

};

```

注意 Workers 的 cron 表达式按 UTC 解释,Deno 的 Deno.cron 支持指定时区,迁移时要把时差算进去。

密钥不要写进 wrangler.toml,用命令行注入:

```bash

npx wrangler secret put API_TOKEN

```

第 9 步:本地联调与部署

```bash

npx wrangler dev

```

默认监听 http://localhost:8787。确认无误后:

```bash

npx wrangler deploy

```

成功时会打印形如 https://deno-to-workers.<你的子域>.workers.dev 的地址以及版本号。

验证部署是否成功

按顺序做四件事:

1. 健康检查

```bash

curl -i https://deno-to-workers.<你的子域>.workers.dev/health

```

预期返回 HTTP/2 200,body 是 {"ok":true,"env":"production"}。如果返回 500,说明入口抛错了,直接看下一步的日志。

2. 业务接口

```bash

curl -i https://deno-to-workers.<你的子域>.workers.dev/api/users/1

```

预期返回之前在 KV 里写入的那条记录;不存在时返回 404 和 {"error":"not found"}。

3. KV 读写

```bash

npx wrangler kv key get --binding=APP_KV "user:1" --remote

```

预期打印出写入的 JSON。如果为空,说明读写用的不是同一个命名空间或同一个环境。

4. 实时日志

```bash

npx wrangler tail

```

新开一个终端请求一次接口,tail 窗口应出现对应请求的日志行,包含状态码和耗时。没有任何输出通常意味着请求打到了别的地方(比如自定义域名还没绑好)。

四条都通过,才算部署成功。最后把 curl 换成浏览器和你的真实客户端各跑一遍。

常见报错与解决

报错:ReferenceError: Deno is not defined

原因:Workers 运行时没有 Deno 全局对象,代码里还在直接调用 Deno.env、Deno.serve 之类。

解决:把 Deno.env.get("X") 换成 env.X 并写进 Env 接口;把 Deno.serve 换成 export default { fetch }。全量搜索一遍残留:

```bash

grep -rn "Deno\." src/

```

报错:TypeError: Cannot read properties of undefined (reading 'get'),且指向 env.APP_KV.get

原因:代码里的绑定名和 wrangler.toml 里的 binding 不一致,或者 KV 命名空间没创建、id 是占位符。

解决:核对两处名字必须完全相同,然后重建并回填 id:

```bash

npx wrangler kv namespace create APP_KV

```

改完重新 npx wrangler deploy。

报错:No such module "node:fs" 或 Could not resolve "node:buffer"

原因:依赖里有 Node 内置模块,但没开启兼容标志。

解决:在 wrangler.toml 加上兼容标志,并把 compatibility_date 调整到官方文档建议的较新日期:

```toml

compatibility_flags = ["nodejs_compat"]

```

改完删掉 .wrangler 缓存目录再重新 wrangler dev。

报错:Error 1102: Worker exceeded CPU time limit

原因:迁移后某段逻辑在边缘环境里跑得比预期重,常见于一次性遍历大量 KV 键、同步做大批量 JSON 解析。

解决:把批量工作拆成 Cron Triggers 定时跑,或交给 Queues 异步处理;list() 记得用 prefix 和 limit 分页,不要一次拉全量。

报错:Authentication error [code: 10000]

原因:本地登录态过期,或 API AI 词典:Token">Token 缺少 Workers 脚本编辑权限。

解决:

```bash

npx wrangler logout

npx wrangler login

npx wrangler whoami

```

用 Token 部署的场景,检查 Token 权限是否包含 Workers Scripts 与 Workers KV Storage。

后续维护

备份。 下线 Deno KV 之前,先把数据全量导出成 JSON 落盘,再导入 Workers KV。导出脚本循环调用 list() 分页写出文件;导入用批量命令:

```bash

npx wrangler kv bulk put ./kv-dump.json --binding=APP_KV --remote

```

kv-dump.json 的结构是 [{ "key": "...", "value": "..." }] 的数组。日常也建议定期跑一次导出,KV 本身不做自动备份。

升级。 wrangler 属于开发依赖,可以随项目一起升级:

```bash

npm outdated

npm update wrangler

```

升级后先在 wrangler dev 里跑一遍主流程再部署。回滚用:

```bash

npx wrangler deployments list

npx wrangler rollback

```

生产建议用版本化发布,先 wrangler versions upload,确认后切流量。

日志与监控。 npx wrangler tail 适合临时排查但不持久;长期建议开启 Workers Logs 或 Logpush 输出到外部存储,同时在 Cloudflare 控制台给错误率和 P95 延迟配告警。KV 侧关注读、写、删除次数和存储量,写入突增往往意味着缓存键设计有问题。

清理。 稳定运行一到两周后再停掉旧的 Deno 服务,先降配观察,再彻底删除,避免出现“两边都在写同一份数据”的窗口期。

整套流程走下来,真正费时间的不是写代码,而是第 1 步的盘点:把 Deno.* 调用一条条列清楚,剩下的基本就是机械替换。建议先在测试账号上完整跑一遍,确认 KV 语义差异不会影响业务,再动生产环境。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。