Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/add-h3-nitro-html-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@kitajs/h3-html-plugin': minor
'@kitajs/html': patch
'@kitajs/nitro-html-plugin': minor
---

Add H3 HTML responses and convention-based Nitro v3 pages with Suspense streaming
10 changes: 10 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pnpm dev

Check each example's directory for specific details and implementation patterns.

Featured examples:

- `h3-signal-room` streams independent environmental readings through H3.
- `nitro-night-atlas` demonstrates convention pages, dynamic routes, route groups, and a
catch-all Nitro renderer.

## Documentation

Learn more about the packages used in these examples:
Expand All @@ -31,6 +37,10 @@ Learn more about the packages used in these examples:
prevention
- **[@kitajs/fastify-html-plugin](../packages/fastify-html-plugin/README.md)** - Fastify
integration
- **[@kitajs/h3-html-plugin](../packages/h3-html-plugin/README.md)** - H3 responses and
factory-safe Suspense
- **[@kitajs/nitro-html-plugin](../packages/nitro-html-plugin/README.md)** - Nitro v3 page
conventions and renderer integration

## Getting Help

Expand Down
12 changes: 12 additions & 0 deletions examples/h3-signal-room/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# H3 Signal Room

A small streaming operations display built with H3 and Kita Html. The shell arrives
immediately, then independent sensor readings replace their fallbacks through
`AutoSuspense`.

```bash
pnpm start
```

Open `http://localhost:3000`. Use `/?station=aurora` to change the trusted station label
rendered with Kita's `safe` attribute.
22 changes: 22 additions & 0 deletions examples/h3-signal-room/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@kitajs/example-h3-signal-room",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "tsx --watch src/server.tsx",
"test": "xss-scan",
"test-types": "tsgo --noEmit"
},
"dependencies": {
"@kitajs/h3-html-plugin": "workspace:*",
"@kitajs/html": "workspace:*",
"@kitajs/ts-html-plugin": "workspace:*",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"h3": "catalog:",
"tslib": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:"
}
}
106 changes: 106 additions & 0 deletions examples/h3-signal-room/src/server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { defineKitaHandler, h3KitaHtml } from '@kitajs/h3-html-plugin'
import { AutoSuspense } from '@kitajs/html/suspense'
import { H3 } from 'h3'
import { serve } from 'h3/node'
import { readFileSync } from 'node:fs'
import { setTimeout } from 'node:timers/promises'

const app = new H3().register(h3KitaHtml())
const styles = readFileSync(new URL('./styles.css', import.meta.url), 'utf8')

type ReadingProps = {
delay: number
label: string
unit: string
value: string
}

async function Reading({ delay, label, unit, value }: ReadingProps) {
await setTimeout(delay)
return (
<article class="reading ready">
<span class="status">received</span>
<strong safe>{value}</strong>
<small safe>{unit}</small>
<p safe>{label}</p>
</article>
)
}

function PendingReading({ label }: Pick<ReadingProps, 'label'>) {
return (
<article class="reading pending">
<span class="status">listening</span>
<strong>···</strong>
<small>signal</small>
<p safe>{label}</p>
</article>
)
}

const signalRoom = defineKitaHandler((event) => {
const station = event.url.searchParams.get('station') || 'north-7'

return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Signal Room</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<main>
<header>
<p class="eyebrow">remote environmental array</p>
<h1>
Signal room <span safe>{station}</span>
</h1>
<p class="lede">
The frame is already here. Each instrument reports when its own reading is
ready.
</p>
</header>

<section class="grid" aria-label="Live station readings">
<AutoSuspense fallback={<PendingReading label="Upper-air temperature" />}>
<Reading
delay={450}
label="Upper-air temperature"
value="−41.8"
unit="°C"
/>
</AutoSuspense>
<AutoSuspense fallback={<PendingReading label="Solar wind velocity" />}>
<Reading delay={900} label="Solar wind velocity" value="428" unit="km/s" />
</AutoSuspense>
<AutoSuspense fallback={<PendingReading label="Magnetic inclination" />}>
<Reading
delay={1350}
label="Magnetic inclination"
value="67.2"
unit="deg"
/>
</AutoSuspense>
</section>

<footer>
<span>H3 request</span>
<code safe>{event.context.kitaHtml?.requestId}</code>
<a href="/?station=aurora">Retune to aurora</a>
</footer>
</main>
</body>
</html>
)
})

app.get('/', signalRoom)
app.get('/health', (event) => event.html(<strong>signal room online</strong>))
app.get(
'/styles.css',
() => new Response(styles, { headers: { 'content-type': 'text/css; charset=utf-8' } })
)

serve(app, { port: 3000 })
console.log('Signal Room listening on http://localhost:3000')
180 changes: 180 additions & 0 deletions examples/h3-signal-room/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
:root {
color-scheme: dark;
font-family: 'IBM Plex Mono', 'SFMono-Regular', monospace;
background: #081217;
color: #d6e5df;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 77% 18%, #17382f 0, transparent 30%),
linear-gradient(135deg, #081217, #0b181d);
}

body::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
opacity: 0.14;
background-image:
linear-gradient(#b6f09c 1px, transparent 1px),
linear-gradient(90deg, #b6f09c 1px, transparent 1px);
background-size: 48px 48px;
mask-image: linear-gradient(to bottom, black, transparent 82%);
}

main {
position: relative;
width: min(1080px, calc(100% - 32px));
margin: auto;
padding: clamp(48px, 9vw, 112px) 0 32px;
}

header {
max-width: 790px;
}

.eyebrow,
.status,
footer {
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
}

.eyebrow {
color: #8ca79e;
}

h1 {
margin: 16px 0;
font-family: Georgia, serif;
font-size: clamp(48px, 9vw, 108px);
font-weight: 400;
line-height: 0.86;
letter-spacing: -0.055em;
}

h1 span {
color: #b6f09c;
font-style: italic;
}

.lede {
max-width: 620px;
color: #9eb1ab;
font:
18px/1.65 system-ui,
sans-serif;
}

.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
margin: 64px 0 28px;
background: #355047;
border: 1px solid #355047;
}

.reading {
display: grid;
grid-template-columns: 1fr auto;
align-content: space-between;
min-height: 260px;
padding: 24px;
background: #0b171a;
}

.status {
grid-column: 1 / -1;
color: #70877f;
}

.ready .status {
color: #b6f09c;
}

.reading strong {
align-self: end;
font:
62px/0.9 Georgia,
serif;
letter-spacing: -0.05em;
}

.reading small {
align-self: end;
color: #b6f09c;
}

.reading p {
grid-column: 1 / -1;
margin: 24px 0 0;
color: #8ca79e;
}

.pending strong {
animation: pulse 1s steps(2, end) infinite;
}

footer {
display: flex;
gap: 18px;
align-items: center;
color: #70877f;
}

footer code {
color: #d6e5df;
}

footer a {
margin-left: auto;
color: #b6f09c;
text-underline-offset: 5px;
}

a:focus-visible {
outline: 2px solid #b6f09c;
outline-offset: 5px;
}

@keyframes pulse {
50% {
opacity: 0.25;
}
}

@media (max-width: 720px) {
.grid {
grid-template-columns: 1fr;
}

.reading {
min-height: 210px;
}

footer {
align-items: flex-start;
flex-wrap: wrap;
}

footer a {
width: 100%;
margin: 8px 0 0;
}
}

@media (prefers-reduced-motion: reduce) {
.pending strong {
animation: none;
}
}
10 changes: 10 additions & 0 deletions examples/h3-signal-room/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "@kitajs/html",
"plugins": [{ "name": "@kitajs/ts-html-plugin" }]
},
"include": ["src"]
}
2 changes: 2 additions & 0 deletions examples/nitro-night-atlas/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.nitro/
.output/
11 changes: 11 additions & 0 deletions examples/nitro-night-atlas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Nitro Night Atlas

A multi-page field guide showing Nitro's `server/pages` convention with static pages,
route groups, dynamic parameters, API priority, and a root catch-all renderer.

```bash
pnpm start
```

Open `http://localhost:3000`, then visit `/objects/orion`, `/observatory`, or any unknown
path to see the renderer.
10 changes: 10 additions & 0 deletions examples/nitro-night-atlas/nitro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { nitroKitaHtml } from '@kitajs/nitro-html-plugin'
import { defineConfig } from 'nitro'

export default defineConfig({
serverDir: './server',
modules: [nitroKitaHtml()],
routeRules: {
'/objects/**': { headers: { 'cache-control': 'public, max-age=60' } }
}
})
Loading
Loading