honox:基于Hono、Vite的快速全栈元框架,支持文件路由与Islands hydration

HonoX - Hono based meta framework

分支2Tags66
当前项目代码仓暂无内容

HonoX

HonoX 是一个简单快速的元框架,用于创建全栈网站或 Web API(前身为 Sonik)。它站在巨人的肩膀上,基于 HonoVite 和 UI 库构建而成。

注意HonoX 目前处于“alpha 阶段”。根据 零版本语义化版本控制,同一主版本内可能会引入破坏性变更。

特性

  • 基于文件的路由 - 你可以像使用 Next.js 一样创建大型应用。
  • 快速 SSR - 借助 Hono,渲染速度极快。
  • BYOR(自带渲染器) - 你可以使用自己的渲染器,而不仅限于使用 hono/jsx 的渲染器。
  • 孤岛 hydration - 如果需要交互功能,创建一个孤岛即可。只有该孤岛的 JavaScript 会被 hydration。
  • 中间件 - 它的工作方式与 Hono 一致,因此你可以使用大量 Hono 的中间件。

安装

你可以从 npm 安装 honox 包。

npm install hono honox

入门模板

如果您要开始一个新的 HonoX 项目,请使用 hono-create 命令。运行以下命令并选择 x-basic(使用箭头键查找该选项)。

npm create hono@latest

快速入门 - 基础篇

让我们使用 hono/jsx 作为渲染器创建一个基础的 HonoX 应用。此应用不包含客户端 JavaScript,通过服务器端渲染 JSX。

项目结构

以下是 HonoX 应用的典型项目结构。

.
├── app
│   ├── global.d.ts // global type definitions
│   ├── routes
│   │   ├── _404.tsx // not found page
│   │   ├── _error.tsx // error page
│   │   ├── _renderer.tsx // renderer definition
│   │   ├── merch
│   │   │   └── [...slug].tsx // matches `/merch/:category`, `/merch/:category/:item`, `/merch/:category/:item/:variant`
│   │   ├── about
│   │   │   └── [name].tsx // matches `/about/:name`
│   │   ├── blog
│   │   │   ├── index.tsx // matches /blog
│   │   │   └── (content)
│   │   │       ├── _renderer.tsx // renderer definition for routes inside this directory
│   │   │       └── [name].tsx    // matches `/blog/:name`
│   │   └── index.tsx // matches `/`
│   └── server.ts // server entry file
├── package.json
├── tsconfig.json
└── vite.config.ts

vite.config.ts

开发所需的最低 Vite 配置如下:

import { defineConfig } from 'vite'
import honox from 'honox/vite'

export default defineConfig({
  plugins: [honox()],
})

服务器入口文件

需要一个服务器入口文件。该文件应放置在 app/server.ts。在开发或构建阶段,Vite 会首先调用此文件。

在入口文件中,只需使用 createApp() 函数初始化你的应用。app 将是 Hono 的实例,因此你可以使用 Hono 的中间件以及 hono/dev 中的 showRoutes() 方法。

// app/server.ts
import { createApp } from 'honox/server'
import { showRoutes } from 'hono/dev'

const app = createApp()

showRoutes(app)

export default app

路由

定义路由有三种方式。

1. createRoute()

每个路由应返回一个 Handler | MiddlewareHandler 数组。createRoute() 是一个用于返回该数组的辅助函数。你可以使用 default export 编写 GET 请求的路由。

// app/routes/index.tsx
// `createRoute()` helps you create handlers
import { createRoute } from 'honox/factory'

export default createRoute((c) => {
  return c.render(
    <div>
      <h1>Hello!</h1>
    </div>
  )
})

你还可以通过 export POSTPUTDELETE 来处理除 GET 之外的其他方法。

// app/routes/index.tsx
import { createRoute } from 'honox/factory'
import { getCookie, setCookie } from 'hono/cookie'

export const POST = createRoute(async (c) => {
  const { name } = await c.req.parseBody<{ name: string }>()
  setCookie(c, 'name', name)
  return c.redirect('/')
})

export default createRoute((c) => {
  const name = getCookie(c, 'name') ?? 'no name'
  return c.render(
    <div>
      <h1>Hello, {name}!</h1>
      <form method='POST'>
        <input type='text' name='name' placeholder='name' />
        <input type='submit' />
      </form>
    </div>
  )
})

2. 使用 Hono 实例

您可以通过导出 Hono 对象的实例来创建 API 端点。

// app/routes/about/index.ts
import { Hono } from 'hono'

const app = new Hono()

// matches `/about/:name`
app.get('/:name', (c) => {
  const name = c.req.param('name')
  return c.json({
    'your name is': name,
  })
})

export default app

3. 直接返回 JSX

或者更简单地说,你可以直接返回 JSX。

// app/routes/index.tsx
export default function Home(_c: Context) {
  return <h1>Welcome!</h1>
}

渲染器

通过在 _renderer.tsx 中编写渲染器(即执行 c.setRender() 的中间件)来定义渲染器。

在编写 _renderer.tsx 之前,请在 global.d.ts 中编写渲染器类型定义。

// app/global.d.ts
import type {} from 'hono'

type Head = {
  title?: string
}

declare module 'hono' {
  interface ContextRenderer {
    (content: string | Promise<string>, head?: Head): Response | Promise<Response>
  }
}

JSX Renderer 中间件允许你按以下方式创建渲染器:

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'

export default jsxRenderer(({ children, title }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        {title ? <title>{title}</title> : <></>}
      </head>
      <body>{children}</body>
    </html>
  )
})

_renderer.tsx 会应用于每个目录下,而 app/routes/posts/_renderer.tsx 会应用于 app/routes/posts/* 中。

404 页面

你可以在 _404.tsx 中编写自定义的 404 页面。

// app/routes/_404.tsx
import { NotFoundHandler } from 'hono'

const handler: NotFoundHandler = (c) => {
  return c.render(<h1>Sorry, Not Found...</h1>)
}

export default handler

错误页面

您可以在 _error.tsx 中编写自定义错误页面。

// app/routes/_error.tsx
import { ErrorHandler } from 'hono'

const handler: ErrorHandler = (e, c) => {
  return c.render(<h1>Error! {e.message}</h1>)
}

export default handler

开始使用 - 带客户端

让我们创建一个包含客户端的应用程序。这里,我们将使用 hono/jsx/dom。

项目结构

以下是包含客户端的最小应用程序的项目结构:

.
├── app
│   ├── client.ts // client entry file
│   ├── global.d.ts
│   ├── islands
│   │   └── counter.tsx // island component
│   ├── routes
│   │   ├── _renderer.tsx
│   │   └── index.tsx
│   └── server.ts
├── package.json
├── tsconfig.json
└── vite.config.ts

渲染器

这是一个 _renderer.tsx 文件,它将为客户端加载 /app/client.ts 入口文件。它会根据 import.meta.env.PROD 变量加载生产环境的 JavaScript 文件。如果页面上存在 islands,则会渲染 <HasIslands /> 内部的内容。

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { HasIslands } from 'honox/server'

export default jsxRenderer(({ children }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        {import.meta.env.PROD ? (
          <HasIslands>
            <script type='module' src='/static/client.js'></script>
          </HasIslands>
        ) : (
          <script type='module' src='/app/client.ts'></script>
        )}
      </head>
      <body>{children}</body>
    </html>
  )
})

如果您的 dist/.vite/manifest.json 中有清单文件,可以使用 <Script /> 轻松编写。

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'

export default jsxRenderer(({ children }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        <Script src='/app/client.ts' />
      </head>
      <body>{children}</body>
    </html>
  )
})

注意:由于使用 <HasIslands /> 可能会对构建性能产生轻微影响,建议你不要在开发环境中使用它,而只在构建时使用。<Script /> 在开发过程中不会导致性能下降,因此最好使用它。

nonce 属性

如果你想为 <Script /><script /> 元素添加 nonce 属性,可以使用 安全头中间件

定义中间件:

// app/routes/_middleware.ts
import { createRoute } from 'honox/factory'
import { secureHeaders, NONCE } from 'hono/secure-headers'

export default createRoute(
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [NONCE],
    },
  })
)

你可以通过 c.get('secureHeadersNonce') 获取 nonce 值:

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'

export default jsxRenderer(({ children }, c) => {
  return (
    <html lang='en'>
      <head>
        <Script src='/app/client.ts' async nonce={c.get('secureHeadersNonce')} />
      </head>
      <body>{children}</body>
    </html>
  )
})

客户端入口文件

客户端入口文件应位于 app/client.ts。只需编写 createClient() 即可。

// app/client.ts
import { createClient } from 'honox/client'

createClient()

交互功能

如果您想为页面添加交互功能,请创建 Island 组件。Island 组件应满足以下要求:

  • 放置在 app/islands 目录下,或使用 $ 前缀命名,例如 $componentName.tsx
  • 应以 default 导出,或使用采用驼峰式命名(不含下划线且不全为大写)的适当组件名称导出。

例如,您可以编写如下计数器之类的交互组件:

// app/islands/counter.tsx
import { useState } from 'hono/jsx'

export default function Counter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

当你在路由文件中加载组件时,它会以服务器端渲染的方式呈现,同时 JavaScript 也会发送到客户端。

// app/routes/index.tsx
import { createRoute } from 'honox/factory'
import Counter from '../islands/counter'

export default createRoute((c) => {
  return c.render(
    <div>
      <h1>Hello</h1>
      <Counter />
    </div>
  )
})

注意:你无法在 Island 组件中访问 Context 对象。因此,你应该从 Island 外部的组件传递值。

import { useRequestContext } from 'hono/jsx-renderer'
import Counter from '../islands/counter.tsx'

export default function Component() {
  const c = useRequestContext()
  return <Counter init={parseInt(c.req.query('count') ?? '0', 10)} />
}

BYOR - 自带渲染器

您可以使用 React、Preact、Solid 或其他 UI 库来引入自己的渲染器。

注意:对于您引入的渲染器,我们可能不提供支持。

React 示例

您可以通过 @hono/react-renderer 定义渲染器。请先安装相关模块。

npm i @hono/react-renderer react react-dom hono
npm i -D @types/react @types/react-dom

global.d.ts 中定义渲染器将接收的 Props。

// global.d.ts
import '@hono/react-renderer'

declare module '@hono/react-renderer' {
  interface Props {
    title?: string
  }
}

以下是 app/routes/_renderer.tsx 的示例。

// app/routes/_renderer.tsx
import { reactRenderer } from '@hono/react-renderer'

export default reactRenderer(({ children, title }) => {
  return (
    <html lang='en'>
      <head>
        <meta charSet='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        {import.meta.env.PROD ? (
          <script type='module' src='/static/client.js'></script>
        ) : (
          <script type='module' src='/app/client.ts'></script>
        )}
        {title ? <title>{title}</title> : ''}
      </head>
      <body>{children}</body>
    </html>
  )
})

app/client.ts 文件将如下所示。

// app/client.ts
import { createClient } from 'honox/client'

createClient({
  hydrate: async (elem, root) => {
    const { hydrateRoot } = await import('react-dom/client')
    hydrateRoot(root, elem)
  },
  createElement: async (type: any, props: any) => {
    const { createElement } = await import('react')
    return createElement(type, props)
  },
})

vite.config.ts 中配置 react。

// vite.config.ts
import build from '@hono/vite-build/cloudflare-workers'
import honox from 'honox/vite'
import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: {
        rollupOptions: {
          input: ['./app/client.ts'],
          output: {
            entryFileNames: 'static/client.js',
            chunkFileNames: 'static/assets/[name]-[hash].js',
            assetFileNames: 'static/assets/[name].[ext]',
          },
        },
        emptyOutDir: false,
      },
    }
  } else {
    return {
      ssr: {
        external: ['react', 'react-dom'],
      },
      plugins: [honox(), build()],
    }
  }
})

调整 tsconfig.json 中的 jsx 工厂函数选项。

// tsconfig.json
{
  "compilerOptions": {
    ...
    "jsxImportSource": "react"
    ...
  }
}

使用 React 与 <Script />

如果您在 dist/.vite/manifest.json 中导出清单文件,就可以轻松使用 <Script /> 编写一些代码。

// app/routes/_renderer.tsx
import { reactRenderer } from '@hono/react-renderer'
import { Script } from 'honox/server'

export default reactRenderer(({ children, title }) => {
  return (
    <html lang='en'>
      <head>
        <meta charSet='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        <Script src='/app/client.ts' async />
        {title ? <title>{title}</title> : ''}
      </head>
      <body>{children}</body>
    </html>
  )
})

vite.config.ts 中配置 react。

// vite.config.ts
import build from '@hono/vite-build/cloudflare-workers'
import honox from 'honox/vite'
import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: {
        rollupOptions: {
          input: ['./app/client.ts'],
        },
        manifest: true,
        emptyOutDir: false,
      },
    }
  } else {
    return {
      ssr: {
        external: ['react', 'react-dom'],
      },
      plugins: [honox(), build()],
    }
  }
})

指南

嵌套布局

如果您正在使用 JSX Renderer 中间件,可以通过 <Layout /> 来嵌套布局。

// app/routes/posts/_renderer.tsx

import { jsxRenderer } from 'hono/jsx-renderer'

export default jsxRenderer(({ children, Layout }) => {
  return (
    <Layout>
      <nav>Posts Menu</nav>
      <div>{children}</div>
    </Layout>
  )
})

在嵌套布局中传递额外 Props

传递给嵌套渲染器的 Props 不会自动传播到父级渲染器。为确保父级布局接收到必要的 Props,您需要从嵌套的 <Layout /> 组件中显式传递它们。以下是实现方法:

让我们从路由处理器开始:

// app/routes/nested/index.tsx
export default createRoute((c) => {
  return c.render(<div>Content</div>, { title: 'Dashboard' })
})

现在,让我们来看看我们的嵌套渲染器:

// app/routes/nested/_renderer.tsx
export default jsxRenderer(({ children, Layout, title }) => {
  return (
    <Layout title={title}>
      {/* Pass the title prop to the parent renderer */}
      <main>{children}</main>
    </Layout>
  )
})

在这种设置中,发送到嵌套渲染器的 <Layout /> 的所有 props 都会被父渲染器消费:

// app/routes/_renderer.tsx
export default jsxRenderer(({ children, title }) => {
  return (
    <html lang='en'>
      <head>
        <title>{title}</title> {/* Use the title prop here */}
      </head>
      <body>
        {children} {/* Insert the Layout's children here */}
      </body>
    </html>
  )
})

使用中间件

你可以在每个根文件中使用 Hono 的中间件,语法与 Hono 相同。例如,要使用 Zod Validator 验证值,请按以下步骤操作:

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const schema = z.object({
  name: z.string().max(10),
})

export const POST = createRoute(zValidator('form', schema), async (c) => {
  const { name } = c.req.valid('form')
  setCookie(c, 'name', name)
  return c.redirect('/')
})

或者,你可以在目录中使用 _middleware.(ts|tsx) 文件,使该中间件应用于当前路由及其所有子路由。中间件将按照其在数组中的列出顺序运行。

// /app/routes/_middleware.ts
import { createRoute } from 'honox/factory'
import { logger } from 'hono/logger'
import { secureHeaders } from 'hono/secure-headers'

export default createRoute(logger(), secureHeaders(), ...<more-middleware>)

尾部斜杠

默认情况下,如果根文件是 index.tsxindex.mdx 等索引文件,尾部斜杠会被移除。 不过,如果你按以下方式将 trailingSlash 选项设置为 true,则不会移除尾部斜杠。

import { createApp } from 'honox/server'

const app = createApp({
  trailingSlash: true,
})

如下所示:

  • trailingSlash 设为 false(默认值):app/routes/path/index.mdx => /path
  • trailingSlash 设为 trueapp/routes/path/index.mdx => /path/

从路由中排除文件和目录

默认情况下,以 - 开头的目录和文件会从路由中排除。

示例:

routes/
├── posts.tsx
├── -post-list.tsx     // 👈🏼 ignored
├── -components/       // 👈🏼 ignored
│   ├── header.tsx     // 👈🏼 ignored
│   ├── footer.tsx     // 👈🏼 ignored
│   └── ...

在本示例中,routes/posts.tsx 会被路由至 /posts,但其他以 - 开头的项不会被路由。

此功能对于代码共存非常有用。

使用 Tailwind CSS

由于 HonoX 是以 Vite 为中心的,如果你希望使用 Tailwind CSS,基本上需遵循 官方指南

编写 app/style.css 时,你必须显式设置源检测的基准路径:

@import 'tailwindcss' source('../app');

在渲染器文件中导入它。使用 Link 组件将在构建后引用正确的 CSS 文件路径。

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Link } from 'honox/server'

export default jsxRenderer(({ children }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        <Link href='/app/style.css' rel='stylesheet' />
      </head>
      <body>{children}</body>
    </html>
  )
})

最后,添加 vite.config.ts 配置以输出生产环境的资源。

import honox from 'honox/vite'
import { defineConfig } from 'vite'
import build from '@hono/vite-build/cloudflare-workers'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    honox({
      client: {
        input: [
          '/app/client.ts', // the default value -> must be added if input is overridden
          '/app/style.css', // add the style file entrypoint
        ],
      },
    }),
    build(),
    tailwindcss(),
  ],
})

MDX

也可以使用 MDX。以下是 vite.config.ts

import devServer from '@hono/vite-dev-server'
import mdx from '@mdx-js/rollup'
import honox from 'honox/vite'
import remarkFrontmatter from 'remark-frontmatter'
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
import { defineConfig } from 'vite'

export default defineConfig(() => {
  return {
    plugins: [
      honox(),
      mdx({
        jsxImportSource: 'hono/jsx',
        remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
      }),
    ],
  }
})

可以创建博客网站。

// app/routes/index.tsx
import type { Meta } from '../types'

export default function Top() {
  const posts = import.meta.glob<{ frontmatter: Meta }>('./posts/*.mdx', {
    eager: true,
  })
  return (
    <div>
      <h2>Posts</h2>
      <ul class='article-list'>
        {Object.entries(posts).map(([id, module]) => {
          if (module.frontmatter) {
            return (
              <li>
                <a href={`${id.replace(/\.mdx$/, '')}`}>{module.frontmatter.title}</a>
              </li>
            )
          }
        })}
      </ul>
    </div>
  )
}

Cloudflare 绑定

如果您想在开发环境中使用 Cloudflare 的 Bindings,请创建 wrangler.jsonc 并进行正确配置。

// wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-project-name",
  "main": "./dist/index.js",
  "compatibility_date": "2025-08-03",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist",
  },
}

vite.config.ts 中,使用 @hono/vite-dev-server 中的 Cloudflare Adapter。

import honox from 'honox/vite'
import adapter from '@hono/vite-dev-server/cloudflare'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    honox({
      devServer: {
        adapter,
      },
    }),
  ],
})

SSG - 静态站点生成

借助 Hono 的 SSG 功能,你可以为每个路由生成静态 HTML。

// vite.config.ts
import { defineConfig } from 'vite'
import honox from 'honox/vite'
import ssg from '@hono/vite-ssg'

const entry = './app/server.ts'

export default defineConfig(() => {
  return {
    plugins: [honox(), ssg({ entry })],
  }
})

如果您想包含客户端脚本和资源:

// vite.config.ts
import ssg from '@hono/vite-ssg'
import honox from 'honox/vite'
import client from 'honox/vite/client'
import { defineConfig } from 'vite'

const entry = './app/server.ts'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      plugins: [client()],
    }
  } else {
    return {
      build: {
        emptyOutDir: false,
      },
      plugins: [honox(), ssg({ entry })],
    }
  }
})

构建命令(包含客户端):

vite build --mode client && vite build

部署

HonoX 由 Hono 实例和 Vite 配置组成,因此可以部署在任何 Hono 支持的平台上。 @hono/vite-build 提供了适用于 Cloudflare Workers、Node.js、Bun 以及许多其他平台的构建配置。

Cloudflare Workers

如果使用 create-hono 命令创建项目并选择 x-basic,则默认已包含部署到 Cloudflare Workers 的配置。

添加 wrangler.jsonc

// wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-project-name",
  "main": "./dist/index.js",
  "compatibility_date": "2025-08-03",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist",
  },
}

设置 vite.config.ts

// vite.config.ts
import build from '@hono/vite-build/cloudflare-workers'
import adapter from '@hono/vite-dev-server/cloudflare'
import tailwindcss from '@tailwindcss/vite'
import honox from 'honox/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    honox({
      devServer: { adapter },
      client: { input: ['/app/client.ts', '/app/style.css'] },
    }),
    tailwindcss(),
    build(),
  ],
})

构建命令(包含客户端):

vite build --mode client && vite build

构建完成后,使用以下命令进行部署。确保已安装 Wrangler

wrangler deploy

本地部署或其他平台

您可以为本地环境或各种其他平台构建 HonoX 应用。有关支持平台的完整列表,请参阅 @hono/vite-build 的 README。例如,您可以为 Bun 构建应用:

// vite.config.ts
import build from '@hono/vite-build/bun' // Change to Bun
import adapter from '@hono/vite-dev-server/bun' // Change to Bun
import tailwindcss from '@tailwindcss/vite'
import honox from 'honox/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    honox({
      devServer: { adapter },
      client: { input: ['/app/client.ts', '/app/style.css'] },
    }),
    tailwindcss(),
    build(),
  ],
})

构建命令(包含客户端):

vite build --mode client && vite build

使用 Bun 运行服务器:

cd ./dist && bun index.js

注意:在本地运行时,请确保将工作目录切换到输出目录(例如 cd ./dist)。否则,JavaScript、CSS 和静态资源可能无法正确解析。

在 HonoX 应用中使用环境变量时,你可能会发现 process.env.MY_VAR 这类变量在开发环境中有效,但在构建后的应用中却无效。这是因为 Vite 在构建过程中会将 process.env 优化为空对象。

要确保环境变量在运行时可用,请在你的 vite.config.ts 中添加以下内容:

export default defineConfig({
  define: {
    'process.env': 'process.env', // <=== Add this line
  },
  plugins: [
    honox({
      devServer: { adapter },
      client: { input: ['/app/client.ts', '/app/style.css'] },
    }),
    tailwindcss(),
    build(),
  ],
})

这会禁用 Vite 对 process.env 的优化,使构建后的应用能够按预期访问环境变量。有关更多详细信息,请参阅 issue #307

测试

集成测试

HonoX 中的集成测试旨在通过对 createApp() 创建的 Hono 实例使用 app.request() 来运行应用,并对响应进行断言。这些测试对于验证路由、中间件、渲染器、404 页面和其他功能的行为非常有用。

HonoX 在内部依赖于多个 Vite 特定的扩展。因此,我们建议使用 Vitest 运行集成测试,Vitest 是一个与 Vite 紧密集成的测试运行器。只有在其他测试运行器提供与 Vite 的兼容性时,才应使用它们。

如果您的项目尚未包含 Vitest,请将其作为开发依赖安装:

npm install -D vitest

以下是一个使用 Vitest 编写集成测试的极简示例:

// tests/integration/index.test.ts
import { describe, expect, it } from 'vitest'
import { createApp } from 'honox/server'

const app = createApp()

describe('Top page', () => {
  it("should return 'Hello, Hono!' when name query param is 'Hono'", async () => {
    const res = await app.request('/?name=Hono')
    const text = await res.text()
    expect(res.status).toBe(200)
    expect(res.headers.get('content-type')).toMatch(/text\/html/)
    expect(text).toMatch(/<h1[^>]*>\s*Hello, Hono!\s*<\/h1>/)
  })
})

你可以使用以下命令运行测试:

npx vitest run

当存在 vitest.config 文件时,Vitest 将优先使用该文件并覆盖 vite.config 中的设置。如果添加了 Vitest 特定配置,请确保将其与 Vite 配置合并。有关更多详细信息,请参阅 Configuring Vitest | Vitest

// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(
  viteConfig,
  defineConfig({
    // Your Vitest-specific configuration here
  })
)

示例

相关项目

作者

许可证

MIT

项目介绍

HonoX —— 基于Hono的元框架【此简介由AI生成】

定制我的领域
122.92 K94访问 GitHub