Compare commits

...

2 Commits

Author SHA1 Message Date
a974b15fd6 0.0.12 2026-03-27 16:11:28 -07:00
edbdbe5146 Add live reload feature for development mode 2026-03-27 16:11:26 -07:00
9 changed files with 99 additions and 1 deletions

View File

@ -18,6 +18,7 @@
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
- Transpile `.ts` files in `src/client/blah.ts` via `website.com/client/blah.ts`
- Default, simple HTML5 layout with working frontend transpilation/bundling, or supply your own.
- Live reload in development mode — pages automatically refresh when the server restarts.
- Optional CSS reset.
- Optional pico.css.
@ -203,6 +204,30 @@ Test with curl:
curl -N http://localhost:3000/api/time
```
### Live Reload
In development mode (`NODE_ENV !== 'production'`), `hype` automatically injects a live reload script into pages using the default layout. When the server restarts (e.g. via `bun --hot`), the browser will automatically reload the page.
If you're using a custom layout, you can add live reload by importing and rendering the `ReloadScript` component:
```tsx
import { ReloadScript } from "hype"
export default ({ children, title }: any) => (
<html lang="en">
<head>
<title>{title ?? "hype"}</title>
</head>
<body>
<main>{children}</main>
<ReloadScript />
</body>
</html>
)
```
`ReloadScript` only renders in development mode — it's a no-op in production.
### utils
`hype` includes helpful utils for your webapp:

View File

@ -0,0 +1,15 @@
{
"name": "hype-live-reload-example",
"module": "src/index.tsx",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"dependencies": {
"@because/hype": "*"
},
"scripts": {
"start": "bun run src/server/index.ts",
"dev": "bun run --hot src/server/index.ts"
}
}

View File

@ -0,0 +1 @@
console.log('Live reload is active in dev mode — no setup required.')

View File

@ -0,0 +1,29 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
justify-content: center;
padding-top: 4rem;
}
section {
max-width: 500px;
text-align: center;
}
code {
background: #f0f0f0;
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-size: 0.9em;
}
@media (prefers-color-scheme: dark) {
code {
background: #333;
}
}
.hint {
color: #888;
font-style: italic;
}

View File

@ -0,0 +1,8 @@
export default () => (
<section>
<h1>Live Reload Demo</h1>
<p>Run this with <code>bun run dev</code>, then edit this file.</p>
<p>The browser will automatically refresh no manual reload needed.</p>
<p class="hint">Try changing this text and saving!</p>
</section>
)

View File

@ -0,0 +1,5 @@
import { Hype } from '@because/hype'
const app = new Hype({})
export default app.defaults

View File

@ -1,6 +1,6 @@
{
"name": "@because/hype",
"version": "0.0.11",
"version": "0.0.12",
"module": "src/index.tsx",
"type": "module",
"exports": {

View File

@ -13,6 +13,7 @@ const CSS_RESET = await Bun.file(join(import.meta.dir, '/css/reset.css')).text()
const PICO_CSS = await Bun.file(join(import.meta.dir, '/css/pico.css')).text()
export * from './utils'
export { ReloadScript } from './layout'
export type { Context } from 'hono'
const pageCache = new Map()
@ -142,6 +143,11 @@ export class Hype<
if (this.routesRegistered) return
this.routesRegistered = true
// live reload endpoint (dev mode only)
if (process.env.NODE_ENV !== 'production') {
this.sse('/__hype/reload', () => {})
}
// healthcheck
if (this.props.ok)
this.get('/ok', c => c.text('ok'))
@ -246,3 +252,4 @@ function findAvailablePort(startPort: number, maxAttempts = 100): number {
function render404(c: Context) {
return c.text('File not found', 404)
}

View File

@ -1,5 +1,7 @@
import { type FC } from 'hono/jsx'
const isDev = process.env.NODE_ENV !== 'production'
const Layout: FC = ({ children, title, props }) =>
<html lang="en">
<head>
@ -17,7 +19,13 @@ const Layout: FC = ({ children, title, props }) =>
<main>
{children}
</main>
{isDev && <ReloadScript />}
</body>
</html>
const RELOAD_SCRIPT = '{let c=false;const e=new EventSource("/__hype/reload");e.onopen=()=>{if(c)location.reload();c=true};e.onerror=()=>{e.close();let d=50;setTimeout(function r(){fetch("/__hype/reload").then(()=>location.reload()).catch(()=>{d=Math.min(d*1.5,2000);setTimeout(r,d)})},d)}}'
export const ReloadScript: FC = () =>
isDev ? <script dangerouslySetInnerHTML={{ __html: RELOAD_SCRIPT }} /> : null
export default Layout