honojs / hono

Web framework built on Web Standards
https://hono.dev
MIT License
18.28k stars 515 forks source link

React + Vite SSR with Hono #3162

Open redbaron76 opened 1 month ago

redbaron76 commented 1 month ago

What is the feature you are proposing?

I'm trying to perform SSR using React + Vite in Hono instead of Express, as explained in all online examples. The problem is that Hono middlewares are not compatible with Connect instances, so it's not possible to achieve the same goal.

Vite server provides a vite.middlewares which is a Connect instance, good for Express but not for Hono 'cause it expects as req an IncomingMessage that Hono context doesn't provide.

Is there any plan to make Hono middlewares fully compatible with Connect or is there any walkaround to get compatibility or any example to follow?

Thanks

yusukebe commented 1 month ago

Hi @redbaron76

Have you ever tried @hono/vite-dev-server? https://github.com/honojs/vite-plugins/tree/main/packages/dev-server?

millsp commented 1 month ago

In my case, I can't use this vite dev server because it is the Remix server forwarding to hono, but not the opposite.

thgh commented 1 day ago

Hono does actually provide the IncomingMessage using the node-server: https://hono.dev/docs/getting-started/nodejs#access-the-raw-node-js-apis

import { serve, type HttpBindings } from '@hono/node-server'

const app = new Hono<{ Bindings: HttpBindings }>()
app.all(
  '/*',
  createMiddleware<{ Bindings: HttpBindings }>(
    (ctx, next) =>
      new Promise((resolve) =>
        viteServer.middlewares(ctx.env.incoming, ctx.env.outgoing, () =>
          resolve(next())
        )
      )
  )
)

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log('Listening on http://localhost:3000')
})

Got something working to get Hono & Vite in both Bun & Node. Not sure if it extends to SSR, only supports streaming in Node.js

// Usage
import { serve, type HttpBindings } from '@hono/node-server'

const app = new Hono<{ Bindings: HttpBindings }>()
app.all('/*', viteMiddleware())

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log('Listening on http://localhost:3000')
})

// Library
import { createServer, type ViteDevServer } from 'vite'
import { IncomingMessage, ServerResponse } from 'http'
import react from '@vitejs/plugin-react'
import { HttpBindings } from '@hono/node-server'
import { createMiddleware } from 'hono/factory'

let server: ViteDevServer

export async function getViteServer() {
  if (!server) {
    server = await createServer({
      ...
      appType: 'spa',
      plugins: [react()],
    })
  }
  return server
}

export function viteMiddleware() {
  return createMiddleware<{ Bindings: HttpBindings }>(async (ctx, next) => {
    const vite = await getViteServer()
    return new Promise((resolve) => {
      // Node.js
      // @ts-expect-error
      if (typeof Bun === 'undefined') {
        vite.middlewares(ctx.env.incoming, ctx.env.outgoing, () =>
          resolve(next())
        )
        return
      }

      // Bun
      let sent = false
      const headers = new Headers()
      vite.middlewares(
        {
          url: new URL(ctx.req.raw.url, 'http://localhost').pathname,
          method: ctx.req.raw.method,
          headers: Object.fromEntries(
            // @ts-expect-error
            ctx.req.raw.headers
          ),
        } as IncomingMessage,
        {
          setHeader(name, value: any) {
            headers.set(name, value)
            return this
          },
          end(body) {
            sent = true
            resolve(
              ctx.body(body, {
                status: this.statusCode,
                statusText: this.statusMessage,
                headers,
              })
            )
          },
        } as ServerResponse,
        () => sent || resolve(next())
      )
    })
  })
}