今天跟大家来介绍一款 JavaScript 运行时框架 - Hono
,在 Github 上目前已经收获了 19.5K
Star!
Hono,寓意为 “火焰🔥”,是一个小巧、简单且超快速的 Web 框架,构建在 Web 标准之上。
Hono
号称可以在任何 JavaScript
运行时环境中运行,包括 Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda、Lambda@Edge
和 Node.js
。
Hono
框架的诞生可以追溯到三年前,即 2021 年 12 月。当时,Hono
的作者(一位 Cloudflare 员工)希望能够为 Cloudflare Workers
创建一个应用程序,但不使用框架的代码会显得非常冗长,而且找不到适合我需求的框架。Itty-router
虽然很好,但太简单了;Worktop
和 Sunder
做的事情正是作者想做的,但它们的 API
不太符合作者的口味。另外作者还对创建一个基于 Trie
树结构的路由器感兴趣,因为这种结构非常快。于是,作者开始构建一个带有 Trie
树路由的 Web
框架。
一次编写,到处运行
Hono
的一个显著特性就是它可以真正地“一次编写,到处运行”,不仅限于 Cloudflare Workers
,还可以在 Deno、Bun
和 Node.js
上运行。这主要归功于 Hono
不依赖于外部库,只使用了 Web
标准 API,而每个运行环境都支持这些 Web
标准。
让我们来看一个简单的例子:以下 src/index.ts
代码可以在 Cloudflare Workers、Deno 和 Bun 上运行:
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => c.text('code秘密花园:Hello Hono!'))
export default app
在 Cloudflare Workers 上运行这段代码,你需要执行以下命令:
wrangler dev src/index.ts
在 Deno 上运行:
deno serve src/index.ts
在 Bun 上运行:
bun run src/index.ts
这是一个简单的 “Hello World” 示例,但更复杂的应用程序也可以在 Cloudflare Workers 或其他运行时环境中运行。作为证明,几乎 Hono 所有的测试代码都可以在这些运行时中以同样的方式运行。这是真正的“一次编写,到处运行”体验。
或许你会疑惑,为什么 Cloudflare 的一名员工要创建一个可以在任何地方运行的框架?最初,Hono 只是为了与 Cloudflare Workers 配合使用而设计的。然而,从版本 2 开始,作者增加了对 Deno 和 Bun 的支持。这是一个非常明智的决定。如果 Hono 只针对 Cloudflare Workers,可能不会吸引那么多用户。通过在更多的运行时上运行,Hono 获得了更多的用户,从而发现更多的 bug 并获得更多的反馈,从而最终提高了软件质量。
速度极快
Hono 的路由器 RegExpRouter
是当前最快的路由器之一,避免了线性循环的使用,性能极其强悍。下表展示了 Hono 在 Cloudflare Workers
中的表现:
Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
Fastest is Hono
✨ Done in 28.06s.
谁在使用 Hono?
Hono
是一个类似于 Express
的简单 Web
应用框架,可以构建更加丰富的应用。通过结合中间件,Hono
可以实现以下几种使用场景:
目前,Hono 已被许多开发者和公司使用。例如,Unkey 使用 Hono 的 OpenAPI 功能将他们的应用程序部署到 Cloudflare Workers
。以下是部分使用 Hono 的公司:
此外,诸如 Prisma、Resend、Vercel AI SDK、Supabase
和 Upstash
等大型 Web 服务或库,也在其示例中使用了 Hono。
甚至有一些开发者将 Hono
作为 Express
的替代品。
Hono 和 Cloudflare 是完美的搭配
Hono 和 Cloudflare 的结合可以为开发者带来最好的体验。许多网站,包括 Cloudflare 的文档,都介绍了如下所示的 “原生”JavaScript 作为 Cloudflare Workers
的 “Hello World”:
export default {
fetch: () => {
return new Response('code秘密花园:Hello World!')
}
}
虽然这有助于理解 Workers 的原理,但如果你想创建一个返回 JSON 响应的接口,比如对请求 /books
的 GET 请求,你需要写类似这样的代码:
export default {
fetch: (req) => {
const url = new URL(req.url)
if (req.method === 'GET' && url.pathname === '/books') {
return Response.json({
ok: true
})
}
return Response.json(
{
ok: false
},
{
status: 404
}
)
}
}
使用 Hono,你可以这样编写:
import { Hono } from 'hono'
const app = new Hono()
app.get('/books', (c) => {
return c.json({
ok: true
})
})
export default app
代码不仅简洁,还能直观地理解它处理对 /books
的 GET 请求。如果需要处理 GET
请求到 /authors/yusuke
并从路径中提取变量 yusuke
(即 yusuke
是可变的),原生 JavaScript 代码如下:
if (req.method === 'GET') {
const match = url.pathname.match(/^\/authors\/([^\/]+)/)
if (match) {
const author = match[1]
return Response.json({
Author: author
})
}
}
使用 Hono,你不需要编写 if
语句,只需添加接口定义,更不需要编写正则表达式来获取变量 yusuke
,而是可以使用 c.req.param()
函数:
app.get('/authors/:name', (c) => {
const author = c.req.param('name')
return c.json({
Author: author
})
})
当路由越来越多时,代码维护将变得复杂。使用 Hono,代码会非常简洁易于维护。此外,Hono 使用“上下文模型”轻松处理绑定到 Cloudflare 的产品,如 KV、R2 和 D1。上下文是一个容器,持有应用程序的状态,直到接收到请求并返回响应。你可以使用上下文来检索请求对象、设置响应头以及创建自定义变量。它还包含 Cloudflare 的绑定。例如,如果你设置了名为 MY_KV
的 Cloudflare KV 命名空间,你可以通过 TypeScript 类型补全来访问它:
import { Hono } from 'hono'
type Env = {
Bindings: {
MY_KV: KVNamespace
}
}
const app = new Hono()
app.post('/message', async (c) => {
const message = c.req.query('message') ?? 'Hi'
await c.env.MY_KV.put('message', message)
return c.text('message is set', 201)
})
使用 Hono 代码书写简单直观,但没有任何限制。你可以使用 Hono 实现 Cloudflare Workers 所有可能的功能。
按需引用功能
Hono 非常小巧,使用最小的预设 hono/tiny
,你可以在 12 KB 内写一个 "Hello World" 程序。这是因为它仅使用运行时内置的 Web 标准 API,且功能最小化。相较之下,Express 的打包大小为 579 KB。
然而,你仍然可以实现许多功能。例如,实现基本身份验证略显麻烦,但 Hono 内置了基本身份验证中间件,你可以这样简单地把基本身份验证应用到 /auth/page
路径:
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
Hono 包包含的内置中间件还允许 Bearer 和 JWT 认证,以及 CORS 的简单配置。这些内置中间件不依赖外部库,但亦可使用许多第三方中间件,这些中间件允许使用外部库,例如使用 Clerk 和 Auth.js 进行身份验证的中间件,以及使用 Zod 和 Valibot 进行验证的中间件。
Hono 还提供了一些内置工具,如 Streaming 助手,对于实现 AI 功能非常有用。这些工具可以按需添加,并且只在添加时增加文件大小。在 Cloudflare Workers 中,Worker 的文件大小有一定限制。保持核心小巧,并通过中间件和助手扩展功能是非常合理的做法。
下面是一些 Hono 具备的中间件和辅助工具,能够大大提升开发效率:
通过引入中间件,Hono 可以实现更为复杂的功能。例如,添加基本身份验证中间件:
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')})
洋葱模型
Hono 的重要概念是“处理器”和“中间件”。处理器是用户定义的用来接收请求并返回响应的函数。例如,你可以写一个处理器,获取查询参数的值,从数据库中检索数据,并以 JSON 格式返回结果。中间件可以处理来到处理器的请求和处理器返回的响应。你可以将中间件与其他中间件结合起来,构建更大、更复杂的应用程序,结构像一个洋葱。
你可以非常简洁地创建中间件。例如,记录请求日志的自定义日志记录器可以这样编写:
app.use(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.path}`)
await next()
})
如果你想在响应中添加自定义头,可以这样写:
app.use(async (c, next) => {
await next()
c.header('X-Message', 'Hi, this is Hono!')
})
将其与 HTMLRewriter 结合起来会很有意思。如果一个接口返回 HTML,可以编写修改 HTML 标签的中间件,如下所示:
app.get('/pages/*', async (c, next) => {
await next()
class AttributeRewriter {
constructor(attributeName) {
this.attributeName = attributeName
}
element(element) {
const attribute = element.getAttribute(this.attributeName)
if (attribute) {
element.setAttribute(this.attributeName, attribute.replace('oldhost', 'newhost'))
}
}
}
const rewriter = new HTMLRewriter().on('a', new AttributeRewriter('href'))
const contentType = c.res.headers.get('Content-Type')
if (contentType!.startsWith('text/html')) {
c.res = rewriter.transform(c.res)
}
})
要创建中间件,你需要记住的内容很少,只需与上下文工作即可。
RPC 调用
Hono 拥有强大的类型系统。其中一个功能是 RPC(远程过程调用)。通过 RPC,你可以用 TypeScript 类型表达服务器端 API 规范。当这些类型在客户端作为泛型加载时,每个 API 端点的路径、参数和返回类型都会被推断出来,就像魔法一样。
例如,假设有一个用于创建博客文章的端点。这个端点接受一个 number
类型的 id
和一个 string
类型的 title
。使用 Zod(一个支持 TypeScript 推断的验证库),可以定义如下模式:
import { z } from 'zod'
const schema = z.object({
id: z.number(),
title: z.string()
})
创建一个处理程序,以 JSON 格式通过 POST 请求接收这个对象,并使用 Zod Validator 检查是否匹配模式。响应将具有一个名为 message
的字符串类型属性:
import { zValidator } from '@hono/zod-validator'
const app = new Hono().basePath('/v1')
// ...
const routes = app.post('/posts', zValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({
message: `${data.id.toString()} is ${data.title}`
})
})
这是一个“典型”的 Hono 处理程序,但是你可以通过 typeof
获取的 routes
类型将包含其 Web API 规范的信息。在此例中,它包括创建博客文章的端点——向 /posts
发送 POST 请求会返回一个 JSON 对象。
export type AppType = typeof routes
现在,我们来创建一个客户端。你将先前的 AppType
作为泛型传递给 Hono 客户端对象。
import { hc } from 'hono/client'
import { AppType } from '.'
const client = hc('http://localhost:8787')
设置完毕后,你就可以开始魔法操作了。代码补全工作完美无缺。当你写客户端代码时,不再需要完全了解 API 规范,这也有助于消除错误。
服务端 JSX
Hono 提供了内置的 JSX,这是一种允许你在 JavaScript 中编写类似 HTML 标签的代码的语法。提到 JSX,你可能首先想到 React,这是一个前端 UI 库。然而,Hono 的 JSX 最初是为了仅在服务器端运行而开发的。当首次开始开发 Hono 时,作者在寻找用于渲染 HTML 的模板引擎。大多数模板引擎,如 Handlebars 和 EJS,都在内部使用 eval
,而 eval
在 Cloudflare Workers 上不被支持。然后作者想到了使用 JSX。
Hono 的 JSX 独特之处在于它将标签视为一个字符串。因此,以下代码实际上是可行的:
console.log((<h1>Hello!h1>).toString())
不需要像在 React 中那样调用 renderToString()
。如果你想渲染 HTML,只需返回这个字符串即可:
app.get('/', (c) => c.html(Hello</h1>))
非常有趣的是创建 Suspense
—— React 中的一个特性,它允许你在等待异步组件加载时显示一个后备 UI —— 无需任何客户端实现。异步组件在仅服务器实现中运行。
服务器端 JSX 比你想象的要好玩。你可以用同样的方式为 Hono 的 JSX 复用 React 的 JSX 工具链,包括在编辑器中完成标签的功能,它们将成熟的前端技术带到了服务端。
编写测试
测试非常重要。幸运的是,使用 Hono 你可以轻松编写测试。
例如,让我们为一个接口编写测试。要测试对 /
的 GET 请求返回状态码 200
,你可以这样编写:
it('should return 200 response', async () => {
const res = await app.request('/')
expect(res.status).toBe(200)
})
很简单,这种测试的美妙之处在于你不需要启动服务器。Web 标准 API 将服务器层黑箱化。Hono 的内部测试代码有 20000 行,但大多数都像上面那样写成,不需要启动服务器。
走向全栈
Hono 于2024年2月发布了新的主要版本 4。有三个突出的主要功能:
通过这些功能,我们可以在 Hono 中创建具有用户界面的全栈应用程序。客户端组件的引入支持 JSX 在客户端中工作,我们可以为页面添加交互,静态站点生成允许我们创建博客等,而不必将它们打包成一个 JavaScript 文件。
Hono
还启动了一个名为 HonoX 的实验项目。这是一个使用 Hono 和 Vite 的元框架,提供基于文件的路由和将客户端组件与服务端生成的 HTML 相结合的机制。更容易创建与 Cloudflare Pages 或 Workers 完美匹配的大型应用程序。
此外,Hono
还计划将其作为现有全栈框架(如 Remix 和 Qwik)的基础服务器运行。与起始于客户端的 React 项目 Next.js 相比,Hono 尝试从服务器端成为全栈框架。
开始使用 Hono
要开始使用 Hono,只需以下几步:
通过 npm:
npm create hono@latest
通过 yarn:
yarn create hono
通过 pnpm:
pnpm create hono@latest
通过 bun:
bun create hono@latest
通过 deno:
deno run -A npm:create-hono@latest
最后
参考:
- https://github.com/honojs/honox
- https://github.com/honojs/hono/releases/tag/v4.0.0
- https://blog.cloudflare.com/the-story-of-web-framework-hono-from-the-creator-of-hono/