Aller au contenu

Middleware JSX Renderer ​

Le middleware JSX Renderer permet de définir une mise en page pour le rendu JSX avec la fonction c.render(), sans utiliser c.setRenderer(). Il permet aussi d’accéder aux instances de Context dans les composants grâce à useRequestContext().

Importation ​

ts
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'

Utilisation ​

jsx
const app = new Hono()

app.get(
  '/page/*',
  jsxRenderer(({ children }) => {
    return (
      <html>
        <body>
          <header>Menu</header>
          <div>{children}</div>
        </body>
      </html>
    )
  })
)

app.get('/page/about', (c) => {
  return c.render(<h1>About me!</h1>)
})

Options ​

facultatif docType: boolean | string ​

Si vous ne souhaitez pas ajouter de DOCTYPE au début du HTML, définissez l’option docType sur false.

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>{children}</body>
        </html>
      )
    },
    { docType: false }
  )
)

Vous pouvez également spécifier le DOCTYPE.

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>{children}</body>
        </html>
      )
    },
    {
      docType:
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
    }
  )
)

facultatif stream: boolean | Record<string, string> ​

Si vous définissez cette option sur true ou fournissez une valeur Record, le rendu est envoyé sous forme de réponse en flux.

tsx
const AsyncComponent = async () => {
  await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
  return <div>Hi!</div>
}

app.get(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>
            <h1>SSR Streaming</h1>
            {children}
          </body>
        </html>
      )
    },
    { stream: true }
  )
)

app.get('/', (c) => {
  return c.render(
    <Suspense fallback={<div>loading...</div>}>
      <AsyncComponent />
    </Suspense>
  )
})

Si l’option vaut true, les en-têtes suivants sont ajoutés :

ts
{
  'Transfer-Encoding': 'chunked',
  'Content-Type': 'text/html; charset=UTF-8',
  'Content-Encoding': 'Identity'
}

Vous pouvez personnaliser les valeurs des en-têtes en spécifiant les valeurs du Record.

Options définies par une fonction ​

Vous pouvez passer une fonction recevant un objet Context à la place d’un objet d’options statique. Cela permet de définir dynamiquement les options selon le contexte de la requête, par exemple les variables d’environnement ou les paramètres de la requête.

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>{children}</body>
        </html>
      )
    },
    (c) => ({
      stream: c.req.header('X-Enable-Streaming') === 'true',
    })
  )
)

Par exemple, vous pouvez désactiver le streaming lors de la génération d’un site statique (SSG) avec <Suspense> en utilisant l’utilitaire isSSGContext :

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <div>
          <Suspense fallback={'loading...'}>
            <Component />
          </Suspense>
        </div>
      )
    },
    (c) => ({
      stream: !isSSGContext(c),
    })
  )
)

Mises en page imbriquées ​

Le composant Layout permet d’imbriquer les mises en page.

tsx
app.use(
  jsxRenderer(({ children }) => {
    return (
      <html>
        <body>{children}</body>
      </html>
    )
  })
)

const blog = new Hono()
blog.use(
  jsxRenderer(({ children, Layout }) => {
    return (
      <Layout>
        <nav>Blog Menu</nav>
        <div>{children}</div>
      </Layout>
    )
  })
)

app.route('/blog', blog)

useRequestContext() ​

useRequestContext() renvoie une instance de Context.

tsx
import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer'

const app = new Hono()
app.use(jsxRenderer())

const RequestUrlBadge: FC = () => {
  const c = useRequestContext()
  return <b>{c.req.url}</b>
}

app.get('/page/info', (c) => {
  return c.render(
    <div>
      You are accessing: <RequestUrlBadge />
    </div>
  )
})

Attention

Vous ne pouvez pas utiliser useRequestContext() avec l’option JSX precompile de Deno. Utilisez react-jsx :

json
   "compilerOptions": {
     "jsx": "precompile", 
     "jsx": "react-jsx", 
     "jsxImportSource": "hono/jsx"
   }
 }

Étendre ContextRenderer ​

En définissant ContextRenderer comme ci-dessous, vous pouvez passer du contenu supplémentaire au moteur de rendu. Cela est pratique, par exemple, pour modifier le contenu de la balise head selon la page.

tsx
declare module 'hono' {
  interface ContextRenderer {
    (
      content: string | Promise<string>,
      props: { title: string }
    ): Response
  }
}

const app = new Hono()

app.get(
  '/page/*',
  jsxRenderer(({ children, title }) => {
    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>
          <header>Menu</header>
          <div>{children}</div>
        </body>
      </html>
    )
  })
)

app.get('/page/favorites', (c) => {
  return c.render(
    <div>
      <ul>
        <li>Eating sushi</li>
        <li>Watching baseball games</li>
      </ul>
    </div>,
    {
      title: 'My favorites',
    }
  )
})

Publié sous licence MIT.