create-blokd 0.1.0-beta.2 → 0.1.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -10
- package/bin/create-blokd.js +5 -2
- package/package.json +1 -1
- package/templates/dashboard/index.html +11 -0
- package/templates/dashboard/package.json +21 -0
- package/templates/dashboard/scripts/dev.mjs +105 -0
- package/templates/dashboard/src/blokd-env.d.ts +4 -0
- package/templates/dashboard/src/entry-client.ts +3 -0
- package/templates/dashboard/src/resumables/counter.ts +10 -0
- package/templates/dashboard/src/resumables/demo.ts +9 -0
- package/templates/dashboard/src/resumables/status.ts +10 -0
- package/templates/dashboard/src/routes/_404.tsx +11 -0
- package/templates/dashboard/src/routes/_error.tsx +17 -0
- package/templates/dashboard/src/routes/_layout.tsx +36 -0
- package/templates/dashboard/src/routes/about.tsx +23 -0
- package/templates/dashboard/src/routes/contact.tsx +58 -0
- package/templates/dashboard/src/routes/index.tsx +46 -0
- package/templates/dashboard/src/routes/reports.tsx +37 -0
- package/templates/dashboard/src/server.ts +19 -0
- package/templates/dashboard/tsconfig.json +17 -0
- package/templates/dashboard/vite.config.ts +11 -0
- package/templates/forms/index.html +11 -0
- package/templates/forms/package.json +21 -0
- package/templates/forms/scripts/dev.mjs +105 -0
- package/templates/forms/src/blokd-env.d.ts +4 -0
- package/templates/forms/src/entry-client.ts +3 -0
- package/templates/forms/src/resumables/counter.ts +10 -0
- package/templates/forms/src/resumables/demo.ts +9 -0
- package/templates/forms/src/routes/_404.tsx +11 -0
- package/templates/forms/src/routes/_error.tsx +17 -0
- package/templates/forms/src/routes/_layout.tsx +36 -0
- package/templates/forms/src/routes/about.tsx +23 -0
- package/templates/forms/src/routes/contact.tsx +58 -0
- package/templates/forms/src/routes/index.tsx +28 -0
- package/templates/forms/src/routes/newsletter.tsx +50 -0
- package/templates/forms/src/server.ts +19 -0
- package/templates/forms/tsconfig.json +17 -0
- package/templates/forms/vite.config.ts +11 -0
- package/templates/marketing/index.html +11 -0
- package/templates/marketing/package.json +21 -0
- package/templates/marketing/scripts/dev.mjs +105 -0
- package/templates/marketing/src/blokd-env.d.ts +4 -0
- package/templates/marketing/src/entry-client.ts +3 -0
- package/templates/marketing/src/resumables/counter.ts +10 -0
- package/templates/marketing/src/resumables/demo.ts +9 -0
- package/templates/marketing/src/routes/_404.tsx +11 -0
- package/templates/marketing/src/routes/_error.tsx +17 -0
- package/templates/marketing/src/routes/_layout.tsx +38 -0
- package/templates/marketing/src/routes/about.tsx +23 -0
- package/templates/marketing/src/routes/contact.tsx +19 -0
- package/templates/marketing/src/routes/index.tsx +24 -0
- package/templates/marketing/src/routes/pricing.tsx +24 -0
- package/templates/marketing/src/server.ts +19 -0
- package/templates/marketing/tsconfig.json +17 -0
- package/templates/marketing/vite.config.ts +11 -0
- package/templates/minimal/package.json +3 -3
- package/templates/minimal/scripts/dev.mjs +105 -0
- package/templates/minimal/src/resumables/counter.ts +10 -0
- package/templates/minimal/src/resumables/demo.ts +9 -4
- package/templates/minimal/src/routes/_layout.tsx +3 -1
- package/templates/minimal/src/routes/about.tsx +7 -1
- package/templates/minimal/src/routes/contact.tsx +58 -0
- package/templates/minimal/src/routes/index.tsx +14 -11
package/README.md
CHANGED
|
@@ -110,7 +110,6 @@ It includes:
|
|
|
110
110
|
- custom 404 page
|
|
111
111
|
- custom error page
|
|
112
112
|
- client entry for resumable islands
|
|
113
|
-
- one interactive signal example
|
|
114
113
|
- one resumable island example
|
|
115
114
|
- one static route
|
|
116
115
|
|
|
@@ -239,11 +238,21 @@ The goal is a small, explicit, Web Platform-oriented framework.
|
|
|
239
238
|
|
|
240
239
|
## Minimal starter walkthrough
|
|
241
240
|
|
|
242
|
-
|
|
241
|
+
Available templates:
|
|
242
|
+
|
|
243
|
+
~~~txt
|
|
244
|
+
minimal Balanced starter with islands, native forms, and static routes
|
|
245
|
+
forms Native form actions with no client runtime
|
|
246
|
+
dashboard Route-local islands plus static dashboard pages
|
|
247
|
+
marketing Static marketing pages with zero client budgets
|
|
248
|
+
~~~
|
|
249
|
+
|
|
250
|
+
The generated app has three public pages:
|
|
243
251
|
|
|
244
252
|
~~~txt
|
|
245
253
|
src/routes/index.tsx -> /
|
|
246
254
|
src/routes/about.tsx -> /about
|
|
255
|
+
src/routes/contact.tsx -> /contact
|
|
247
256
|
~~~
|
|
248
257
|
|
|
249
258
|
It also has three special route files:
|
|
@@ -257,11 +266,11 @@ src/routes/_error.tsx -> custom error page
|
|
|
257
266
|
The root page demonstrates:
|
|
258
267
|
|
|
259
268
|
- `signal`
|
|
260
|
-
- an inline event handler
|
|
261
269
|
- `Island`
|
|
262
270
|
- `resumable`
|
|
263
271
|
|
|
264
272
|
The about page demonstrates a static server-rendered page with no client interactivity.
|
|
273
|
+
The contact page demonstrates a native POST form action that re-renders success or validation UI without client JavaScript.
|
|
265
274
|
|
|
266
275
|
## Hono server entry
|
|
267
276
|
|
|
@@ -330,11 +339,11 @@ The Vite plugin is responsible for:
|
|
|
330
339
|
The starter includes a minimal resumable island:
|
|
331
340
|
|
|
332
341
|
~~~tsx
|
|
333
|
-
<Island name="demo-island" state={{
|
|
342
|
+
<Island name="demo-island" state={{ text: "Hello from Blokd" }}>
|
|
334
343
|
<button
|
|
335
344
|
type="button"
|
|
336
345
|
data-output
|
|
337
|
-
onClick={
|
|
346
|
+
onClick={on("/src/resumables/demo.ts#show")}
|
|
338
347
|
>
|
|
339
348
|
Run resumable handler
|
|
340
349
|
</button>
|
|
@@ -344,10 +353,15 @@ The starter includes a minimal resumable island:
|
|
|
344
353
|
The corresponding handler is:
|
|
345
354
|
|
|
346
355
|
~~~ts
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
356
|
+
import { defineAction } from "blokd/resume";
|
|
357
|
+
|
|
358
|
+
type MessageState = {
|
|
359
|
+
text: string;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export const show = defineAction<MessageState>(({ state, el }) => {
|
|
363
|
+
el.text(state.text);
|
|
364
|
+
});
|
|
351
365
|
~~~
|
|
352
366
|
|
|
353
367
|
Blokd resumability is island-scoped. It is not full application graph serialization.
|
|
@@ -356,12 +370,22 @@ Blokd resumability is island-scoped. It is not full application graph serializat
|
|
|
356
370
|
|
|
357
371
|
Blokd can analyze routes to determine whether they need the client entry.
|
|
358
372
|
|
|
359
|
-
A route with event handlers, signals, effects, `Island`, or `resumable` needs client behavior.
|
|
373
|
+
A route with event handlers, signals, effects, `Island`, `on`, or `resumable` needs client behavior.
|
|
360
374
|
|
|
361
375
|
A route without client markers can be server-rendered without framework client JavaScript.
|
|
362
376
|
|
|
363
377
|
This keeps simple pages small by default.
|
|
364
378
|
|
|
379
|
+
Static starter routes also declare:
|
|
380
|
+
|
|
381
|
+
~~~ts
|
|
382
|
+
export const budget = {
|
|
383
|
+
client: "0kb"
|
|
384
|
+
};
|
|
385
|
+
~~~
|
|
386
|
+
|
|
387
|
+
This fails the build if client behavior is accidentally added to those routes.
|
|
388
|
+
|
|
365
389
|
## Package manager behavior
|
|
366
390
|
|
|
367
391
|
`create-blokd` detects the package manager from the current npm user agent when possible.
|
package/bin/create-blokd.js
CHANGED
|
@@ -22,7 +22,7 @@ Usage:
|
|
|
22
22
|
yarn create blokd my-app
|
|
23
23
|
|
|
24
24
|
Options:
|
|
25
|
-
--template <name> Template to use. Default: minimal
|
|
25
|
+
--template <name> Template to use: minimal, forms, dashboard, marketing. Default: minimal
|
|
26
26
|
--install Install dependencies after creating the project
|
|
27
27
|
--no-install Do not install dependencies
|
|
28
28
|
--pm <name> Package manager: pnpm, npm, yarn, bun
|
|
@@ -31,6 +31,9 @@ Options:
|
|
|
31
31
|
Examples:
|
|
32
32
|
pnpm create blokd my-app
|
|
33
33
|
pnpm create blokd my-app --template minimal
|
|
34
|
+
pnpm create blokd my-app --template forms
|
|
35
|
+
pnpm create blokd my-app --template dashboard
|
|
36
|
+
pnpm create blokd my-app --template marketing
|
|
34
37
|
pnpm create blokd my-app --install
|
|
35
38
|
`;
|
|
36
39
|
|
|
@@ -240,4 +243,4 @@ function sanitizePackageName(name) {
|
|
|
240
243
|
.replace(/[._-]+$/, "") || "my-blokd-app";
|
|
241
244
|
}
|
|
242
245
|
|
|
243
|
-
main();
|
|
246
|
+
main();
|
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Blokd App</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<p>This file is used by Vite during development.</p>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blokd-dashboard-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.0.0",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "node scripts/dev.mjs",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"blokd": "0.2.0-beta.0",
|
|
14
|
+
"hono": ">=4.5 <5"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": ">=20 <23",
|
|
18
|
+
"typescript": ">=5.5 <6",
|
|
19
|
+
"vite": ">=6 <9"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { createServer as createViteServer } from "vite";
|
|
6
|
+
|
|
7
|
+
const host = process.env.HOST ?? "0.0.0.0";
|
|
8
|
+
const port = Number(process.env.PORT ?? 5173);
|
|
9
|
+
|
|
10
|
+
let handleRequest = (_req, res) => {
|
|
11
|
+
res.statusCode = 503;
|
|
12
|
+
res.end("Dev server is starting");
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const httpServer = createServer((req, res) => {
|
|
16
|
+
handleRequest(req, res);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const vite = await createViteServer({
|
|
20
|
+
appType: "custom",
|
|
21
|
+
server: {
|
|
22
|
+
middlewareMode: true,
|
|
23
|
+
hmr: {
|
|
24
|
+
server: httpServer
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
handleRequest = async (req, res) => {
|
|
30
|
+
vite.middlewares(req, res, async () => {
|
|
31
|
+
try {
|
|
32
|
+
const mod = await vite.ssrLoadModule("/src/server.ts");
|
|
33
|
+
const app = mod.default;
|
|
34
|
+
|
|
35
|
+
if (!app || typeof app.fetch !== "function") {
|
|
36
|
+
throw new Error("src/server.ts must default-export a Hono app.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const request = toWebRequest(req);
|
|
40
|
+
const response = await app.fetch(request);
|
|
41
|
+
|
|
42
|
+
await writeWebResponse(res, response);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
vite.ssrFixStacktrace(error);
|
|
45
|
+
|
|
46
|
+
res.statusCode = 500;
|
|
47
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
48
|
+
res.end(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
httpServer.listen(port, host, () => {
|
|
54
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
55
|
+
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(` Blokd dev server`);
|
|
58
|
+
console.log(` Local: http://${displayHost}:${port}/`);
|
|
59
|
+
console.log("");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function toWebRequest(req) {
|
|
63
|
+
const protocol = req.headers["x-forwarded-proto"] ?? "http";
|
|
64
|
+
const hostHeader = req.headers.host ?? `${host}:${port}`;
|
|
65
|
+
const url = `${protocol}://${hostHeader}${req.url ?? "/"}`;
|
|
66
|
+
|
|
67
|
+
const headers = new Headers();
|
|
68
|
+
|
|
69
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
for (const item of value) headers.append(key, item);
|
|
72
|
+
} else if (value !== undefined) {
|
|
73
|
+
headers.set(key, value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const init = {
|
|
78
|
+
method: req.method ?? "GET",
|
|
79
|
+
headers
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (init.method !== "GET" && init.method !== "HEAD") {
|
|
83
|
+
init.body = Readable.toWeb(req);
|
|
84
|
+
init.duplex = "half";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return new Request(url, init);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function writeWebResponse(res, response) {
|
|
91
|
+
res.statusCode = response.status;
|
|
92
|
+
res.statusMessage = response.statusText;
|
|
93
|
+
|
|
94
|
+
response.headers.forEach((value, key) => {
|
|
95
|
+
res.setHeader(key, value);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.body) {
|
|
99
|
+
res.end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const stream = Readable.fromWeb(response.body);
|
|
104
|
+
stream.pipe(res);
|
|
105
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineAction } from "blokd/resume";
|
|
2
|
+
|
|
3
|
+
type StatusState = {
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const refresh = defineAction<StatusState>(({ state, el }) => {
|
|
8
|
+
state.text = state.text === "Queue healthy" ? "Queue checked" : "Queue healthy";
|
|
9
|
+
el.text(state.text);
|
|
10
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type ErrorPageProps = {
|
|
2
|
+
error?: {
|
|
3
|
+
message?: string;
|
|
4
|
+
};
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default function ErrorPage(props: ErrorPageProps) {
|
|
8
|
+
return (
|
|
9
|
+
<section>
|
|
10
|
+
<h1>Something went wrong</h1>
|
|
11
|
+
<p>
|
|
12
|
+
{props.error?.message ??
|
|
13
|
+
"The application encountered an unexpected error."}
|
|
14
|
+
</p>
|
|
15
|
+
</section>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type LayoutProps = {
|
|
2
|
+
children?: unknown;
|
|
3
|
+
meta?: {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function Layout(props: LayoutProps) {
|
|
10
|
+
return (
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<title>{props.meta?.title ?? "Blokd App"}</title>
|
|
14
|
+
<meta
|
|
15
|
+
name="description"
|
|
16
|
+
content={props.meta?.description ?? "A minimal Blokd application."}
|
|
17
|
+
/>
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
+
</head>
|
|
20
|
+
|
|
21
|
+
<body>
|
|
22
|
+
<header>
|
|
23
|
+
<nav>
|
|
24
|
+
<a href="/">Home</a>
|
|
25
|
+
{" · "}
|
|
26
|
+
<a href="/reports">Reports</a>
|
|
27
|
+
{" · "}
|
|
28
|
+
<a href="/contact">Contact</a>
|
|
29
|
+
</nav>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<main>{props.children}</main>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const meta = () => ({
|
|
2
|
+
title: "About | Blokd App",
|
|
3
|
+
description: "About this minimal Blokd application."
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export const runtime = "none";
|
|
7
|
+
|
|
8
|
+
export const budget = {
|
|
9
|
+
client: "0kb"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function About() {
|
|
13
|
+
return (
|
|
14
|
+
<section>
|
|
15
|
+
<h1>About</h1>
|
|
16
|
+
|
|
17
|
+
<p>
|
|
18
|
+
This route has no event handlers or islands, so Blokd can treat it as a
|
|
19
|
+
static/server-rendered route.
|
|
20
|
+
</p>
|
|
21
|
+
</section>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
type ContactData = {
|
|
2
|
+
ok?: boolean;
|
|
3
|
+
email?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type ContactProps = {
|
|
8
|
+
data?: ContactData;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const meta = () => ({
|
|
12
|
+
title: "Contact | Blokd App",
|
|
13
|
+
description: "A native form route in a minimal Blokd application."
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const runtime = "none";
|
|
17
|
+
|
|
18
|
+
export const budget = {
|
|
19
|
+
client: "0kb"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function action({ request }: { request: Request }) {
|
|
23
|
+
const form = await request.formData();
|
|
24
|
+
const email = String(form.get("email") ?? "").trim();
|
|
25
|
+
|
|
26
|
+
if (!email.includes("@")) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: "Enter a valid email."
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
email
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function Contact(props: ContactProps) {
|
|
40
|
+
return (
|
|
41
|
+
<section>
|
|
42
|
+
<h1>Contact</h1>
|
|
43
|
+
|
|
44
|
+
<p>This form submits with native browser POST and no client JavaScript.</p>
|
|
45
|
+
|
|
46
|
+
{props.data?.error ? <p role="alert">{props.data.error}</p> : null}
|
|
47
|
+
{props.data?.ok ? <p>{`Subscribed ${props.data.email}`}</p> : null}
|
|
48
|
+
|
|
49
|
+
<form method="post">
|
|
50
|
+
<label>
|
|
51
|
+
Email
|
|
52
|
+
<input name="email" type="email" required />
|
|
53
|
+
</label>
|
|
54
|
+
<button>Subscribe</button>
|
|
55
|
+
</form>
|
|
56
|
+
</section>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Island, on } from "blokd";
|
|
2
|
+
|
|
3
|
+
export const meta = () => ({
|
|
4
|
+
title: "Dashboard | Blokd App",
|
|
5
|
+
description: "A dashboard starter using route-local islands."
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default function Home() {
|
|
9
|
+
return (
|
|
10
|
+
<section>
|
|
11
|
+
<h1>Dashboard</h1>
|
|
12
|
+
|
|
13
|
+
<p>
|
|
14
|
+
Interactive controls are isolated to the dashboard route. Static routes
|
|
15
|
+
still ship no client runtime.
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<Island name="counter" state={{ count: 0 }}>
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={on("/src/resumables/counter.ts#increment")}
|
|
22
|
+
>
|
|
23
|
+
Count: 0
|
|
24
|
+
</button>
|
|
25
|
+
</Island>
|
|
26
|
+
|
|
27
|
+
<Island name="demo-island" state={{ text: "Hello from Blokd" }}>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
onClick={on("/src/resumables/demo.ts#show")}
|
|
31
|
+
>
|
|
32
|
+
Run resumable handler
|
|
33
|
+
</button>
|
|
34
|
+
</Island>
|
|
35
|
+
|
|
36
|
+
<Island name="status" state={{ text: "Queue healthy" }}>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={on("/src/resumables/status.ts#refresh")}
|
|
40
|
+
>
|
|
41
|
+
Refresh status
|
|
42
|
+
</button>
|
|
43
|
+
</Island>
|
|
44
|
+
</section>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const meta = () => ({
|
|
2
|
+
title: "Reports | Blokd App",
|
|
3
|
+
description: "A static dashboard report route."
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export const runtime = "none";
|
|
7
|
+
|
|
8
|
+
export const budget = {
|
|
9
|
+
client: "0kb"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function Reports() {
|
|
13
|
+
return (
|
|
14
|
+
<section>
|
|
15
|
+
<h1>Reports</h1>
|
|
16
|
+
<p>This route is static and keeps the dashboard client runtime out.</p>
|
|
17
|
+
<table>
|
|
18
|
+
<thead>
|
|
19
|
+
<tr>
|
|
20
|
+
<th>Metric</th>
|
|
21
|
+
<th>Value</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<tr>
|
|
26
|
+
<td>Open tasks</td>
|
|
27
|
+
<td>12</td>
|
|
28
|
+
</tr>
|
|
29
|
+
<tr>
|
|
30
|
+
<td>Response time</td>
|
|
31
|
+
<td>42ms</td>
|
|
32
|
+
</tr>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
</section>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { createPages } from "blokd/hono";
|
|
3
|
+
import routes from "virtual:blokd/routes";
|
|
4
|
+
|
|
5
|
+
const app = new Hono();
|
|
6
|
+
|
|
7
|
+
app.get("/api/health", c => {
|
|
8
|
+
return c.json({ ok: true });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
app.route(
|
|
12
|
+
"/",
|
|
13
|
+
createPages({
|
|
14
|
+
routes,
|
|
15
|
+
entryClient: "/src/entry-client.ts"
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default app;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"jsxImportSource": "blokd",
|
|
8
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src", "vite.config.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Blokd App</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<p>This file is used by Vite during development.</p>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blokd-forms-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.0.0",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "node scripts/dev.mjs",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"blokd": "0.2.0-beta.0",
|
|
14
|
+
"hono": ">=4.5 <5"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": ">=20 <23",
|
|
18
|
+
"typescript": ">=5.5 <6",
|
|
19
|
+
"vite": ">=6 <9"
|
|
20
|
+
}
|
|
21
|
+
}
|