97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import { join } from 'path'
|
|
import { type Context, Hono } from 'hono'
|
|
import { serveStatic } from 'hono/bun'
|
|
import color from 'kleur'
|
|
|
|
import { transpile } from './utils'
|
|
import defaultLayout from './layout'
|
|
|
|
const SHOW_HTTP_LOG = true
|
|
const CSS_RESET = await Bun.file(join(import.meta.dir, '/reset.css')).text()
|
|
|
|
export class Hype extends Hono {
|
|
routesRegistered = false
|
|
|
|
constructor() {
|
|
super()
|
|
}
|
|
|
|
registerRoutes() {
|
|
if (this.routesRegistered) return
|
|
this.routesRegistered = true
|
|
|
|
// static assets in pub/
|
|
this.use('/*', serveStatic({ root: './pub' }))
|
|
|
|
// css lives in src/, close to real code
|
|
this.use('/css/*', serveStatic({ root: './src' }))
|
|
|
|
// console logging
|
|
this.use('*', async (c, next) => {
|
|
if (!SHOW_HTTP_LOG) return await next()
|
|
|
|
const start = Date.now()
|
|
await next()
|
|
const end = Date.now()
|
|
const fn = c.res.status < 400 ? color.green : c.res.status < 500 ? color.yellow : color.red
|
|
const method = c.req.method === 'GET' ? color.cyan(c.req.method) : color.magenta(c.req.method)
|
|
console.log(fn(`${c.res.status}`), `${color.bold(method)} ${c.req.url} (${end - start}ms)`)
|
|
})
|
|
|
|
// css reset
|
|
this.get('/css/reset.css', async c => new Response(CSS_RESET, {
|
|
headers: { 'Content-Type': 'text/css' }
|
|
}))
|
|
|
|
// serve transpiled js
|
|
this.on('GET', ['/js/:path{.+}', '/shared/:path{.+}'], async c => {
|
|
let path = './src/' + c.req.path.replace('..', '.')
|
|
|
|
// path must end in .js or .ts
|
|
if (!path.endsWith('.js') && !path.endsWith('.ts')) path += '.ts'
|
|
|
|
const ts = path.replace('.js', '.ts')
|
|
if (await Bun.file(ts).exists()) {
|
|
return new Response(await transpile(ts), { headers: { 'Content-Type': 'text/javascript' } })
|
|
} else if (await Bun.file(path).exists()) {
|
|
return new Response(Bun.file(path), { headers: { 'Content-Type': 'text/javascript' } })
|
|
} else {
|
|
return render404(c)
|
|
}
|
|
})
|
|
|
|
// file based routing
|
|
this.on('GET', ['/', '/:page'], async c => {
|
|
const pageName = (c.req.param('page') ?? 'index').replace('.', '')
|
|
if (pageName.startsWith('_')) return render404(c)
|
|
|
|
const path = join(process.env.PWD ?? '.', `./src/pages/${pageName}.tsx`)
|
|
|
|
if (!(await Bun.file(path).exists()))
|
|
return render404(c)
|
|
|
|
const layoutPath = join(process.env.PWD ?? '.', `./src/pages/_layout.tsx`)
|
|
let Layout = defaultLayout
|
|
if (await Bun.file(layoutPath).exists())
|
|
Layout = (await import(layoutPath + `?t=${Date.now()}`)).default
|
|
|
|
const page = await import(path + `?t=${Date.now()}`)
|
|
const innerHTML = typeof page.default === 'function' ? <page.default req={c.req} /> : page.default
|
|
const withLayout = Layout ? <Layout>{innerHTML}</Layout> : innerHTML
|
|
return c.html(withLayout)
|
|
})
|
|
}
|
|
|
|
get defaults() {
|
|
this.registerRoutes()
|
|
|
|
return {
|
|
fetch: this.fetch,
|
|
idleTimeout: 255
|
|
}
|
|
}
|
|
}
|
|
|
|
function render404(c: Context) {
|
|
return c.text('File not found', 404)
|
|
} |