Skip to content

Commit

Permalink
use hono as server
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito authored and kentcdodds committed Apr 29, 2024
1 parent 260a1a0 commit cdd8165
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"author": "Kent C. Dodds <[email protected]> (https://kentcdodds.com/)",
"license": "MIT",
"dependencies": {
"body-parser": "^1.20.2",
"@hono/node-server": "^1.11.1",
"busboy": "^1.6.0",
"close-with-grace": "^1.3.0",
"compression": "^1.7.4",
"express": "^4.19.1",
"hono": "^4.2.9",
"react": "19.0.0-beta-94eed63c49-20240425",
"react-dom": "19.0.0-beta-94eed63c49-20240425",
"react-error-boundary": "^4.0.13",
Expand Down
57 changes: 29 additions & 28 deletions exercises/05.actions/05.solution.history-revalidation/server/app.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import bodyParser from 'body-parser'
import { Readable } from 'node:stream'
import { serve } from '@hono/node-server'
import { serveStatic } from '@hono/node-server/serve-static'
import busboy from 'busboy'
import closeWithGrace from 'close-with-grace'
import compress from 'compression'
import express from 'express'
import { Hono } from 'hono'
import { compress } from 'hono/compress'
import { createElement as h } from 'react'
import {
renderToPipeableStream,
Expand All @@ -13,54 +15,54 @@ import { shipDataStorage } from './async-storage.js'

const PORT = process.env.PORT || 3000

const app = express()
const app = new Hono()

app.use(compress())
// this is here so the workshop app knows when the server has started
app.head('/', (req, res) => res.status(200).end())

app.use(express.static('public', { index: false }))
app.use('/js/src', express.static('src'))
app.use(serveStatic({ root: 'public', index: false }))
app.use('/js/src', serveStatic({ root: 'src' }))

// This just cleans up the URL if the search ever gets cleared... Not important
// for RSCs... Just ... I just can't help myself. I like URLs clean.
app.use((req, res, next) => {
app.use(({ req, redirect }) => {
if (req.query.search === '') {
const searchParams = new URLSearchParams(req.search)
const searchParams = new URLSearchParams()
searchParams.delete('search')
const location = [req.path, searchParams.toString()]
.filter(Boolean)
.join('?')
return res.redirect(302, location)
} else {
next()
return redirect(location, 302)
}
})

const moduleBasePath = new URL('../src', import.meta.url).href

async function renderApp(res, returnValue) {
async function renderApp(context, returnValue) {
const { req, res } = context
try {
const shipId = res.req.params.shipId || null
const search = res.req.query.search || ''
const shipId = req.param('shipId') || null
const search = req.query('search') || ''
const data = { shipId, search }
const readable = new Readable()
shipDataStorage.run(data, () => {
const root = h(App)
const payload = { root, returnValue }
const { pipe } = renderToPipeableStream(payload, moduleBasePath)
pipe(res)
pipe(readable)
})
return new Response(Readable.toWeb(readable))
} catch (error) {
console.error(error)
res.status(500).json({ error: error.message })
}
}

app.get('/rsc/:shipId?', async (req, res) => {
await renderApp(res, null)
app.get('/rsc/:shipId?', async context => {
await renderApp(context, null)
})

app.post('/action/:shipId?', bodyParser.text(), async (req, res) => {
const serverReference = req.get('rsc-action')
app.post('/action/:shipId?', async context => {
const serverReference = context.req.get('rsc-action')
const [filepath, name] = serverReference.split('#')
const action = (await import(filepath))[name]
// Validate that this is actually a function we intended to expose and
Expand All @@ -70,21 +72,20 @@ app.post('/action/:shipId?', bodyParser.text(), async (req, res) => {
throw new Error('Invalid action')
}

const bb = busboy({ headers: req.headers })
const bb = busboy({
headers: Object.fromEntries(context.req.raw.headers.entries()),
})
const reply = decodeReplyFromBusboy(bb, moduleBasePath)
req.pipe(bb)
Readable.fromWeb(context.req.raw.body).pipe(bb)
const args = await reply
const result = await action(...args)

await renderApp(res, result)
})

app.get('/:shipId?', async (req, res) => {
res.set('Content-type', 'text/html')
return res.sendFile('index.html', { root: 'public' })
})
app.get('/:shipId?', serveStatic({ root: 'public', path: 'index.html' }))

const server = app.listen(PORT, () => {
const server = serve({ port: PORT, fetch: app.fetch }, () => {
console.log(`🚀 We have liftoff!`)
console.log(`http://localhost:${PORT}`)
})
Expand Down
21 changes: 18 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cdd8165

Please sign in to comment.