JSX
Mit hono/jsx kannst du HTML in JSX-Syntax schreiben.
Obwohl hono/jsx auf dem Client funktioniert, wirst du es wahrscheinlich am häufigsten zum serverseitigen Rendern von Inhalten verwenden. Hier sind einige JSX-Funktionen, die Server und Client gemeinsam haben.
Einstellungen
Um JSX zu verwenden, ändere tsconfig.json:
tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}Alternativ kannst du Pragma-Direktiven verwenden:
/** @jsx jsx */
/** @jsxImportSource hono/jsx */Für Deno musst du statt tsconfig.json die Datei deno.json ändern:
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "@hono/hono/jsx"
}
}Verwendung
Info
Wenn du direkt vom Schnellstart kommst, hat die Hauptdatei die Erweiterung .ts. Du musst sie in .tsx ändern, sonst kannst du die Anwendung überhaupt nicht ausführen. Passe außerdem package.json oder bei Deno deno.json entsprechend an. Beispielsweise muss das Entwicklungsskript statt bun run --hot src/index.ts den Befehl bun run --hot src/index.tsx enthalten.
index.tsx:
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
<html>
<body>{props.children}</body>
</html>
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
<Layout>
<h1>Hello Hono!</h1>
<ul>
{props.messages.map((message) => {
return <li>{message}!!</li>
})}
</ul>
</Layout>
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html(<Top messages={messages} />)
})
export default appMetadaten in den Dokumentkopf verschieben
Du kannst Metadaten-Tags wie <title>, <link> und <meta> direkt in deinen Komponenten schreiben. Diese Tags werden automatisch in den Abschnitt <head> des Dokuments verschoben. Das ist besonders nützlich, wenn das Element <head> weit von der Komponente entfernt gerendert wird, die die passenden Metadaten bestimmt.
import { Hono } from 'hono'
const app = new Hono()
app.use('*', async (c, next) => {
c.setRenderer((content) => {
return c.html(
<html>
<head></head>
<body>{content}</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>About Page</title>
<meta name='description' content='This is the about page.' />
about page content
</>
)
})
export default appInfo
Dabei werden vorhandene Elemente nicht entfernt. Später auftretende Elemente werden am Ende hinzugefügt. Wenn beispielsweise in deinem <head> bereits <title>Default</title> steht und eine Komponente <title>Page Title</title> rendert, erscheinen beide Titel im Dokumentkopf.
Fragment
Mit Fragment kannst du mehrere Elemente gruppieren, ohne zusätzliche Knoten hinzuzufügen:
import { Fragment } from 'hono/jsx'
const List = () => (
<Fragment>
<p>first child</p>
<p>second child</p>
<p>third child</p>
</Fragment>
)Bei entsprechender Konfiguration kannst du auch <></> schreiben.
const List = () => (
<>
<p>first child</p>
<p>second child</p>
<p>third child</p>
</>
)PropsWithChildren
Mit PropsWithChildren kannst du ein untergeordnetes Element in einer Funktionskomponente korrekt typisieren lassen.
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren<Post>) {
return (
<div>
<h1>{title}</h1>
{children}
</div>
)
}Unverarbeitetes HTML einfügen
Um HTML direkt einzufügen, verwende dangerouslySetInnerHTML:
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div = <div dangerouslySetInnerHTML={inner} />
})Memoisierung
Optimiere deine Komponenten, indem du berechnete Zeichenketten mit memo memoisiert speicherst:
import { memo } from 'hono/jsx'
const Header = memo(() => <header>Welcome to Hono</header>)
const Footer = memo(() => <footer>Powered by Hono</footer>)
const Layout = (
<div>
<Header />
<p>Hono is cool!</p>
<Footer />
</div>
)Kontext
Mit useContext kannst du Daten global über jede Ebene des Komponentenbaums hinweg teilen, ohne Werte über Props weiterzugeben.
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: {
color: '#000000',
background: '#eeeeee',
},
dark: {
color: '#ffffff',
background: '#222222',
},
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return <button style={theme}>Push!</button>
}
const Toolbar: FC = () => {
return (
<div>
<Button />
</div>
)
}
// ...
app.get('/', (c) => {
return c.html(
<div>
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
</div>
)
})Asynchrone Komponenten
hono/jsx unterstützt asynchrone Komponenten, sodass du async/await in deiner Komponente verwenden kannst. Wenn du sie mit c.html() renderst, wird automatisch auf das Ergebnis gewartet.
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return <div>Done!</div>
}
app.get('/', (c) => {
return c.html(
<html>
<body>
<AsyncComponent />
</body>
</html>
)
})Suspense Experimental
Die React-ähnliche Funktion Suspense ist verfügbar. Wenn du eine asynchrone Komponente mit Suspense umschließt, wird zunächst der Fallback-Inhalt gerendert. Sobald das Promise aufgelöst ist, wird der erwartete Inhalt angezeigt. Du kannst sie mit renderToReadableStream() verwenden.
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
//...
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<Suspense fallback={<div>loading...</div>}>
<Component />
</Suspense>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})ErrorBoundary Experimental
Mit ErrorBoundary kannst du Fehler in untergeordneten Komponenten abfangen.
Im folgenden Beispiel wird bei einem Fehler der in fallback angegebene Inhalt angezeigt.
function SyncComponent() {
throw new Error('Error')
return <div>Hello</div>
}
app.get('/sync', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<SyncComponent />
</ErrorBoundary>
</body>
</html>
)
})ErrorBoundary kann auch mit asynchronen Komponenten und Suspense verwendet werden.
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return <div>Hello</div>
}
app.get('/with-suspense', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
</body>
</html>
)
})StreamingContext Experimental
Mit StreamingContext kannst du Streaming-Komponenten wie Suspense und ErrorBoundary konfigurieren. Das ist nützlich, um den von diesen Komponenten generierten script-Tags Nonce-Werte für die Content Security Policy (CSP) hinzuzufügen.
import { Suspense, StreamingContext } from 'hono/jsx/streaming'
// ...
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<StreamingContext
value={{ scriptNonce: 'random-nonce-value' }}
>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</StreamingContext>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
'Content-Security-Policy':
"script-src 'nonce-random-nonce-value'",
},
})
})Der Wert von scriptNonce wird automatisch zu allen <script>-Tags hinzugefügt, die die Komponenten Suspense und ErrorBoundary generieren.
Integration mit html-Middleware
Kombiniere die JSX- und HTML-Middleware für leistungsfähige Vorlagen. Ausführliche Informationen findest du in der Dokumentation zur HTML-Middleware.
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
interface SiteData {
title: string
children?: any
}
const Layout = (props: SiteData) =>
html`<!doctype html>
<html>
<head>
<title>${props.title}</title>
</head>
<body>
${props.children}
</body>
</html>`
const Content = (props: { siteData: SiteData; name: string }) => (
<Layout {...props.siteData}>
<h1>Hello {props.name}</h1>
</Layout>
)
app.get('/:name', (c) => {
const { name } = c.req.param()
const props = {
name: name,
siteData: {
title: 'JSX with html sample',
},
}
return c.html(<Content {...props} />)
})
export default appMit JSX-Renderer-Middleware
Die JSX-Renderer-Middleware erleichtert dir das Erstellen von HTML-Seiten mit JSX.
Typdefinitionen überschreiben
Du kannst die Typdefinition überschreiben, um eigene Elemente und Attribute hinzuzufügen.
declare module 'hono/jsx' {
namespace JSX {
interface IntrinsicElements {
'my-custom-element': HTMLAttributes & {
'x-event'?: 'click' | 'scroll'
}
}
}
}