Add live reload feature for development mode
This commit is contained in:
parent
143862059a
commit
edbdbe5146
25
README.md
25
README.md
|
|
@ -18,6 +18,7 @@
|
||||||
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
|
- 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`
|
- 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.
|
- 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 CSS reset.
|
||||||
- Optional pico.css.
|
- Optional pico.css.
|
||||||
|
|
||||||
|
|
@ -203,6 +204,30 @@ Test with curl:
|
||||||
curl -N http://localhost:3000/api/time
|
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
|
### utils
|
||||||
|
|
||||||
`hype` includes helpful utils for your webapp:
|
`hype` includes helpful utils for your webapp:
|
||||||
|
|
|
||||||
15
examples/live-reload/package.json
Normal file
15
examples/live-reload/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
examples/live-reload/src/client/main.ts
Normal file
1
examples/live-reload/src/client/main.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
console.log('Live reload is active in dev mode — no setup required.')
|
||||||
29
examples/live-reload/src/css/main.css
Normal file
29
examples/live-reload/src/css/main.css
Normal 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;
|
||||||
|
}
|
||||||
8
examples/live-reload/src/pages/index.tsx
Normal file
8
examples/live-reload/src/pages/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
5
examples/live-reload/src/server/index.ts
Normal file
5
examples/live-reload/src/server/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Hype } from '@because/hype'
|
||||||
|
|
||||||
|
const app = new Hype({})
|
||||||
|
|
||||||
|
export default app.defaults
|
||||||
|
|
@ -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()
|
const PICO_CSS = await Bun.file(join(import.meta.dir, '/css/pico.css')).text()
|
||||||
|
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
export { ReloadScript } from './layout'
|
||||||
export type { Context } from 'hono'
|
export type { Context } from 'hono'
|
||||||
|
|
||||||
const pageCache = new Map()
|
const pageCache = new Map()
|
||||||
|
|
@ -142,6 +143,11 @@ export class Hype<
|
||||||
if (this.routesRegistered) return
|
if (this.routesRegistered) return
|
||||||
this.routesRegistered = true
|
this.routesRegistered = true
|
||||||
|
|
||||||
|
// live reload endpoint (dev mode only)
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
this.sse('/__hype/reload', () => {})
|
||||||
|
}
|
||||||
|
|
||||||
// healthcheck
|
// healthcheck
|
||||||
if (this.props.ok)
|
if (this.props.ok)
|
||||||
this.get('/ok', c => c.text('ok'))
|
this.get('/ok', c => c.text('ok'))
|
||||||
|
|
@ -246,3 +252,4 @@ function findAvailablePort(startPort: number, maxAttempts = 100): number {
|
||||||
function render404(c: Context) {
|
function render404(c: Context) {
|
||||||
return c.text('File not found', 404)
|
return c.text('File not found', 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { type FC } from 'hono/jsx'
|
import { type FC } from 'hono/jsx'
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
const Layout: FC = ({ children, title, props }) =>
|
const Layout: FC = ({ children, title, props }) =>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -17,7 +19,13 @@ const Layout: FC = ({ children, title, props }) =>
|
||||||
<main>
|
<main>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
{isDev && <ReloadScript />}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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
|
export default Layout
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user