为什么Hono可以运行在Edge

要点

  • Edge Runtime 不是完整的 Node.js,它有严格的 API 白名单限制
  • Hono 的核心代码不依赖任何 Node.js 内置模块(fs、path、http、crypto 等全部不碰)
  • 整个框架只使用 Request、Response、fetch、URL、Headers 等 Web Standards API
  • 打包体积约 14KB,对 tree-shaking 友好——未使用的中间件不会进入最终产物
  • V8 Isolate 模型要求代码无状态、无长生命周期副作用,Hono 的请求处理模型天然契合
  • Express 无法运行在 Edge,根本原因是它深度绑定了 Node.js 的 http 模块和流式处理模型

1. Edge Runtime 到底限制了什么

在理解 Hono 为什么能跑在 Edge 之前,需要先搞清楚 Edge Runtime 不是一个普通的 Node.js 环境。

以 Cloudflare Workers 为代表的 Edge 平台,底层使用的是 V8 Isolate 而不是完整的 Node.js 进程。一个 Isolate 是一个轻量级的 JavaScript 执行沙箱,启动时间在微秒级别,但代价是大量 Node.js 能力被直接移除。

具体来说,Edge Runtime 通常有以下限制:

限制类别具体表现
文件系统fsfs/promises,无法读写本地文件
网络模块httphttpsnettls,只有全局 fetch
加密模块无 Node.js crypto,只有 Web Crypto API(globalThis.crypto
流处理无 Node.js stream,只有 Web Streams API(ReadableStream
进程模型child_processclusterworker_threads
路径处理path 模块,路径拼接需自行处理或用 URL
CPU 时间单次请求有 CPU 时间上限(Workers 免费方案 10ms,付费 50ms)
内存单个 Isolate 内存上限通常为 128MB
长连接不支持 WebSocket 服务端监听(部分平台已支持 WebSocket Hibernation)

这些限制意味着:你在 Node.js 里习以为常的很多操作——读文件、创建 TCP 服务器、spawn 子进程——在 Edge 上全部不可用。一个框架如果内部使用了其中任何一个 API,就无法在 Edge 上运行。

2. 零 Node.js 依赖:Hono 的核心约束

Hono 从设计之初就确立了一条硬规则:核心代码不引入任何 Node.js 内置模块

打开 Hono 的源码(src/hono.tssrc/router/src/compose.tssrc/context.ts),你会发现它的 import 全部来自:

// Hono 源码中的典型 import——全部是相对路径或标准 API
import { HonoBase } from './hono-base'
import type { Context } from './context'
import type { MiddlewareHandler } from './types'

没有 import fs from 'node:fs',没有 import { createServer } from 'node:http',没有 import { Buffer } from 'node:buffer'

这不是偶然。Hono 的作者 Yusuke Wada 在最初设计时就考虑了多运行时支持(参见上一篇 11 多运行时适配)。核心层保持对运行时的无感知,是这一切的基础。

一个具体的例子:如果你需要在框架内部做 URL 解析,Node.js 的做法和 Web 标准的做法完全不同。

// ❌ Node.js 方式——Edge 不可用
import { URL } from 'node:url'
const parsed = new URL(req.url, `http://${req.headers.host}`)
 
// ✅ Web Standards 方式——Edge 可用
const parsed = new URL(req.url)

Hono 选择了后者。URL 是全局可用的 Web API,在浏览器、Node.js 18+、Cloudflare Workers、Deno、Bun 中都有原生支持。

3. 纯 Web Standards:一套 API 走遍所有运行时

Hono 的内部只依赖以下 Web Standards API:

API用途
Request表示 HTTP 请求
Response表示 HTTP 响应
Headers操作请求头/响应头
fetch()发起 HTTP 请求(中间件中常用)
URL / URLSearchParams解析 URL 和查询参数
ReadableStream / WritableStream流式响应
TextEncoder / TextDecoder字符串与字节转换
crypto(Web Crypto)签名、哈希、随机数
FormData表单数据解析
AbortController / AbortSignal请求取消

这些 API 的共同特点是:它们是 W3C/WHATWG 标准的一部分,由浏览器厂商和运行时引擎共同实现。你不需要 polyfill,不需要适配层,它们在目标运行时中已经存在。

来看 Hono 的 Context 对象如何体现这一点:

// Hono Context 的核心结构(简化)
class Context {
  req: HonoRequest    // 内部持有标准 Request 对象
  // ...
 
  json(object: unknown, status?: number): Response {
    const body = JSON.stringify(object)
    return new Response(body, {
      status: status ?? 200,
      headers: { 'Content-Type': 'application/json; charset=UTF-8' },
    })
  }
 
  text(content: string, status?: number): Response {
    return new Response(content, {
      status: status ?? 200,
      headers: { 'Content-Type': 'text/plain; charset=UTF-8' },
    })
  }
}

c.json()c.text() 返回的就是原生的 Response 对象。整个 Hono 的请求处理流程,本质上就是把一个 Request 经过路由匹配和中间件链,转换成一个 Response。这和 Cloudflare Workers 的 export default { fetch(request) { ... } } 入口模型完全对齐。

4. 极小体积:~14KB 是怎么做到的

Hono 的核心包体积约为 14KB(minzipped)。这个数字在 Node.js 框架中极为突出——Express 约 200KB,Koa 约 70KB,Fastify 约 500KB+。

小体积来自三个设计决策:

第一,核心只做路由分发和中间件编排。 Hono 的核心代码就是三件事:路由匹配(Router)、请求上下文构建(Context)、中间件执行(compose)。不内置模板引擎、不内置数据库连接、不内置校验库。所有扩展能力通过中间件包(hono/*)按需引入。

第二,tree-shaking 友好。 Hono 使用 ES Module 导出,且中间件之间没有循环依赖。如果你只用了 Hono 类和 logger 中间件,其他几十个中间件的代码不会进入你的最终产物:

// 只引入需要的——未使用的模块会被 bundler 消除
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
 
const app = new Hono()
app.use('*', logger())
app.use('/api/*', cors())

第三,Router 实现本身极其精简。 Hono 内置了多种路由器(RegExpRouter、SmartRouter、TrieRouter、LinearRouter),每个路由器都是独立的模块,内部实现不依赖外部库。以 RegExpRouter 为例,它把路由模式编译成正则表达式,匹配时一次 RegExp.exec() 即可完成,没有递归遍历,没有复杂的树结构。

// RegExpRouter 的思路(极度简化)
// 路由注册:/users/:id → /^\/users\/([^/]+)$/
// 匹配时:requestPath.match(compiledRegExp)
// 一次正则执行,O(1) 级别的路由查找

这些决策叠加在一起,让 Hono 在 Edge 场景下的冷启动和内存占用都保持在极低水平。

5. V8 Isolate 兼容:为什么 Hono 能跑在 Workers 里

Cloudflare Workers 的 V8 Isolate 模型有几个关键特征:

  1. Isolate 是共享的。 多个请求可能复用同一个 Isolate,Isolate 之间不共享内存。
  2. 没有全局可变状态保障。 你不能假设全局变量在两次请求之间保持不变(虽然实际上 Workers 有全局缓存,但这不是规范保证的)。
  3. CPU 时间有硬限制。 免费方案单次请求最多 10ms CPU 时间,超出即终止。
  4. 事件循环模型不同。 没有 Node.js 的 setImmediateprocess.nextTick,只有标准的 setTimeoutqueueMicrotask 和 Promise。

Hono 的设计天然契合这些约束:

无全局可变状态。 Hono 的 Context 对象是每次请求新建的,路由匹配结果也绑定在当前请求的上下文中。框架本身不维护跨请求的缓存、连接池或状态。

// Hono 的请求处理——每次请求都是独立的 Context
async function dispatchRequest(request: Request) {
  const c = new Context(request)  // 每次新建,无状态泄漏
  // ... 路由匹配 + 中间件执行
  return response
}

同步路由匹配。 Hono 的路由匹配是纯计算,不涉及 I/O,不产生 Promise 链。在 CPU 时间受限的 Edge 环境中,这意味着路由查找本身的开销可以忽略不计。

中间件编排不使用 Node.js 微任务 API。 Hono 的 compose 函数用标准 async/await 实现中间件链,不依赖 process.nextTicksetImmediate。这保证了在只有标准事件循环的 V8 Isolate 中也能正确执行。

// Hono compose 的核心逻辑(简化)
async function compose(middlewares: MiddlewareHandler[], c: Context) {
  let index = 0
  async function next() {
    if (index >= middlewares.length) return
    const handler = middlewares[index++]
    await handler(c, next)
  }
  await next()
}

这段代码里没有任何 Node.js 特有 API。它只使用 async/await——V8 引擎原生支持的语言特性。

6. 与 Express 的对比:为什么 Express 不能跑在 Edge

Express 无法运行在 Edge Runtime,不是一个「还没适配」的问题,而是架构层面的不可能。

Express 绑定 Node.js http 模块。 Express 的整个请求处理模型建立在 http.IncomingMessagehttp.ServerResponse 之上。这两个类是 Node.js 私有的,不属于任何 Web 标准。Edge Runtime 没有 http 模块,Express 连请求都接收不到。

// Express 的核心依赖——Edge 不可用
import http from 'node:http'
 
const server = http.createServer((req, res) => {
  // req 是 IncomingMessage,res 是 ServerResponse
  // 这两个类型在 Edge Runtime 中不存在
  app(req, res)
})

Express 使用 Node.js 流。 Express 的响应发送、静态文件服务、body parser 都依赖 Node.js 的 stream 模块。Edge Runtime 只有 Web Streams(ReadableStream),两者 API 完全不同。

Express 的中间件模型假定长生命周期进程。 Express 应用是一个持续运行的 Node.js 进程,中间件可以挂载全局状态、数据库连接池、文件缓存。V8 Isolate 模型不允许这种假设。

Express 的体积不适合 Edge。 ~200KB 的包体积对冷启动敏感的 Edge 环境来说过重。Edge 函数需要在毫秒级完成初始化和首次响应。

如果你想在 Edge 上跑类似 Express 的体验,Hono 就是为此设计的替代品。API 风格相近,但底层完全基于 Web Standards:

// Express 风格(Node.js only)
app.get('/users/:id', (req, res) => {
  res.json({ id: req.params.id })
})
 
// Hono 风格(Edge 兼容)
app.get('/users/:id', (c) => {
  return c.json({ id: c.req.param('id') })
})

7. Edge 环境下的注意事项

虽然 Hono 本身可以在 Edge 上运行,但你写的业务代码不一定能。以下是在 Edge 环境中开发 Hono 应用时需要注意的限制和替代方案:

不能用的 Node.js API 及替代方案

不可用 API替代方案
fs.readFile()静态资源放到 KV / R2 / 平台存储中,通过 fetch 或平台 SDK 读取
path.join()字符串拼接,或用 URL 对象处理路径
crypto.createHash()crypto.subtle.digest('SHA-256', data) — Web Crypto API
Buffer.from()new TextEncoder().encode() / new Uint8Array()
child_process.exec()不可用,Edge 无子进程能力
http.createServer()Hono 的 app.fetch() 即入口
stream.ReadableReadableStream — Web Streams API
setTimeout(fn, ms) 用于长延时部分平台限制定时器精度,避免依赖精确延时
process.env平台环境变量(Workers 用 env 参数传入)

CPU 时间限制的应对

Edge 平台对单次请求的 CPU 时间有严格限制。在编写中间件和路由时需要注意:

// ❌ 危险:大量同步计算可能触发 CPU 限制
app.get('/compute', (c) => {
  let result = 0
  for (let i = 0; i < 1e9; i++) {
    result += Math.sqrt(i)
  }
  return c.json({ result })
})
 
// ✅ 安全:将计算密集任务移到外部服务
app.get('/compute', async (c) => {
  const result = await fetch('https://compute-service.internal/heavy-task')
  return c.json(await result.json())
})

环境变量获取方式不同

Node.js 中通过 process.env.DATABASE_URL 读取环境变量。Edge 平台上(以 Cloudflare Workers 为例),环境变量通过 fetch 处理函数的第三个参数传入:

// Cloudflare Workers 入口
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // env 包含绑定的环境变量、KV namespace、D1 database 等
    const app = createApp(env)
    return app.fetch(request)
  },
}
 
// Hono 应用通过 c.env 访问
app.get('/api/data', (c) => {
  const dbUrl = c.env.DATABASE_URL
  // ...
})

不能读写本地文件

这是从 Node.js 迁移到 Edge 最常见的障碍。所有文件操作都需要替换为远程存储:

// ❌ Edge 不可用
import { readFileSync } from 'node:fs'
const template = readFileSync('./templates/email.html', 'utf-8')
 
// ✅ 替代方案 1:把模板内容内联或编译时注入
import emailTemplate from './templates/email.html?raw'
 
// ✅ 替代方案 2:从 KV / R2 读取
app.get('/send-email', async (c) => {
  const template = await c.env.KV.get('email-template')
  // ...
})

延伸阅读

总结

Hono 能运行在 Edge Runtime,不是后期适配的结果,而是架构设计的直接产物。零 Node.js 依赖、纯 Web Standards API、极小体积、无全局可变状态——这四条设计约束共同决定了 Hono 的 Edge 兼容性。

理解这一点的关键在于认清 Edge Runtime 的本质:它不是「阉割版的 Node.js」,而是「实现了 Web Standards 的 JavaScript 运行时」。Hono 选择站在 Web Standards 这一边,自然就能运行在所有实现了这些标准的平台上。

对于开发者来说,这意味着两件事:第一,用 Hono 写的代码天然具备跨平台能力,今天跑在 Cloudflare Workers 上,明天可以无缝迁移到 Deno Deploy 或 Vercel Edge Functions。第二,你在 Hono 里使用的 API(Request、Response、fetch)和你在前端写的 fetch('/api') 是同一套东西,前后端的 API 模型终于统一了。