Relevant source files
The following files were used as context for generating this wiki page:
- src/jsx/jsx-runtime.ts
- src/jsx/base.ts
- src/jsx/components.ts
- src/jsx/dom/render.ts
- src/jsx/streaming.ts
- src/jsx/dom/intrinsic-element/components.ts
- src/jsx/intrinsic-element/components.ts
- src/jsx/jsx-dev-runtime.ts
- src/jsx/hooks/index.ts
- src/jsx/dom/jsx-dev-runtime.ts
- src/middleware/jsx-renderer/index.ts
- src/jsx/dom/server.ts
- src/jsx/dom/jsx-runtime.ts
- src/jsx/dom/client.ts
- src/jsx/dom/index.ts
- src/helper/html/index.ts
- src/jsx/dom/components.ts
- src/utils/html.ts
- src/jsx/index.ts
- src/jsx/dom/css.ts
- src/jsx/context.ts
- src/helper/css/index.ts
- runtime-tests/deno-jsx/deno.react-jsx.json
- runtime-tests/deno-jsx/deno.precompile.json
- src/jsx/children.ts
- src/jsx/dom/context.ts
- runtime-tests/deno/deno.json
- src/jsx/intrinsic-elements.ts
- src/jsx/types.ts
- src/jsx/constants.ts
Overview
Hono's JSX runtime provides both server-side rendering and client-side DOM execution models JSX Components and Context, Client DOM and Hooks. It processes functional components and intrinsic tags through unified entry points src/jsx/jsx-runtime.ts:6-8, src/jsx/jsx-dev-runtime.ts:12-25, validates and escapes attributes src/jsx/jsx-runtime.ts:16-49, and serializes hierarchical nodes into string buffers or browser DOM trees src/jsx/base.ts:249-302, src/jsx/dom/render.ts:41-80.
Sources: src/jsx/jsx-runtime.ts:6-8, src/jsx/jsx-dev-runtime.ts:12-25, src/jsx/base.ts:249-302, src/jsx/dom/render.ts:41-80, src/jsx/jsx-runtime.ts:16-49
JSX Runtime Entry Points
Automatic and Classic Runtimes
Hono supports both automatic (react-jsx) and development JSX transforms through dedicated module entry points runtime-tests/deno-jsx/deno.react-jsx.json:3-4, src/jsx/jsx-runtime.ts:6-8. The primary production runtime module hono/jsx/jsx-runtime exports jsx, jsxs, and Fragment directly from jsx-dev-runtime, alongside jsxTemplate (aliased from html) and utility helpers such as jsxAttr and jsxEscape [[src/jsx/jsx-runtime.ts:6-14], [src/jsx/jsx-runtime.ts:51]].
export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'
export { jsxDEV as jsxs } from './jsx-dev-runtime'
export type { JSX } from './jsx-dev-runtime'
import { html, raw } from '../helper/html'Sources: src/jsx/jsx-runtime.ts:6-9
The development runtime function jsxDEV accepts a tag, a props dictionary, and an optional key, normalizing single child properties and arrays into structured node hierarchies via jsxFn src/jsx/jsx-dev-runtime.ts:12-25.
export function jsxDEV(
tag: string | Function,
props: Record<string, unknown>,
key?: string
): JSXNode {
let node: JSXNode
if (!props || !('children' in props)) {
node = jsxFn(tag, props, [])
} else {
const children = props.children as string | HtmlEscapedString
node = Array.isArray(children) ? jsxFn(tag, props, children) : jsxFn(tag, props, [children])
}
node.key = key
return node
}Sources: src/jsx/jsx-dev-runtime.ts:12-25
Note
When configured with Deno and TypeScript compiler options like "jsx": "react-jsx" and "jsxImportSource": "hono/jsx", compiler targets resolve module specifiers against hono/jsx/jsx-runtime [[runtime-tests/deno-jsx/deno.react-jsx.json:3-4], [runtime-tests/deno-jsx/deno.react-jsx.json:10]].
Sources: runtime-tests/deno-jsx/deno.react-jsx.json:3-4, runtime-tests/deno-jsx/deno.react-jsx.json:10
Attribute Processing and Exports
The jsxAttr function evaluates attribute keys and dynamic values, checking attribute name validity and formatting styles, numbers, promises, or raw escaped strings into valid markup attributes src/jsx/jsx-runtime.ts:16-49.
| Parameter / Export | Type / Behavior | Source Reference |
|---|---|---|
jsx / jsxs | Aliased to jsxDEV for automatic JSX compilation | src/jsx/jsx-runtime.ts:6-7 |
Fragment | Component grouping container exported from dev runtime | src/jsx/jsx-runtime.ts:6 |
jsxTemplate | Aliased to html helper for template literals | src/jsx/jsx-runtime.ts:14 |
jsxAttr | Function validating attribute keys and formatting values | src/jsx/jsx-runtime.ts:16-49 |
jsxEscape | Identity helper function returning raw string values | src/jsx/jsx-runtime.ts:51 |
Sources: src/jsx/jsx-runtime.ts:6-14, src/jsx/jsx-runtime.ts:16-49, src/jsx/jsx-runtime.ts:51
JSX Node Hierarchy
Node Factories and Creation
The jsx factory function accepts a tag, a props dictionary, and variadic children, handling prop initialization and key extraction before delegating to jsxFn src/jsx/base.ts:305-317. The jsxFn function evaluates the tag type to instantiate the appropriate hierarchy node src/jsx/base.ts:320-352.
export const jsx = (tag: string | Function, props: Props | null, ...children: Child[]): JSXNode => {
props ??= {}
if (children.length) {
props.children = children.length === 1 ? children[0] : children
}
const key = props.key
delete props['key']
const node = jsxFn(tag, props, children)
node.key = key
return node
}Sources: src/jsx/base.ts:305-317
When tag is a function, jsxFn returns a JSXFunctionNode src/jsx/base.ts:330-331. Intrinsic element tags from lookup dictionaries instantiate JSXFunctionNode mapped to their intrinsic renderer src/jsx/base.ts:332-337. Namespace-sensitive tags such as svg and head wrap children in a namespace context component node inside a base JSXNode src/jsx/base.ts:338-349, while remaining tags construct a standard JSXNode src/jsx/base.ts:350-352.
Note
On the initial call to jsxFn, domRenderers bindings are assigned to intrinsic element tags using the DOM_RENDERER symbol property, initializing the client DOM rendering pipeline src/jsx/base.ts:319-328.
Sources: src/jsx/base.ts:319-328
Node Types and Specialization
Specialized node classes extend the hierarchy to handle functional evaluation and document fragments src/jsx/base.ts:255-303. The JSXFunctionNode executes functional components by invoking the component function with its properties and children, processing return values like promises, nested nodes, or escaped strings src/jsx/base.ts:255-296. The JSXFragmentNode implements container nodes that serialize their children directly to buffers without rendering surrounding tags src/jsx/base.ts:299-303.
class JSXFunctionNode extends JSXNode {
override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
const { children } = this
const props = { ...this.props }
if (children.length) {
props.children = children.length === 1 ? children[0] : children
}
const res = (this.tag as Function).call(null, props)
// ...
}
}Sources: src/jsx/base.ts:255-265
React-compatible element definitions and validation helpers support element checking and tree cloning [[src/jsx/types.ts:19-23], [src/jsx/base.ts:422-443]]. The isValidElement function validates unknown inputs src/jsx/base.ts:422-424, and cloneElement merges new properties and children into existing element nodes src/jsx/base.ts:426-443. Memoized functional components (memo) wrap components with shallowEqual checks to skip redundant computations src/jsx/base.ts:354-405.
| Utility / Class | Target Structure / Purpose | Source Reference |
|---|---|---|
JSXNode | Base structure for intrinsic and generic element nodes | src/jsx/base.ts:338-352 |
JSXFunctionNode | Executes functional components and intrinsic tag renderers | src/jsx/base.ts:255-296 |
JSXFragmentNode | Renders fragment children without enclosing HTML tags | src/jsx/base.ts:299-303 |
isValidElement | Type guard checking for tag and props properties | src/jsx/base.ts:422-424 |
cloneElement | Clones elements with merged property overrides | src/jsx/base.ts:426-443 |
memo | Wraps functional components with memoization and DOM_MEMO | src/jsx/base.ts:384-405 |
Sources: src/jsx/base.ts:255-303, src/jsx/base.ts:354-443, src/jsx/types.ts:19-23
Attribute Normalization and Escaping
Attribute Validation and Processing
The runtime formats and serializes element attributes using the jsxAttr utility src/jsx/jsx-runtime.ts:16-49. Before processing any value, jsxAttr validates the attribute name with isValidAttributeName; if validation fails, it returns an empty raw string src/jsx/jsx-runtime.ts:20-22. When the attribute name is valid, it initializes a string buffer formatted as key=" src/jsx/jsx-runtime.ts:23.
export const jsxAttr = (
key: string,
v: string | Promise<string> | Record<string, string | number | null | undefined | boolean>
): HtmlEscapedString | Promise<HtmlEscapedString> => {
if (!isValidAttributeName(key)) {
return raw('')
}
const buffer: StringBuffer = [`${key}="`] as StringBuffer
// ...Sources: src/jsx/jsx-runtime.ts:16-23
Style Stringification and Value Types
Attribute values are handled differently based on their runtime types src/jsx/jsx-runtime.ts:24-46. If the attribute key is style and the value is an object, styleObjectForEach iterates over the style properties, skipping null or undefined entries, separating properties with semicolons, and escaping the resulting string into the buffer src/jsx/jsx-runtime.ts:24-33.
if (key === 'style' && typeof v === 'object') {
// object to style strings
let styleStr = ''
styleObjectForEach(v as Record<string, string | number>, (property, value) => {
if (value != null) {
styleStr += `${styleStr ? ';' : ''}${property}:${value}`
}
})
escapeToBuffer(styleStr, buffer)
buffer[0] += '"'Sources: src/jsx/jsx-runtime.ts:24-33
Note
Null and undefined attribute values are suppressed entirely, returning an empty raw string instead of rendering empty attributes src/jsx/jsx-runtime.ts:37-38.
String values undergo HTML escaping via escapeToBuffer before closing quotation marks are appended src/jsx/jsx-runtime.ts:34-36. Numbers and already-escaped HTML strings append directly without re-escaping src/jsx/jsx-runtime.ts:39-40. Promise instances unshift the promise into the buffer for asynchronous resolution src/jsx/jsx-runtime.ts:41-42, while other types are converted to strings and escaped src/jsx/jsx-runtime.ts:43-45.
| Value Type | Processing Behavior | Source Reference |
|---|---|---|
| Invalid Name | Returns raw('') immediately | src/jsx/jsx-runtime.ts:20-22 |
| Style Object | Iterates properties, formats property:value; pairs, escapes buffer | src/jsx/jsx-runtime.ts:24-33 |
string | Escapes string content via escapeToBuffer | src/jsx/jsx-runtime.ts:34-36 |
null / undefined | Suppresses attribute, returns raw('') | src/jsx/jsx-runtime.ts:37-38 |
number / isEscaped | Appends directly to buffer without escaping | src/jsx/jsx-runtime.ts:39-40 |
Promise | Unshifts promise into buffer for async evaluation | src/jsx/jsx-runtime.ts:41-42 |
Sources: src/jsx/jsx-runtime.ts:20-46
Buffer Stringification Pipeline
Node Serialization and Promise Interception
Tree serialization converts hierarchical node structures into contiguous string chunks using a mutable StringBufferWithCallbacks buffer. The process evaluates functional components through JSXFunctionNode.toStringToBuffer, invoking component functions with props and child elements src/jsx/base.ts:255-264. When a component return value is a Promise, the pipeline intercepts it to support asynchronous rendering src/jsx/base.ts:269. If global contexts are active, the implementation captures current context bindings via globalContexts.map to preserve them across asynchronous yields src/jsx/base.ts:273-274. It then unshifts the promise into the buffer alongside an asynchronous callback that attaches captured local contexts to resolved node instances src/jsx/base.ts:275-283.
} else if (res instanceof Promise) {
if (globalContexts.length === 0) {
buffer.unshift('', res)
} else {
// save current contexts for resuming
const currentContexts: LocalContexts = globalContexts.map((c) => [c, c.values.at(-1)])
buffer.unshift(
'',
res.then((childRes) => {
if (childRes instanceof JSXNode) {
childRes.localContexts = currentContexts
}
return childRes
})
)
}Sources: src/jsx/base.ts:269-284
Warning
Async components executing without active global contexts omit the context-capturing map step, directly unshifting the promise to minimize overhead src/jsx/base.ts:270-272.
Primitive Handling and Fragment Flattening
Non-promise component outputs undergo strict type checks before buffer insertion src/jsx/base.ts:266-295. Booleans, null, and undefined values yield immediate termination without appending anything to the buffer src/jsx/base.ts:266-268. Fragment nodes delegate traversal directly to childrenToStringToBuffer src/jsx/base.ts:299-302, flattening child collections without introducing wrapper elements.
export class JSXFragmentNode extends JSXNode {
override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
childrenToStringToBuffer(this.children, buffer)
}
}Sources: src/jsx/base.ts:299-303]
Numbers and pre-escaped objects (isEscaped) append directly to the current buffer chunk, and any associated rendering callbacks are pushed onto the buffer's callback array src/jsx/base.ts:287-292. All other non-escaped primitives pass through escapeToBuffer to guarantee safety src/jsx/base.ts:294.
| Return Type | Serialization Action | Source Reference |
|---|---|---|
boolean / null / undefined | Discards output and returns immediately | src/jsx/base.ts:266-268 |
Promise | Unshifts promise and context capture onto the buffer | src/jsx/base.ts:269-284 |
JSXNode | Recursively invokes toStringToBuffer on the node | src/jsx/base.ts:285-286 |
number / isEscaped | Appends directly and preserves evaluation callbacks | src/jsx/base.ts:287-292 |
| Other Types | Encodes value via escapeToBuffer | src/jsx/base.ts:294 |
Sources: src/jsx/base.ts:266-296, src/jsx/base.ts:299-303
Child Collection Traversal
Array Normalization and Children Object Utilities
The toArray utility normalizes child node inputs into an array representation, wrapping non-array elements in a single-element array while passing arrays through unchanged src/jsx/children.ts:3-4. The Children namespace aggregates utility methods designed for functional inspection and transformation of child node trees src/jsx/children.ts:5-20.
export const toArray = (children: Child): Child[] =>
Array.isArray(children) ? children : [children]
export const Children = {
map: (children: Child[], fn: (child: Child, index: number) => Child): Child[] =>
toArray(children).map(fn),
forEach: (children: Child[], fn: (child: Child, index: number) => void): void => {
toArray(children).forEach(fn)
},
count: (children: Child[]): number => toArray(children).length,
only: (_children: Child[]): Child => {
const children = toArray(_children)
if (children.length !== 1) {
throw new Error('Children.only() expects only one child')
}
return children[0]
},
toArray,
}Sources: src/jsx/children.ts:3-20
| Method | Parameters | Return Type | Description | Sources Reference |
|---|---|---|---|---|
toArray | children: Child | Child[] | Normalizes single child or array into an array | src/jsx/children.ts:3-4 |
Children.map | children: Child[], fn: (child: Child, index: number) => Child | Child[] | Maps over normalized child collection | src/jsx/children.ts:6-7 |
Children.forEach | children: Child[], fn: (child: Child, index: number) => void | void | Iterates through normalized child collection | src/jsx/children.ts:8-10 |
Children.count | children: Child[] | number | Returns the total count of normalized children | src/jsx/children.ts:11 |
Children.only | _children: Child[] | Child | Asserts exactly one child exists and returns it | src/jsx/children.ts:12-18 |
Children.toArray | children: Child | Child[] | Alias to module-level toArray | src/jsx/children.ts:19 |
Sources: src/jsx/children.ts:3-20
Warning
Children.only evaluates normalized length and throws an explicit Error with message 'Children.only() expects only one child' if the count is anything other than 1 src/jsx/children.ts:12-17.
Sources: src/jsx/children.ts:12-18
Client JSX Runtime Adapters
DOM-Targeted JSX Factories and Server Emulation
The hono/jsx/dom runtime supplies adapters tailored for interactive client-side rendering and compatibility APIs mimicking react-dom/server [[src/jsx/dom/jsx-dev-runtime.ts:3-4], [src/jsx/dom/server.ts:3-4]]. The jsxDEV function constructs virtual DOM nodes while checking tag lookups against intrinsic element components src/jsx/dom/jsx-dev-runtime.ts:10-13. If a string tag matches an entry in intrinsic-element/components, the factory substitutes it before returning the descriptor object containing tag, type, props, key, and ref src/jsx/dom/jsx-dev-runtime.ts:11-20. The Fragment factory constructs an empty tag node utilizing jsxDEV with an empty string src/jsx/dom/jsx-dev-runtime.ts:23. The runtime exports jsx, jsxs, and Fragment through standard and dev entry modules src/jsx/dom/jsx-runtime.ts:6-8.
export const jsxDEV = (tag: string | Function, props: Props, key?: string): JSXNode => {
if (typeof tag === 'string' && intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {
tag = intrinsicElementTags[tag as keyof typeof intrinsicElementTags]
}
return {
tag,
type: tag,
props,
key,
ref: props.ref,
} as JSXNode
}
export const Fragment = (props: Record<string, unknown>): JSXNode => jsxDEV('', props, undefined)Sources: src/jsx/dom/jsx-dev-runtime.ts:10-23
Server-side rendering adapters in hono/jsx/dom/server provide renderToString and renderToReadableStream functions src/jsx/dom/server.ts:65. The renderToString implementation evaluates element stringification, logging a warning if options are provided, and throws an error if an asynchronous component is encountered src/jsx/dom/server.ts:21-30. The renderToReadableStream utility passes non-object elements through string conversion and delegates streaming output to renderToReadableStreamHono src/jsx/dom/server.ts:50-63.
const renderToString = (element: Child, options: RenderToStringOptions = {}): string => {
if (Object.keys(options).length > 0) {
console.warn('options are not supported yet')
}
const res = element?.toString() ?? ''
if (typeof res !== 'string') {
throw new Error('Async component is not supported in renderToString')
}
return res
}Sources: src/jsx/dom/server.ts:21-30
Warning
renderToString throws an explicit Error with the message 'Async component is not supported in renderToString' if the evaluated result is not a primitive string src/jsx/dom/server.ts:26-28.
Sources: src/jsx/dom/server.ts:26-28]
DOM Error Boundaries, Suspense, and Context
Specialized components for the DOM runtime handle asynchronous operations, boundary error trapping, and state context propagation [[src/jsx/dom/components.ts:7-38], [src/jsx/dom/context.ts:7-51]]. The ErrorBoundary component attaches a DOM_ERROR_HANDLER property to fragment nodes that intercepts errors, invokes optional onError callbacks, and returns fallback UI via fallbackRender or fallback props src/jsx/dom/components.ts:7-23. The Suspense component attaches a handler that checks whether caught errors are instances of Promise; if not, they are rethrown, otherwise err.finally(retry) is scheduled and the fallback child is returned src/jsx/dom/components.ts:25-38.
export const ErrorBoundary: FC<
PropsWithChildren<{
fallback?: Child
fallbackRender?: FallbackRender
onError?: ErrorHandler
}>
> = (({ children, fallback, fallbackRender, onError }: any) => {
const res = Fragment({ children })
;(res as any)[DOM_ERROR_HANDLER] = (err: any) => {
if (err instanceof Promise) {
throw err
}
onError?.(err)
return fallbackRender?.(err) || fallback
}
return res
}) as any
export const Suspense: FC<PropsWithChildren<{ fallback: any }>> = (({
children,
fallback,
}: any) => {
const res = Fragment({ children })
;(res as any)[DOM_ERROR_HANDLER] = (err: any, retry: () => void) => {
if (!(err instanceof Promise)) {
throw err
}
err.finally(retry)
return fallback
}
return res
}) as anySources: src/jsx/dom/components.ts:7-38
Context providers use createContextProviderFunction to push and pop context values into an internal array during tree traversal src/jsx/dom/context.ts:7-43. When rendering, the provider injects internal tag nodes that mutate the shared values array before and after evaluating children, and registers an error handler to pop values if rendering throws src/jsx/dom/context.ts:17-42. The createContext function initializes the default value array, binds Provider, and registers the context inside globalContexts src/jsx/dom/context.ts:45-51.
export const createContextProviderFunction =
<T>(values: T[]): Function =>
({ value, children }: { value: T; children: Child[] }) => {
if (!children) {
return undefined
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props: { children: any } = {
children: [
{
tag: setInternalTagFlag(() => {
values.push(value)
}),
props: {},
},
],
}
if (Array.isArray(children)) {
props.children.push(...children.flat())
} else {
props.children.push(children)
}
props.children.push({
tag: setInternalTagFlag(() => {
values.pop()
}),
props: {},
})
const res = { tag: '', props, type: '' }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(res as any)[DOM_ERROR_HANDLER] = (err: unknown) => {
values.pop()
throw err
}
return res
}Sources: src/jsx/dom/context.ts:7-43
Related
On This Page
OverviewJSX Runtime Entry PointsAutomatic and Classic RuntimesAttribute Processing and ExportsJSX Node HierarchyNode Factories and CreationNode Types and SpecializationAttribute Normalization and EscapingAttribute Validation and ProcessingStyle Stringification and Value TypesBuffer Stringification PipelineNode Serialization and Promise InterceptionPrimitive Handling and Fragment FlatteningChild Collection TraversalArray Normalization and Children Object UtilitiesClient JSX Runtime AdaptersDOM-Targeted JSX Factories and Server EmulationDOM Error Boundaries, Suspense, and ContextRelated