kilatjs 0.1.2 → 0.1.4

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 CHANGED
@@ -20,78 +20,18 @@ KilatJS is a server-first web framework inspired by the simplicity of PHP and th
20
20
 
21
21
  ---
22
22
 
23
- ## *Quick Start
23
+ ## Quick Start
24
24
 
25
- ### Installation
25
+ ### Create a New Project
26
26
 
27
- ```bash
28
- # Create a new project
29
- mkdir my-app && cd my-app
30
- bun init
31
-
32
- # Install KilatJS
33
- bun add kilatjs react react-dom
34
-
35
- # Install dev dependencies
36
- bun add -d @types/react @types/react-dom typescript tailwindcss
37
- ```
38
-
39
- ### Create Your First App
40
-
41
- **1. Create config file `kilat.config.ts`:**
42
-
43
- ```ts
44
- import { defineConfig } from "kilatjs";
45
-
46
- export default defineConfig({
47
- appDir: "./src",
48
- outDir: "./dist",
49
- port: 3000,
50
- tailwind: {
51
- enabled: true,
52
- inputPath: "./input.css",
53
- cssPath: "./styles.css",
54
- },
55
- });
56
- ```
57
-
58
- **2. Create input CSS `input.css`:**
59
-
60
- ```css
61
- @import "tailwindcss";
62
- ```
63
-
64
- **3. Create your first route `src/routes/index.tsx`:**
65
-
66
- ```tsx
67
- export const meta = {
68
- title: "Welcome to KilatJS",
69
- description: "A Bun-native, HTML-first framework",
70
- };
71
-
72
- export async function load() {
73
- return {
74
- message: "Hello from the server!",
75
- time: new Date().toLocaleTimeString(),
76
- };
77
- }
78
-
79
- export default function HomePage({ data }) {
80
- return (
81
- <div className="min-h-screen bg-gray-100 flex items-center justify-center">
82
- <div className="bg-white p-8 rounded-lg shadow-lg">
83
- <h1 className="text-3xl font-bold text-gray-900">{data.message}</h1>
84
- <p className="text-gray-600 mt-2">Server time: {data.time}</p>
85
- </div>
86
- </div>
87
- );
88
- }
89
- ```
90
-
91
- **4. Run the dev server:**
27
+ The easiest way to start is using the KilatJS CLI:
92
28
 
93
29
  ```bash
94
- bunx kilat dev
30
+ # Create a new project in my-app folder
31
+ bunx kilat create my-app
32
+ cd my-app
33
+ bun install
34
+ bun run dev
95
35
  ```
96
36
 
97
37
  Visit [http://localhost:3000](http://localhost:3000) 🎉
@@ -100,12 +40,13 @@ Visit [http://localhost:3000](http://localhost:3000) 🎉
100
40
 
101
41
  ## 📖 CLI Commands
102
42
 
103
- | Command | Description |
104
- |---------|-------------|
105
- | `kilat dev` | Start development server with HMR + Live Reload |
106
- | `kilat build` | Build for production |
107
- | `kilat serve` | Run production server (`bun dist/server.js`) |
108
- | `kilat preview` | Preview static files |
43
+ | Command | Description |
44
+ | -------------------- | ----------------------------------------------- |
45
+ | `kilat create [dir]` | Create a new project from template |
46
+ | `kilat dev` | Start development server with HMR + Live Reload |
47
+ | `kilat build` | Build for production |
48
+ | `kilat serve` | Run production server (`bun dist/server.js`) |
49
+ | `kilat preview` | Preview build output |
109
50
 
110
51
  ### Development Output
111
52
 
@@ -118,54 +59,42 @@ Visit [http://localhost:3000](http://localhost:3000) 🎉
118
59
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
119
60
  ```
120
61
 
121
- ### Build Output
122
-
123
- ```
124
- 🔨 KilatJS Production Build
125
-
126
- 🎨 Building Tailwind CSS...
127
-
128
- 📄 Routes Analysis:
129
- ─────────────────────────────────────────────────────────
130
- 📄 / SSR index.tsx
131
- 📄 /about SSR about.tsx
132
- ⚡ /api/posts API api/posts.ts
133
- 🔄 /blog/[slug] Dynamic SSR blog/[slug].tsx
134
- ─────────────────────────────────────────────────────────
135
- Total: 15 routes (10 SSR, 3 Dynamic, 2 API)
136
-
137
- ✅ Build Complete!
138
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
139
- Output: ./dist
140
- Start: bun ./dist/server.js
141
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
142
- ```
143
-
144
62
  ---
145
63
 
146
64
  ## 📁 Project Structure
147
65
 
66
+ KilatJS uses a simplified, root-level structure:
67
+
148
68
  ```
149
69
  my-app/
70
+ ├── public/ # Static assets & index.html
71
+ ├── routes/ # SSR routes (index.tsx, about.tsx)
72
+ ├── components/ # React/Kilat components
150
73
  ├── kilat.config.ts # Configuration
151
74
  ├── input.css # Tailwind input
152
- ├── styles.css # Generated CSS
153
- ├── src/
154
- │ ├── components/ # Shared components
155
- │ │ └── Layout.tsx
156
- │ └── routes/ # File-based routing
157
- │ ├── index.tsx # → /
158
- │ ├── about.tsx # → /about
159
- │ ├── blog/
160
- │ │ ├── index.tsx # → /blog
161
- │ │ └── [slug].tsx # → /blog/:slug
162
- │ └── api/
163
- │ └── posts.ts # → /api/posts
75
+ ├── index.client.tsx # (Optional) React client entry
76
+ ├── App.tsx # (Optional) Client-side app
164
77
  └── dist/ # Production build
165
78
  ```
166
79
 
167
80
  ---
168
81
 
82
+ ## ⚛️ React Client Support (Opt-in)
83
+
84
+ While KilatJS is HTML-first, you can easily add a full React client-side application for complex interactive features.
85
+
86
+ 1. **Auto-detection**: If `index.client.tsx` exists in your root, KilatJS will automatically bundle it.
87
+ 2. **Setup**: Create a `public/index.html` with a mount point:
88
+
89
+ ```html
90
+ <div id="root"></div>
91
+ <script type="module" src="/client/index.client.js"></script>
92
+ ```
93
+
94
+ 3. **Route**: Define `clientRoute: "/client"` in `kilat.config.ts` to host your SPA.
95
+
96
+ ---
97
+
169
98
  ## 📄 Route Contract
170
99
 
171
100
  Each route file can export:
@@ -173,30 +102,36 @@ Each route file can export:
173
102
  ```tsx
174
103
  // SEO meta tags
175
104
  export const meta = {
176
- title: "Page Title",
177
- description: "Page description",
178
- robots: "index,follow",
179
- ogTitle: "Open Graph Title",
180
- ogDescription: "OG description",
181
- ogImage: "https://example.com/image.jpg",
105
+ title: "Page Title",
106
+ description: "Page description",
107
+ robots: "index,follow",
108
+ ogTitle: "Open Graph Title",
109
+ ogDescription: "OG description",
110
+ ogImage: "https://example.com/image.jpg",
182
111
  };
183
112
 
184
113
  // Server-side data loading
185
114
  export async function load(ctx) {
186
- const data = await fetchData();
187
- return { items: data };
115
+ const data = await fetchData();
116
+ return { items: data };
188
117
  }
189
118
 
190
119
  // HTTP method handlers (POST, PUT, DELETE, etc.)
191
120
  export async function POST(ctx) {
192
- const formData = await ctx.request.formData();
193
- // Process form...
194
- return Response.redirect("/success", 302);
121
+ const formData = await ctx.request.formData();
122
+ // Process form...
123
+ return Response.redirect("/success", 302);
195
124
  }
196
125
 
197
126
  // Page component (receives data from load())
198
127
  export default function Page({ data, params, state }) {
199
- return <div>{data.items.map(item => <p>{item.name}</p>)}</div>;
128
+ return (
129
+ <div>
130
+ {data.items.map((item) => (
131
+ <p>{item.name}</p>
132
+ ))}
133
+ </div>
134
+ );
200
135
  }
201
136
  ```
202
137
 
@@ -210,28 +145,34 @@ export default function Page({ data, params, state }) {
210
145
 
211
146
  ```tsx
212
147
  interface LayoutProps {
213
- children: React.ReactNode;
214
- title?: string;
148
+ children: React.ReactNode;
149
+ title?: string;
215
150
  }
216
151
 
217
152
  export function Layout({ children, title = "My Blog" }: LayoutProps) {
218
- return (
219
- <div className="min-h-screen bg-gray-50">
220
- <header className="bg-white shadow">
221
- <nav className="max-w-4xl mx-auto px-4 py-4">
222
- <a href="/" className="text-xl font-bold">My Blog</a>
223
- <div className="float-right space-x-4">
224
- <a href="/" className="text-gray-600 hover:text-gray-900">Home</a>
225
- <a href="/blog" className="text-gray-600 hover:text-gray-900">Blog</a>
226
- <a href="/about" className="text-gray-600 hover:text-gray-900">About</a>
227
- </div>
228
- </nav>
229
- </header>
230
- <main className="max-w-4xl mx-auto px-4 py-8">
231
- {children}
232
- </main>
233
- </div>
234
- );
153
+ return (
154
+ <div className="min-h-screen bg-gray-50">
155
+ <header className="bg-white shadow">
156
+ <nav className="max-w-4xl mx-auto px-4 py-4">
157
+ <a href="/" className="text-xl font-bold">
158
+ My Blog
159
+ </a>
160
+ <div className="float-right space-x-4">
161
+ <a href="/" className="text-gray-600 hover:text-gray-900">
162
+ Home
163
+ </a>
164
+ <a href="/blog" className="text-gray-600 hover:text-gray-900">
165
+ Blog
166
+ </a>
167
+ <a href="/about" className="text-gray-600 hover:text-gray-900">
168
+ About
169
+ </a>
170
+ </div>
171
+ </nav>
172
+ </header>
173
+ <main className="max-w-4xl mx-auto px-4 py-8">{children}</main>
174
+ </div>
175
+ );
235
176
  }
236
177
  ```
237
178
 
@@ -243,37 +184,47 @@ export function Layout({ children, title = "My Blog" }: LayoutProps) {
243
184
  import { Layout } from "../../components/Layout";
244
185
 
245
186
  export const meta = {
246
- title: "Blog - My Site",
247
- description: "Read our latest articles",
187
+ title: "Blog - My Site",
188
+ description: "Read our latest articles",
248
189
  };
249
190
 
250
191
  export async function load() {
251
- // In real app, fetch from database
252
- const posts = [
253
- { id: 1, slug: "hello-world", title: "Hello World", excerpt: "My first post..." },
254
- { id: 2, slug: "getting-started", title: "Getting Started", excerpt: "Learn how to..." },
255
- ];
256
- return { posts };
192
+ // In real app, fetch from database
193
+ const posts = [
194
+ {
195
+ id: 1,
196
+ slug: "hello-world",
197
+ title: "Hello World",
198
+ excerpt: "My first post...",
199
+ },
200
+ {
201
+ id: 2,
202
+ slug: "getting-started",
203
+ title: "Getting Started",
204
+ excerpt: "Learn how to...",
205
+ },
206
+ ];
207
+ return { posts };
257
208
  }
258
209
 
259
210
  export default function BlogPage({ data }) {
260
- return (
261
- <Layout>
262
- <h1 className="text-3xl font-bold mb-8">Blog</h1>
263
- <div className="space-y-6">
264
- {data.posts.map(post => (
265
- <article key={post.id} className="bg-white p-6 rounded-lg shadow">
266
- <h2 className="text-xl font-semibold">
267
- <a href={`/blog/${post.slug}`} className="hover:text-blue-600">
268
- {post.title}
269
- </a>
270
- </h2>
271
- <p className="text-gray-600 mt-2">{post.excerpt}</p>
272
- </article>
273
- ))}
274
- </div>
275
- </Layout>
276
- );
211
+ return (
212
+ <Layout>
213
+ <h1 className="text-3xl font-bold mb-8">Blog</h1>
214
+ <div className="space-y-6">
215
+ {data.posts.map((post) => (
216
+ <article key={post.id} className="bg-white p-6 rounded-lg shadow">
217
+ <h2 className="text-xl font-semibold">
218
+ <a href={`/blog/${post.slug}`} className="hover:text-blue-600">
219
+ {post.title}
220
+ </a>
221
+ </h2>
222
+ <p className="text-gray-600 mt-2">{post.excerpt}</p>
223
+ </article>
224
+ ))}
225
+ </div>
226
+ </Layout>
227
+ );
277
228
  }
278
229
  ```
279
230
 
@@ -285,42 +236,45 @@ export default function BlogPage({ data }) {
285
236
  import { Layout } from "../../components/Layout";
286
237
 
287
238
  export const meta = {
288
- title: "Blog Post",
289
- description: "Read this article",
239
+ title: "Blog Post",
240
+ description: "Read this article",
290
241
  };
291
242
 
292
243
  export async function load({ params }) {
293
- // params.slug contains the URL parameter
294
- const post = await getPostBySlug(params.slug);
295
-
296
- if (!post) {
297
- throw new Response("Not Found", { status: 404 });
298
- }
299
-
300
- return { post };
244
+ // params.slug contains the URL parameter
245
+ const post = await getPostBySlug(params.slug);
246
+
247
+ if (!post) {
248
+ throw new Response("Not Found", { status: 404 });
249
+ }
250
+
251
+ return { post };
301
252
  }
302
253
 
303
254
  async function getPostBySlug(slug: string) {
304
- // In real app, fetch from database
305
- const posts = {
306
- "hello-world": { title: "Hello World", content: "This is my first post!" },
307
- "getting-started": { title: "Getting Started", content: "Let me show you how..." },
308
- };
309
- return posts[slug] || null;
255
+ // In real app, fetch from database
256
+ const posts = {
257
+ "hello-world": { title: "Hello World", content: "This is my first post!" },
258
+ "getting-started": {
259
+ title: "Getting Started",
260
+ content: "Let me show you how...",
261
+ },
262
+ };
263
+ return posts[slug] || null;
310
264
  }
311
265
 
312
266
  export default function BlogPostPage({ data, params }) {
313
- return (
314
- <Layout>
315
- <article>
316
- <h1 className="text-4xl font-bold mb-4">{data.post.title}</h1>
317
- <div className="prose max-w-none">
318
- {data.post.content}
319
- </div>
320
- </article>
321
- <a href="/blog" className="text-blue-600 mt-8 inline-block">← Back to Blog</a>
322
- </Layout>
323
- );
267
+ return (
268
+ <Layout>
269
+ <article>
270
+ <h1 className="text-4xl font-bold mb-4">{data.post.title}</h1>
271
+ <div className="prose max-w-none">{data.post.content}</div>
272
+ </article>
273
+ <a href="/blog" className="text-blue-600 mt-8 inline-block">
274
+ ← Back to Blog
275
+ </a>
276
+ </Layout>
277
+ );
324
278
  }
325
279
  ```
326
280
 
@@ -332,40 +286,40 @@ export default function BlogPostPage({ data, params }) {
332
286
  import { RouteContext } from "kilatjs";
333
287
 
334
288
  const posts = [
335
- { id: 1, title: "Hello World", slug: "hello-world" },
336
- { id: 2, title: "Getting Started", slug: "getting-started" },
289
+ { id: 1, title: "Hello World", slug: "hello-world" },
290
+ { id: 2, title: "Getting Started", slug: "getting-started" },
337
291
  ];
338
292
 
339
293
  // GET /api/posts
340
294
  export async function GET(ctx: RouteContext) {
341
- const search = ctx.query.get("search")?.toLowerCase();
342
-
343
- let results = posts;
344
- if (search) {
345
- results = posts.filter(p => p.title.toLowerCase().includes(search));
346
- }
347
-
348
- return new Response(JSON.stringify({ data: results }), {
349
- headers: { "Content-Type": "application/json" },
350
- });
295
+ const search = ctx.query.get("search")?.toLowerCase();
296
+
297
+ let results = posts;
298
+ if (search) {
299
+ results = posts.filter((p) => p.title.toLowerCase().includes(search));
300
+ }
301
+
302
+ return new Response(JSON.stringify({ data: results }), {
303
+ headers: { "Content-Type": "application/json" },
304
+ });
351
305
  }
352
306
 
353
307
  // POST /api/posts
354
308
  export async function POST(ctx: RouteContext) {
355
- const body = await ctx.request.json();
356
-
357
- const newPost = {
358
- id: posts.length + 1,
359
- title: body.title,
360
- slug: body.title.toLowerCase().replace(/\s+/g, "-"),
361
- };
362
-
363
- posts.push(newPost);
364
-
365
- return new Response(JSON.stringify({ data: newPost }), {
366
- status: 201,
367
- headers: { "Content-Type": "application/json" },
368
- });
309
+ const body = await ctx.request.json();
310
+
311
+ const newPost = {
312
+ id: posts.length + 1,
313
+ title: body.title,
314
+ slug: body.title.toLowerCase().replace(/\s+/g, "-"),
315
+ };
316
+
317
+ posts.push(newPost);
318
+
319
+ return new Response(JSON.stringify({ data: newPost }), {
320
+ status: 201,
321
+ headers: { "Content-Type": "application/json" },
322
+ });
369
323
  }
370
324
  ```
371
325
 
@@ -377,65 +331,65 @@ export async function POST(ctx: RouteContext) {
377
331
  import { Layout } from "../components/Layout";
378
332
 
379
333
  export const meta = {
380
- title: "Contact Us",
381
- description: "Get in touch with us",
334
+ title: "Contact Us",
335
+ description: "Get in touch with us",
382
336
  };
383
337
 
384
338
  // Handle form submission
385
339
  export async function POST({ request }) {
386
- const formData = await request.formData();
387
- const name = formData.get("name");
388
- const email = formData.get("email");
389
- const message = formData.get("message");
390
-
391
- // Save to database, send email, etc.
392
- console.log("Contact form:", { name, email, message });
393
-
394
- // PRG: Post-Redirect-Get pattern
395
- return Response.redirect("/contact/success", 302);
340
+ const formData = await request.formData();
341
+ const name = formData.get("name");
342
+ const email = formData.get("email");
343
+ const message = formData.get("message");
344
+
345
+ // Save to database, send email, etc.
346
+ console.log("Contact form:", { name, email, message });
347
+
348
+ // PRG: Post-Redirect-Get pattern
349
+ return Response.redirect("/contact/success", 302);
396
350
  }
397
351
 
398
352
  export default function ContactPage() {
399
- return (
400
- <Layout>
401
- <h1 className="text-3xl font-bold mb-8">Contact Us</h1>
402
- <form method="POST" className="max-w-md space-y-4">
403
- <div>
404
- <label className="block text-sm font-medium mb-1">Name</label>
405
- <input
406
- type="text"
407
- name="name"
408
- required
409
- className="w-full px-3 py-2 border rounded-lg"
410
- />
411
- </div>
412
- <div>
413
- <label className="block text-sm font-medium mb-1">Email</label>
414
- <input
415
- type="email"
416
- name="email"
417
- required
418
- className="w-full px-3 py-2 border rounded-lg"
419
- />
420
- </div>
421
- <div>
422
- <label className="block text-sm font-medium mb-1">Message</label>
423
- <textarea
424
- name="message"
425
- rows={4}
426
- required
427
- className="w-full px-3 py-2 border rounded-lg"
428
- />
429
- </div>
430
- <button
431
- type="submit"
432
- className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700"
433
- >
434
- Send Message
435
- </button>
436
- </form>
437
- </Layout>
438
- );
353
+ return (
354
+ <Layout>
355
+ <h1 className="text-3xl font-bold mb-8">Contact Us</h1>
356
+ <form method="POST" className="max-w-md space-y-4">
357
+ <div>
358
+ <label className="block text-sm font-medium mb-1">Name</label>
359
+ <input
360
+ type="text"
361
+ name="name"
362
+ required
363
+ className="w-full px-3 py-2 border rounded-lg"
364
+ />
365
+ </div>
366
+ <div>
367
+ <label className="block text-sm font-medium mb-1">Email</label>
368
+ <input
369
+ type="email"
370
+ name="email"
371
+ required
372
+ className="w-full px-3 py-2 border rounded-lg"
373
+ />
374
+ </div>
375
+ <div>
376
+ <label className="block text-sm font-medium mb-1">Message</label>
377
+ <textarea
378
+ name="message"
379
+ rows={4}
380
+ required
381
+ className="w-full px-3 py-2 border rounded-lg"
382
+ />
383
+ </div>
384
+ <button
385
+ type="submit"
386
+ className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700"
387
+ >
388
+ Send Message
389
+ </button>
390
+ </form>
391
+ </Layout>
392
+ );
439
393
  }
440
394
  ```
441
395
 
@@ -445,19 +399,25 @@ export default function ContactPage() {
445
399
  import { Layout } from "../../components/Layout";
446
400
 
447
401
  export const meta = {
448
- title: "Message Sent",
402
+ title: "Message Sent",
449
403
  };
450
404
 
451
405
  export default function ContactSuccessPage() {
452
- return (
453
- <Layout>
454
- <div className="text-center py-12">
455
- <h1 className="text-3xl font-bold text-green-600 mb-4">✓ Message Sent!</h1>
456
- <p className="text-gray-600 mb-8">Thank you for contacting us. We'll get back to you soon.</p>
457
- <a href="/" className="text-blue-600 hover:underline">← Back to Home</a>
458
- </div>
459
- </Layout>
460
- );
406
+ return (
407
+ <Layout>
408
+ <div className="text-center py-12">
409
+ <h1 className="text-3xl font-bold text-green-600 mb-4">
410
+ Message Sent!
411
+ </h1>
412
+ <p className="text-gray-600 mb-8">
413
+ Thank you for contacting us. We'll get back to you soon.
414
+ </p>
415
+ <a href="/" className="text-blue-600 hover:underline">
416
+ ← Back to Home
417
+ </a>
418
+ </div>
419
+ </Layout>
420
+ );
461
421
  }
462
422
  ```
463
423
 
@@ -468,26 +428,26 @@ export default function ContactSuccessPage() {
468
428
  ```tsx
469
429
  // src/routes/dashboard.tsx
470
430
  export async function load({ request }) {
471
- const cookie = request.headers.get("cookie");
472
- const session = await getSession(cookie);
473
-
474
- if (!session) {
475
- // Redirect to login if not authenticated
476
- throw Response.redirect("/login", 302);
477
- }
478
-
479
- return { user: session.user };
431
+ const cookie = request.headers.get("cookie");
432
+ const session = await getSession(cookie);
433
+
434
+ if (!session) {
435
+ // Redirect to login if not authenticated
436
+ throw Response.redirect("/login", 302);
437
+ }
438
+
439
+ return { user: session.user };
480
440
  }
481
441
 
482
442
  export default function DashboardPage({ data }) {
483
- return (
484
- <div>
485
- <h1>Welcome, {data.user.name}!</h1>
486
- <form method="POST" action="/logout">
487
- <button type="submit">Logout</button>
488
- </form>
489
- </div>
490
- );
443
+ return (
444
+ <div>
445
+ <h1>Welcome, {data.user.name}!</h1>
446
+ <form method="POST" action="/logout">
447
+ <button type="submit">Logout</button>
448
+ </form>
449
+ </div>
450
+ );
491
451
  }
492
452
  ```
493
453
 
@@ -500,16 +460,16 @@ export default function DashboardPage({ data }) {
500
460
  import { defineConfig } from "kilatjs";
501
461
 
502
462
  export default defineConfig({
503
- appDir: "./src", // Source directory (auto-detects routes/ or pages/)
504
- outDir: "./dist", // Production build output
505
- port: 3000, // Server port
506
- hostname: "localhost", // Server hostname
507
- publicDir: "./public", // Static assets directory
508
- tailwind: {
509
- enabled: true, // Enable Tailwind CSS
510
- inputPath: "./input.css",
511
- cssPath: "./styles.css",
512
- },
463
+ appDir: "./src", // Source directory (auto-detects routes/ or pages/)
464
+ outDir: "./dist", // Production build output
465
+ port: 3000, // Server port
466
+ hostname: "localhost", // Server hostname
467
+ publicDir: "./public", // Static assets directory
468
+ tailwind: {
469
+ enabled: true, // Enable Tailwind CSS
470
+ inputPath: "./input.css",
471
+ cssPath: "./styles.css",
472
+ },
513
473
  });
514
474
  ```
515
475
 
@@ -526,6 +486,7 @@ KilatJS leverages Bun's runtime directly:
526
486
  - 🔍 **Bun.Glob** - Pattern matching for routes
527
487
 
528
488
  **Performance:**
489
+
529
490
  - 3x faster cold starts vs Node.js
530
491
  - Native TypeScript - no transpilation
531
492
  - Zero-copy streaming for static files
@@ -555,25 +516,25 @@ Export a function that runs in the browser. TypeScript/LSP fully supports it!
555
516
 
556
517
  // Client-side script - auto-injected, LSP checks the code!
557
518
  export function clientScript() {
558
- let count = 0;
559
- const countEl = document.getElementById("count")!;
560
-
561
- document.getElementById("decrement")!.onclick = () => {
562
- countEl.textContent = String(--count);
563
- };
564
- document.getElementById("increment")!.onclick = () => {
565
- countEl.textContent = String(++count);
566
- };
519
+ let count = 0;
520
+ const countEl = document.getElementById("count")!;
521
+
522
+ document.getElementById("decrement")!.onclick = () => {
523
+ countEl.textContent = String(--count);
524
+ };
525
+ document.getElementById("increment")!.onclick = () => {
526
+ countEl.textContent = String(++count);
527
+ };
567
528
  }
568
529
 
569
530
  export default function CounterPage() {
570
- return (
571
- <div>
572
- <button id="decrement">-</button>
573
- <span id="count">0</span>
574
- <button id="increment">+</button>
575
- </div>
576
- );
531
+ return (
532
+ <div>
533
+ <button id="decrement">-</button>
534
+ <span id="count">0</span>
535
+ <button id="increment">+</button>
536
+ </div>
537
+ );
577
538
  }
578
539
  ```
579
540
 
@@ -585,13 +546,13 @@ Lightweight reactivity with declarative HTML attributes:
585
546
 
586
547
  ```tsx
587
548
  export default function AlpinePage() {
588
- return (
589
- <div x-data="{ count: 0 }">
590
- <button x-on:click="count--">-</button>
591
- <span x-text="count">0</span>
592
- <button x-on:click="count++">+</button>
593
- </div>
594
- );
549
+ return (
550
+ <div x-data="{ count: 0 }">
551
+ <button x-on:click="count--">-</button>
552
+ <span x-text="count">0</span>
553
+ <button x-on:click="count++">+</button>
554
+ </div>
555
+ );
595
556
  }
596
557
  ```
597
558
 
@@ -601,11 +562,8 @@ Server-driven UI updates without JavaScript:
601
562
 
602
563
  ```html
603
564
  <!-- Can use .html files directly in routes/ -->
604
- <button
605
- hx-get="/api/greeting"
606
- hx-target="#result"
607
- hx-swap="innerHTML">
608
- Load
565
+ <button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML">
566
+ Load
609
567
  </button>
610
568
  <div id="result"></div>
611
569
  ```
@@ -618,33 +576,41 @@ Best for mutations - no JS required:
618
576
 
619
577
  ```tsx
620
578
  <form method="POST" action="/contact">
621
- <input name="email" type="email" />
622
- <button type="submit">Submit</button>
579
+ <input name="email" type="email" />
580
+ <button type="submit">Submit</button>
623
581
  </form>
624
582
  ```
625
583
 
626
- ---
584
+ ## 🛠️ Configuration Reference
627
585
 
628
- ## ❌ What KilatJS Doesn't Do
586
+ ```ts
587
+ // kilat.config.ts
588
+ import { defineConfig } from "kilatjs";
629
589
 
630
- - Client-Side Rendering (CSR)
631
- - React Hydration / Islands
632
- - Client-side routing
633
- - React hooks (`useState`, `useEffect`) - No client React runtime
634
- - SPA patterns
590
+ export default defineConfig({
591
+ appDir: ".", // Root directory for routes/ and components/
592
+ outDir: "./dist", // Production build output
593
+ port: 3000, // Server port
594
+ publicDir: "./public", // Static assets directory (auto-detects index.html)
595
+ clientRoute: "/client", // (Optional) Path for React client SPA
596
+ tailwind: {
597
+ enabled: true, // Enable Tailwind CSS
598
+ inputPath: "./input.css",
599
+ cssPath: "./public/styles.css",
600
+ },
601
+ });
602
+ ```
635
603
 
636
- ### Why No React Hooks?
604
+ ---
637
605
 
638
- KilatJS uses `renderToStaticMarkup` which outputs pure HTML without React runtime. This means:
606
+ ## Hybrid Approach
639
607
 
640
- - Faster page loads (no React bundle)
641
- - ✅ Better SEO (real HTML)
642
- - ✅ Works without JavaScript
643
- - ❌ No `useState`, `useEffect`, `onClick`
608
+ KilatJS uniquely allows you to mix **HTML-First SSR** with **Full React CSR**:
644
609
 
645
- **For interactivity, use `clientScript`, Alpine.js, HTMX, or vanilla JS instead.**
610
+ - **Use SSR** for public pages, SEO, blogs, and landing pages.
611
+ - **Use CSR** for complex dashboards, interactive editors, or apps requiring shared state.
646
612
 
647
- > If you need React hooks with SSR, consider Next.js or Remix which have built-in hydration.
613
+ > **Note**: KilatJS keeps these worlds separate. SSR pages are pure HTML (no React runtime), while CSR pages are full React apps. This gives you the speed of a static site with the power of a modern app.
648
614
 
649
615
  ---
650
616