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@EdgeNode.js

Hono 框架的誕生可以追溯到三年前,即 2021 年 12 月。當時,Hono 的作者(一位 Cloudflare 員工)希望能夠爲 Cloudflare Workers 創建一個應用程序,但不使用框架的代碼會顯得非常冗長,而且找不到適合我需求的框架。Itty-router 雖然很好,但太簡單了;WorktopSunder 做的事情正是作者想做的,但它們的 API 不太符合作者的口味。另外作者還對創建一個基於 Trie 樹結構的路由器感興趣,因爲這種結構非常快。於是,作者開始構建一個帶有 Trie 樹路由的 Web 框架。

一次編寫,到處運行

Hono 的一個顯著特性就是它可以真正地 “一次編寫,到處運行”,不僅限於 Cloudflare Workers,還可以在 Deno、BunNode.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、SupabaseUpstash 等大型 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 具備的中間件和輔助工具,能夠大大提升開發效率:

通過引入中間件,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

最後

參考:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Qu9fkh0LxJF6xh-NxaFeIg