import type { Context } from 'hono' import type { Env, MiddlewareHandler } from 'hono/types' import React from 'react' import { renderToString, type RenderToReadableStreamOptions } from 'react-dom/server' import type { Props } from '.' type RendererOptions = { docType?: boolean | string stream?: boolean | Record readableStreamOptions?: RenderToReadableStreamOptions } type BaseProps = { c: Context children: React.ReactElement } const RequestContext = React.createContext(null) const createRenderer = (c: Context, component?: React.FC, options?: RendererOptions) => async (children: React.ReactElement, props?: Props) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const node = component ? component({ children, c, ...props }) : children if (options?.stream) { const { renderToReadableStream } = await import('react-dom/server') const stream = await renderToReadableStream( React.createElement(RequestContext.Provider, { value: c }, node), options.readableStreamOptions ) return c.body(stream, { headers: options.stream === true ? { 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html; charset=UTF-8', } : options.stream, }) } else { const docType = typeof options?.docType === 'string' ? options.docType : options?.docType === true ? '' : '' const body = docType + renderToString(React.createElement(RequestContext.Provider, { value: c }, node)) return c.html(body) } } export const reactRenderer = ( component?: React.FC, options?: RendererOptions ): MiddlewareHandler => function reactRenderer(c, next) { c.setRenderer(createRenderer(c, component, options)) return next() } // eslint-disable-next-line @typescript-eslint/no-explicit-any export const useRequestContext = (): Context => { const c = React.useContext(RequestContext) if (!c) { throw new Error('RequestContext is not provided.') } return c }