create-blokd 0.1.0-beta.2 → 0.1.0-beta.8

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.
Files changed (63) hide show
  1. package/README.md +34 -10
  2. package/bin/create-blokd.js +5 -2
  3. package/package.json +1 -1
  4. package/templates/dashboard/index.html +11 -0
  5. package/templates/dashboard/package.json +21 -0
  6. package/templates/dashboard/scripts/dev.mjs +105 -0
  7. package/templates/dashboard/src/blokd-env.d.ts +4 -0
  8. package/templates/dashboard/src/entry-client.ts +3 -0
  9. package/templates/dashboard/src/resumables/counter.ts +10 -0
  10. package/templates/dashboard/src/resumables/demo.ts +9 -0
  11. package/templates/dashboard/src/resumables/status.ts +10 -0
  12. package/templates/dashboard/src/routes/_404.tsx +11 -0
  13. package/templates/dashboard/src/routes/_error.tsx +17 -0
  14. package/templates/dashboard/src/routes/_layout.tsx +36 -0
  15. package/templates/dashboard/src/routes/about.tsx +23 -0
  16. package/templates/dashboard/src/routes/contact.tsx +58 -0
  17. package/templates/dashboard/src/routes/index.tsx +46 -0
  18. package/templates/dashboard/src/routes/reports.tsx +37 -0
  19. package/templates/dashboard/src/server.ts +19 -0
  20. package/templates/dashboard/tsconfig.json +17 -0
  21. package/templates/dashboard/vite.config.ts +11 -0
  22. package/templates/forms/index.html +11 -0
  23. package/templates/forms/package.json +21 -0
  24. package/templates/forms/scripts/dev.mjs +105 -0
  25. package/templates/forms/src/blokd-env.d.ts +4 -0
  26. package/templates/forms/src/entry-client.ts +3 -0
  27. package/templates/forms/src/resumables/counter.ts +10 -0
  28. package/templates/forms/src/resumables/demo.ts +9 -0
  29. package/templates/forms/src/routes/_404.tsx +11 -0
  30. package/templates/forms/src/routes/_error.tsx +17 -0
  31. package/templates/forms/src/routes/_layout.tsx +36 -0
  32. package/templates/forms/src/routes/about.tsx +23 -0
  33. package/templates/forms/src/routes/contact.tsx +58 -0
  34. package/templates/forms/src/routes/index.tsx +28 -0
  35. package/templates/forms/src/routes/newsletter.tsx +50 -0
  36. package/templates/forms/src/server.ts +19 -0
  37. package/templates/forms/tsconfig.json +17 -0
  38. package/templates/forms/vite.config.ts +11 -0
  39. package/templates/marketing/index.html +11 -0
  40. package/templates/marketing/package.json +21 -0
  41. package/templates/marketing/scripts/dev.mjs +105 -0
  42. package/templates/marketing/src/blokd-env.d.ts +4 -0
  43. package/templates/marketing/src/entry-client.ts +3 -0
  44. package/templates/marketing/src/resumables/counter.ts +10 -0
  45. package/templates/marketing/src/resumables/demo.ts +9 -0
  46. package/templates/marketing/src/routes/_404.tsx +11 -0
  47. package/templates/marketing/src/routes/_error.tsx +17 -0
  48. package/templates/marketing/src/routes/_layout.tsx +38 -0
  49. package/templates/marketing/src/routes/about.tsx +23 -0
  50. package/templates/marketing/src/routes/contact.tsx +19 -0
  51. package/templates/marketing/src/routes/index.tsx +24 -0
  52. package/templates/marketing/src/routes/pricing.tsx +24 -0
  53. package/templates/marketing/src/server.ts +19 -0
  54. package/templates/marketing/tsconfig.json +17 -0
  55. package/templates/marketing/vite.config.ts +11 -0
  56. package/templates/minimal/package.json +3 -3
  57. package/templates/minimal/scripts/dev.mjs +105 -0
  58. package/templates/minimal/src/resumables/counter.ts +10 -0
  59. package/templates/minimal/src/resumables/demo.ts +9 -4
  60. package/templates/minimal/src/routes/_layout.tsx +3 -1
  61. package/templates/minimal/src/routes/about.tsx +7 -1
  62. package/templates/minimal/src/routes/contact.tsx +58 -0
  63. package/templates/minimal/src/routes/index.tsx +14 -11
@@ -0,0 +1,19 @@
1
+ export const meta = () => ({
2
+ title: "Contact | Blokd App",
3
+ description: "Contact information for a marketing site."
4
+ });
5
+
6
+ export const runtime = "none";
7
+
8
+ export const budget = {
9
+ client: "0kb"
10
+ };
11
+
12
+ export default function Contact() {
13
+ return (
14
+ <section>
15
+ <h1>Contact</h1>
16
+ <p>Email hello@example.com to start a project.</p>
17
+ </section>
18
+ );
19
+ }
@@ -0,0 +1,24 @@
1
+ export const meta = () => ({
2
+ title: "Blokd Marketing",
3
+ description: "A static marketing site built with 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>Blokd Marketing</h1>
16
+
17
+ <p>
18
+ A server-rendered marketing starter with no client framework runtime.
19
+ </p>
20
+
21
+ <p><a href="/pricing">View pricing</a></p>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,24 @@
1
+ export const meta = () => ({
2
+ title: "Pricing | Blokd App",
3
+ description: "Static pricing page."
4
+ });
5
+
6
+ export const runtime = "none";
7
+
8
+ export const budget = {
9
+ client: "0kb"
10
+ };
11
+
12
+ export default function Pricing() {
13
+ return (
14
+ <section>
15
+ <h1>Pricing</h1>
16
+ <p>Simple static pricing tiers with no client JavaScript.</p>
17
+ <ul>
18
+ <li>Starter: $99</li>
19
+ <li>Growth: $299</li>
20
+ <li>Scale: custom</li>
21
+ </ul>
22
+ </section>
23
+ );
24
+ }
@@ -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
+ import { defineConfig } from "vite";
2
+ import { blokd } from "blokd/vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [
6
+ blokd({
7
+ routesDir: "src/routes",
8
+ clientEntry: "/src/entry-client.ts"
9
+ })
10
+ ]
11
+ });
@@ -5,12 +5,12 @@
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.0.0",
7
7
  "scripts": {
8
- "dev": "vite --host 0.0.0.0",
8
+ "dev": "node scripts/dev.mjs",
9
9
  "build": "vite build",
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "blokd": "0.1.0-beta.2",
13
+ "blokd": "0.2.0-beta.0",
14
14
  "hono": ">=4.5 <5"
15
15
  },
16
16
  "devDependencies": {
@@ -18,4 +18,4 @@
18
18
  "typescript": ">=5.5 <6",
19
19
  "vite": ">=6 <9"
20
20
  }
21
- }
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 CounterState = {
4
+ count: number;
5
+ };
6
+
7
+ export const increment = defineAction<CounterState>(({ state, el }) => {
8
+ state.count += 1;
9
+ el.text(`Count: ${state.count}`);
10
+ });
@@ -1,4 +1,9 @@
1
- export function sayHello(event: Event, ctx: any) {
2
- const button = event.currentTarget as HTMLButtonElement;
3
- button.textContent = ctx.state.message;
4
- }
1
+ import { defineAction } from "blokd/resume";
2
+
3
+ type MessageState = {
4
+ text: string;
5
+ };
6
+
7
+ export const show = defineAction<MessageState>(({ state, el }) => {
8
+ el.text(state.text);
9
+ });
@@ -24,6 +24,8 @@ export default function Layout(props: LayoutProps) {
24
24
  <a href="/">Home</a>
25
25
  {" · "}
26
26
  <a href="/about">About</a>
27
+ {" · "}
28
+ <a href="/contact">Contact</a>
27
29
  </nav>
28
30
  </header>
29
31
 
@@ -31,4 +33,4 @@ export default function Layout(props: LayoutProps) {
31
33
  </body>
32
34
  </html>
33
35
  );
34
- }
36
+ }
@@ -3,6 +3,12 @@ export const meta = () => ({
3
3
  description: "About this minimal Blokd application."
4
4
  });
5
5
 
6
+ export const runtime = "none";
7
+
8
+ export const budget = {
9
+ client: "0kb"
10
+ };
11
+
6
12
  export default function About() {
7
13
  return (
8
14
  <section>
@@ -14,4 +20,4 @@ export default function About() {
14
20
  </p>
15
21
  </section>
16
22
  );
17
- }
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
+ }
@@ -1,4 +1,4 @@
1
- import { signal, Island, resumable } from "blokd";
1
+ import { Island, on } from "blokd";
2
2
 
3
3
  export const meta = () => ({
4
4
  title: "Blokd App",
@@ -6,29 +6,32 @@ export const meta = () => ({
6
6
  });
7
7
 
8
8
  export default function Home() {
9
- const [count, setCount] = signal(0);
10
-
11
9
  return (
12
10
  <section>
13
11
  <h1>Blokd App</h1>
14
12
 
15
13
  <p>
16
- This page demonstrates Solid-familiar signals and a resumable island.
14
+ This page demonstrates resumable islands. Blokd does not hydrate the
15
+ whole component tree; client behavior is attached explicitly.
17
16
  </p>
18
17
 
19
- <button onClick={() => setCount(c => c + 1)}>
20
- Count: {count()}
21
- </button>
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>
22
26
 
23
- <Island name="demo-island" state={{ message: "Hello from Blokd" }}>
27
+ <Island name="demo-island" state={{ text: "Hello from Blokd" }}>
24
28
  <button
25
29
  type="button"
26
- data-output
27
- onClick={resumable("/src/resumables/demo.ts#sayHello")}
30
+ onClick={on("/src/resumables/demo.ts#show")}
28
31
  >
29
32
  Run resumable handler
30
33
  </button>
31
34
  </Island>
32
35
  </section>
33
36
  );
34
- }
37
+ }