Hono - 適用於任何 JavaScript 運行時的 Web 框架!
大家好,我是 ConardLi。
今天跟大家來介紹一款 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
可以實現以下幾種使用場景:
-
構建 Web API
-
後端服務器的代理
-
CDN 前端
-
邊緣應用
-
庫的基礎服務器
-
全棧應用程序
目前,Hono 已被許多開發者和公司使用。例如,Unkey 使用 Hono 的 OpenAPI 功能將他們的應用程序部署到 Cloudflare Workers
。以下是部分使用 Hono 的公司:
-
Cloudflare
-
Nodecraft
-
OpenStatus
-
Unkey
-
Goens
-
NOT A HOTEL
-
CyberAgent
-
AI shift
-
Hanabi.rest
-
BaseAI
此外,諸如 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<Env>()
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 具備的中間件和輔助工具,能夠大大提升開發效率:
-
Basic Authentication
-
Bearer Authentication
-
Body Limit
-
Cache
-
Compress
-
Context Storage
-
Cookie
-
CORS
-
ETag
-
html
-
JSX
-
JWT Authentication
-
Logger
-
Pretty JSON
-
Secure Headers
-
SSG
-
Streaming
-
GraphQL Server
-
Firebase Authentication
-
Sentry 等
通過引入中間件,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<AppType>('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(<h1>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://hono.dev/docs/
-
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/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Qu9fkh0LxJF6xh-NxaFeIg