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
|
@@ -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,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="/contact">Contact</a>
|
|
27
|
+
{" · "}
|
|
28
|
+
<a href="/newsletter">Newsletter</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,28 @@
|
|
|
1
|
+
export const meta = () => ({
|
|
2
|
+
title: "Forms | Blokd App",
|
|
3
|
+
description: "Native form examples in Blokd."
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export const runtime = "none";
|
|
7
|
+
|
|
8
|
+
export const budget = {
|
|
9
|
+
client: "0kb"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function Home() {
|
|
13
|
+
return (
|
|
14
|
+
<section>
|
|
15
|
+
<h1>Forms</h1>
|
|
16
|
+
|
|
17
|
+
<p>
|
|
18
|
+
This starter demonstrates native forms and route actions without client
|
|
19
|
+
JavaScript.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<ul>
|
|
23
|
+
<li><a href="/contact">Contact form</a></li>
|
|
24
|
+
<li><a href="/newsletter">Newsletter form</a></li>
|
|
25
|
+
</ul>
|
|
26
|
+
</section>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
type NewsletterData = {
|
|
2
|
+
ok?: boolean;
|
|
3
|
+
email?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type NewsletterProps = {
|
|
8
|
+
data?: NewsletterData;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const meta = () => ({
|
|
12
|
+
title: "Newsletter | Blokd App",
|
|
13
|
+
description: "A native newsletter form."
|
|
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 { ok: false, error: "Enter a valid email." };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { ok: true, email };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function Newsletter(props: NewsletterProps) {
|
|
34
|
+
return (
|
|
35
|
+
<section>
|
|
36
|
+
<h1>Newsletter</h1>
|
|
37
|
+
|
|
38
|
+
{props.data?.error ? <p role="alert">{props.data.error}</p> : null}
|
|
39
|
+
{props.data?.ok ? <p>{`Subscribed ${props.data.email}`}</p> : null}
|
|
40
|
+
|
|
41
|
+
<form method="post">
|
|
42
|
+
<label>
|
|
43
|
+
Email
|
|
44
|
+
<input name="email" type="email" required />
|
|
45
|
+
</label>
|
|
46
|
+
<button>Subscribe</button>
|
|
47
|
+
</form>
|
|
48
|
+
</section>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -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-marketing-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,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,38 @@
|
|
|
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="/about">About</a>
|
|
27
|
+
{" · "}
|
|
28
|
+
<a href="/pricing">Pricing</a>
|
|
29
|
+
{" · "}
|
|
30
|
+
<a href="/contact">Contact</a>
|
|
31
|
+
</nav>
|
|
32
|
+
</header>
|
|
33
|
+
|
|
34
|
+
<main>{props.children}</main>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -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
|
+
}
|