kilatjs 0.1.0 → 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
@@ -14,425 +14,612 @@
14
14
 
15
15
  KilatJS is a server-first web framework inspired by the simplicity of PHP and the power of modern component libraries. It rejects the complexity of SPAs, hydration, and client-side routing in favor of **real HTML rendered on the server**.
16
16
 
17
- **🔥 Powered by Bun's Native APIs - No Abstractions, Pure Performance:**
18
- - 🚀 **Bun.FileSystemRouter** - Zero-config file-based routing at native speed
19
- - ⚡ **Bun.serve()** - HTTP/2 server with built-in WebSocket support
20
- - 🔧 **Bun.file()** - Streaming file operations with automatic MIME detection
21
- - 📦 **Bun.build()** - Native TypeScript/JSX bundling (no Webpack/Vite needed)
22
- - 🔄 **Bun.spawn()** - Process management for build tools and external commands
23
- - 💾 **Bun.write()** - Optimized file writing with atomic operations
24
- - 🔍 **Bun.glob()** - Pattern matching for static site generation
25
-
26
17
  ### One-Line Positioning
27
18
 
28
19
  > **"Request → Load → Render HTML → Response. That's it."**
29
20
 
30
21
  ---
31
22
 
32
- ## 🔥 Why Bun Native?
33
-
34
- KilatJS leverages Bun's runtime directly instead of abstracting it away:
23
+ ## Quick Start
35
24
 
36
- ```typescript
37
- // Direct Bun.FileSystemRouter usage
38
- const router = new Bun.FileSystemRouter({
39
- style: "nextjs",
40
- dir: "./routes"
41
- });
25
+ ### Create a New Project
42
26
 
43
- // Native Bun.serve() with streaming
44
- export default {
45
- port: 3000,
46
- fetch: async (request) => {
47
- // Zero-copy request handling
48
- return new Response(Bun.file("./static/index.html"));
49
- }
50
- };
27
+ The easiest way to start is using the KilatJS CLI:
51
28
 
52
- // Built-in TypeScript compilation
53
- const result = await Bun.build({
54
- entrypoints: ["./src/index.tsx"],
55
- outdir: "./dist",
56
- target: "bun"
57
- });
29
+ ```bash
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
58
35
  ```
59
36
 
60
- **Performance Benefits:**
61
- - ⚡ **3x faster** cold starts vs Node.js frameworks
62
- - 🚀 **Native TypeScript** - no transpilation overhead
63
- - 💾 **Zero-copy streaming** for static files
64
- - 🔧 **Built-in bundling** eliminates build tool complexity
37
+ Visit [http://localhost:3000](http://localhost:3000) 🎉
65
38
 
66
39
  ---
67
40
 
68
- ## Core Principles
69
-
70
- 1. **HTML is the protocol** — Every response is complete, semantic HTML
71
- 2. **Server owns all truth** — No client-side state management nightmares
72
- 3. **Every meaningful change = HTTP request** — Forms, mutations, navigation
73
- 4. **UI frameworks render, never orchestrate** — React/Solid/Vue for templating only
74
- 5. **JavaScript is optional, not required** — Sites work without JS
75
-
76
- ---
41
+ ## 📖 CLI Commands
77
42
 
78
- ## Strict Non-Goals
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 |
79
50
 
80
- KilatJS intentionally avoids:
51
+ ### Development Output
81
52
 
82
- - ❌ Client-Side Rendering (CSR)
83
- - Hydration / Islands architecture
84
- - ❌ Client-side routing
85
- - ❌ Framework hooks (`useLoaderData`, `useRoute`, etc.)
86
- - Middleware chains
87
- - Dependency injection
88
- - ❌ SPA build assumptions
89
- - ❌ Vite as a hard dependency
53
+ ```
54
+ KilatJS Dev Server
55
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56
+ http://localhost:3000
57
+ HMR + Live Reload enabled
58
+ Edit files and see changes instantly
59
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
60
+ ```
90
61
 
91
62
  ---
92
63
 
93
- ## 🚀 Quick Start
64
+ ## 📁 Project Structure
94
65
 
95
- ### 1. Install dependencies
66
+ KilatJS uses a simplified, root-level structure:
96
67
 
97
- ```bash
98
- bun install
99
68
  ```
100
-
101
- ### 2. Run the example
102
-
103
- ```bash
104
- bun run dev
69
+ my-app/
70
+ ├── public/ # Static assets & index.html
71
+ ├── routes/ # SSR routes (index.tsx, about.tsx)
72
+ ├── components/ # React/Kilat components
73
+ ├── kilat.config.ts # Configuration
74
+ ├── input.css # Tailwind input
75
+ ├── index.client.tsx # (Optional) React client entry
76
+ ├── App.tsx # (Optional) Client-side app
77
+ └── dist/ # Production build
105
78
  ```
106
79
 
107
- Visit [http://localhost:3000](http://localhost:3000) to see the example site.
108
-
109
80
  ---
110
81
 
111
- ## 📁 Project Structure
82
+ ## ⚛️ React Client Support (Opt-in)
112
83
 
113
- ```
114
- kilatjs/
115
- ├── src/
116
- │ ├── index.ts # Main entry point
117
- │ ├── core/
118
- │ │ ├── router.ts # File-based router
119
- │ │ └── types.ts # TypeScript types
120
- │ ├── adapters/
121
- │ │ └── react.ts # React SSR adapter
122
- │ └── server/
123
- │ └── server.ts # Bun HTTP server
124
- ├── example/
125
- │ ├── server.ts # Example server entry
126
- │ ├── build.ts # Static build script
127
- │ ├── styles.css # CSS styles
128
- │ ├── components/
129
- │ │ └── Layout.tsx # Shared layout
130
- │ └── routes/
131
- │ ├── index.tsx # Home page
132
- │ ├── about.tsx # About page
133
- │ ├── contact.tsx # Contact form
134
- │ ├── contact/
135
- │ │ └── success.tsx
136
- │ └── blog/
137
- │ ├── index.tsx # Blog listing
138
- │ └── [slug].tsx # Dynamic blog posts
139
- └── README.md
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>
140
92
  ```
141
93
 
94
+ 3. **Route**: Define `clientRoute: "/client"` in `kilat.config.ts` to host your SPA.
95
+
142
96
  ---
143
97
 
144
98
  ## 📄 Route Contract
145
99
 
146
- Each route file may export **at most**:
100
+ Each route file can export:
147
101
 
148
102
  ```tsx
149
- // Rendering mode: "static" (build-time) or "ssr" (request-time)
150
- export const mode = "ssr";
151
-
152
- // UI framework: "react" | "solid" | "vue" (default: react)
153
- export const ui = "react";
154
-
155
103
  // SEO meta tags
156
104
  export const meta = {
157
- title: "Page Title",
158
- description: "Page description for SEO",
159
- robots: "index,follow",
160
- ogTitle: "Open Graph Title",
161
- ogDescription: "Open Graph description",
162
- 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",
163
111
  };
164
112
 
165
- // For static generation of dynamic routes
166
- export function getStaticPaths() {
167
- return ["/blog/post-1", "/blog/post-2"];
168
- }
169
-
170
- // Server-only data loading
113
+ // Server-side data loading
171
114
  export async function load(ctx) {
172
- const data = await fetchFromDatabase();
173
- return { posts: data };
115
+ const data = await fetchData();
116
+ return { items: data };
174
117
  }
175
118
 
176
- // HTTP action handlers
119
+ // HTTP method handlers (POST, PUT, DELETE, etc.)
177
120
  export async function POST(ctx) {
178
- const formData = await ctx.request.formData();
179
- // Process form...
180
- return Response.redirect("/success", 302);
121
+ const formData = await ctx.request.formData();
122
+ // Process form...
123
+ return Response.redirect("/success", 302);
181
124
  }
182
125
 
183
- // Pure render function
126
+ // Page component (receives data from load())
184
127
  export default function Page({ data, params, state }) {
185
- return <div>{data.posts.map(p => <h2>{p.title}</h2>)}</div>;
128
+ return (
129
+ <div>
130
+ {data.items.map((item) => (
131
+ <p>{item.name}</p>
132
+ ))}
133
+ </div>
134
+ );
186
135
  }
187
136
  ```
188
137
 
189
138
  ---
190
139
 
191
- ## 🔄 Actions (PRG Pattern)
140
+ ## 🔧 Tutorial: Building a Blog
141
+
142
+ ### Step 1: Create Layout Component
192
143
 
193
- All meaningful state changes happen via HTTP requests:
144
+ **`src/components/Layout.tsx`:**
194
145
 
195
146
  ```tsx
196
- // routes/contact.tsx
197
- export async function POST({ request }) {
198
- const formData = await request.formData();
199
- const email = formData.get("email");
200
-
201
- await saveToDatabase({ email });
202
-
203
- // PRG: Post-Redirect-Get pattern
204
- return Response.redirect("/contact/success", 302);
147
+ interface LayoutProps {
148
+ children: React.ReactNode;
149
+ title?: string;
205
150
  }
206
151
 
207
- export default function ContactPage() {
208
- return (
209
- <form method="POST" action="/contact">
210
- <input type="email" name="email" required />
211
- <button type="submit">Subscribe</button>
212
- </form>
213
- );
152
+ export function Layout({ children, title = "My Blog" }: LayoutProps) {
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
+ );
214
176
  }
215
177
  ```
216
178
 
217
- **No JavaScript required.** Forms work natively.
179
+ ### Step 2: Create Blog List Page
218
180
 
219
- ---
181
+ **`src/routes/blog/index.tsx`:**
182
+
183
+ ```tsx
184
+ import { Layout } from "../../components/Layout";
220
185
 
221
- ## 🎨 Styling (Tailwind-Ready)
186
+ export const meta = {
187
+ title: "Blog - My Site",
188
+ description: "Read our latest articles",
189
+ };
222
190
 
223
- KilatJS supports Tailwind CSS via CLI (no Vite required):
191
+ export async function load() {
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 };
208
+ }
224
209
 
225
- ```bash
226
- # Generate CSS
227
- npx tailwindcss -i ./input.css -o ./styles.css --watch
210
+ export default function BlogPage({ data }) {
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
+ );
228
+ }
228
229
  ```
229
230
 
230
- Configure in your `kilat.config.ts`:
231
+ ### Step 3: Create Dynamic Blog Post Page
231
232
 
232
- ```ts
233
- import { createKilat } from 'kilatjs';
234
-
235
- createKilat({
236
- routesDir: "./routes",
237
- tailwind: {
238
- enabled: true,
239
- cssPath: "./styles.css"
240
- }
241
- });
233
+ **`src/routes/blog/[slug].tsx`:**
234
+
235
+ ```tsx
236
+ import { Layout } from "../../components/Layout";
237
+
238
+ export const meta = {
239
+ title: "Blog Post",
240
+ description: "Read this article",
241
+ };
242
+
243
+ export async function load({ params }) {
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 };
252
+ }
253
+
254
+ async function getPostBySlug(slug: string) {
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;
264
+ }
265
+
266
+ export default function BlogPostPage({ data, params }) {
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
+ );
278
+ }
242
279
  ```
243
280
 
244
- ---
281
+ ### Step 4: Create API Route
245
282
 
246
- ## 📊 Rendering Modes
283
+ **`src/routes/api/posts.ts`:**
247
284
 
248
- ### Static Generation
285
+ ```ts
286
+ import { RouteContext } from "kilatjs";
249
287
 
250
- - Build-time HTML
251
- - Best for SEO & caching
252
- - Requires explicit path enumeration for dynamic routes
288
+ const posts = [
289
+ { id: 1, title: "Hello World", slug: "hello-world" },
290
+ { id: 2, title: "Getting Started", slug: "getting-started" },
291
+ ];
253
292
 
254
- ```tsx
255
- export const mode = "static";
293
+ // GET /api/posts
294
+ export async function GET(ctx: RouteContext) {
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
+ });
305
+ }
306
+
307
+ // POST /api/posts
308
+ export async function POST(ctx: RouteContext) {
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);
256
318
 
257
- export function getStaticPaths() {
258
- return ["/blog/post-1", "/blog/post-2"];
319
+ return new Response(JSON.stringify({ data: newPost }), {
320
+ status: 201,
321
+ headers: { "Content-Type": "application/json" },
322
+ });
259
323
  }
260
324
  ```
261
325
 
262
- ### Server-Side Rendering (SSR)
326
+ ### Step 5: Create Contact Form with PRG Pattern
263
327
 
264
- - Request-time HTML
265
- - PHP-style execution
266
- - Infinite routes allowed
328
+ **`src/routes/contact.tsx`:**
267
329
 
268
330
  ```tsx
269
- export const mode = "ssr"; // or omit (default)
270
- ```
331
+ import { Layout } from "../components/Layout";
271
332
 
272
- ---
333
+ export const meta = {
334
+ title: "Contact Us",
335
+ description: "Get in touch with us",
336
+ };
273
337
 
274
- ## 🔐 Authentication
338
+ // Handle form submission
339
+ export async function POST({ request }) {
340
+ const formData = await request.formData();
341
+ const name = formData.get("name");
342
+ const email = formData.get("email");
343
+ const message = formData.get("message");
275
344
 
276
- Identity is proven **per request** via cookies/headers:
345
+ // Save to database, send email, etc.
346
+ console.log("Contact form:", { name, email, message });
277
347
 
278
- ```tsx
279
- export async function load({ request }) {
280
- const session = await getSession(request.headers.get("cookie"));
281
-
282
- if (!session) {
283
- throw Response.redirect("/login");
284
- }
285
-
286
- return { user: session.user };
348
+ // PRG: Post-Redirect-Get pattern
349
+ return Response.redirect("/contact/success", 302);
350
+ }
351
+
352
+ export default function ContactPage() {
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
+ );
287
393
  }
288
394
  ```
289
395
 
290
- - Never trust localStorage for auth
291
- - Server validates every request
292
- - Cookies are the source of truth
396
+ **`src/routes/contact/success.tsx`:**
397
+
398
+ ```tsx
399
+ import { Layout } from "../../components/Layout";
400
+
401
+ export const meta = {
402
+ title: "Message Sent",
403
+ };
404
+
405
+ export default function ContactSuccessPage() {
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
+ );
421
+ }
422
+ ```
293
423
 
294
424
  ---
295
425
 
296
- ## 🏃 Running Your App
426
+ ## 🔐 Authentication Example
427
+
428
+ ```tsx
429
+ // src/routes/dashboard.tsx
430
+ export async function load({ request }) {
431
+ const cookie = request.headers.get("cookie");
432
+ const session = await getSession(cookie);
297
433
 
298
- ### Development
434
+ if (!session) {
435
+ // Redirect to login if not authenticated
436
+ throw Response.redirect("/login", 302);
437
+ }
299
438
 
300
- ```ts
301
- // Named import
302
- import { createKilat } from 'kilatjs';
303
-
304
- // Or default import
305
- import Kilat from 'kilatjs';
306
-
307
- const server = createKilat({
308
- routesDir: "./routes",
309
- port: 3000,
310
- dev: true,
311
- tailwind: {
312
- enabled: false,
313
- cssPath: "./styles.css"
314
- }
315
- });
439
+ return { user: session.user };
440
+ }
316
441
 
317
- server.start();
442
+ export default function DashboardPage({ data }) {
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
+ );
451
+ }
318
452
  ```
319
453
 
320
- ### Static Build
454
+ ---
455
+
456
+ ## 🛠️ Configuration Reference
321
457
 
322
458
  ```ts
323
- import { buildStatic } from 'kilatjs';
324
-
325
- buildStatic({
326
- routesDir: "./routes",
327
- outDir: "./dist"
459
+ // kilat.config.ts
460
+ import { defineConfig } from "kilatjs";
461
+
462
+ export default defineConfig({
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
+ },
328
473
  });
329
474
  ```
330
475
 
331
476
  ---
332
477
 
333
- ## 🧩 UI Framework Support
478
+ ## 🔥 Why Bun Native?
334
479
 
335
- | Framework | Status | Notes |
336
- |-----------|--------|-------|
337
- | React | ✅ Default | Full SSR support |
338
- | Solid | 🚧 Planned | Coming soon |
339
- | Vue | 🚧 Planned | Coming soon |
480
+ KilatJS leverages Bun's runtime directly:
340
481
 
341
- **Rule:** One renderer per route. No mixing.
482
+ - **Bun.FileSystemRouter** - Zero-config file-based routing
483
+ - 🚀 **Bun.serve()** - HTTP server with WebSocket support
484
+ - 📦 **Bun.build()** - Native TypeScript/JSX bundling
485
+ - 💾 **Bun.file()** - Streaming file operations
486
+ - 🔍 **Bun.Glob** - Pattern matching for routes
342
487
 
343
- ---
488
+ **Performance:**
344
489
 
345
- ## 🎯 SEO Guarantee
490
+ - 3x faster cold starts vs Node.js
491
+ - Native TypeScript - no transpilation
492
+ - Zero-copy streaming for static files
493
+
494
+ ---
346
495
 
347
- > **If a route exists, it is SEO-friendly.**
496
+ ## Core Principles
348
497
 
349
- Because:
350
- - HTML is always complete
351
- - `<head>` is server-rendered
352
- - No JS required for content
353
- - Meta tags are first-class
498
+ 1. **HTML is the protocol** — Every response is complete, semantic HTML
499
+ 2. **Server owns all truth** — No client-side state management
500
+ 3. **Every change = HTTP request** — Forms, mutations, navigation
501
+ 4. **UI frameworks render only** — React for templating, not orchestration
502
+ 5. **JavaScript is optional** — Sites work without JS
354
503
 
355
504
  ---
356
505
 
357
- ## 📜 Philosophy
506
+ ## Client-Side Interactivity
358
507
 
359
- KilatJS rejects modern web complexity:
508
+ KilatJS is server-first, but you can add client-side interactivity using these patterns:
360
509
 
361
- | We Don't Do | We Do |
362
- |-------------|-------|
363
- | Client-side rendering | Server-rendered HTML |
364
- | Hydration/Islands | Static markup |
365
- | Client-side router | HTTP navigation |
366
- | Framework hooks | Props from loader |
367
- | State sync | HTTP mutations |
510
+ ### 1. `clientScript` Export (Recommended)
368
511
 
369
- ---
512
+ Export a function that runs in the browser. TypeScript/LSP fully supports it!
513
+
514
+ ```tsx
515
+ // src/routes/counter.tsx
516
+
517
+ // Client-side script - auto-injected, LSP checks the code!
518
+ export function clientScript() {
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
+ };
528
+ }
529
+
530
+ export default function CounterPage() {
531
+ return (
532
+ <div>
533
+ <button id="decrement">-</button>
534
+ <span id="count">0</span>
535
+ <button id="increment">+</button>
536
+ </div>
537
+ );
538
+ }
539
+ ```
370
540
 
371
- ## 🛠️ API Reference
541
+ The framework automatically injects the script - no manual `<script>` tags needed!
372
542
 
373
- ### `createKilat(config)`
543
+ ### 2. Alpine.js
374
544
 
375
- Creates a Kilat server instance.
545
+ Lightweight reactivity with declarative HTML attributes:
376
546
 
377
- ```ts
378
- interface KilatConfig {
379
- routesDir: string; // Path to routes directory
380
- outDir: string; // Output for static builds
381
- port?: number; // Server port (default: 3000)
382
- hostname?: string; // Server hostname (default: "localhost")
383
- dev?: boolean; // Development mode
384
- publicDir?: string; // Static assets directory
385
- tailwind?: {
386
- enabled: boolean;
387
- cssPath: string;
388
- configPath?: string;
389
- };
547
+ ```tsx
548
+ export default function AlpinePage() {
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
+ );
390
556
  }
391
557
  ```
392
558
 
393
- ### `RouteContext`
559
+ ### 3. HTMX
394
560
 
395
- Passed to `load()` and action handlers:
561
+ Server-driven UI updates without JavaScript:
396
562
 
397
- ```ts
398
- interface RouteContext {
399
- request: Request; // Web Request object
400
- params: Record<string, string>; // URL parameters
401
- query: URLSearchParams; // Query string
402
- state: ServerGlobalState; // Request-scoped state
403
- }
563
+ ```html
564
+ <!-- Can use .html files directly in routes/ -->
565
+ <button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML">
566
+ Load
567
+ </button>
568
+ <div id="result"></div>
404
569
  ```
405
570
 
406
- ### `PageProps`
571
+ KilatJS supports `.html` files as routes - perfect for HTMX pages!
572
+
573
+ ### 4. Native HTML Forms
574
+
575
+ Best for mutations - no JS required:
576
+
577
+ ```tsx
578
+ <form method="POST" action="/contact">
579
+ <input name="email" type="email" />
580
+ <button type="submit">Submit</button>
581
+ </form>
582
+ ```
407
583
 
408
- Passed to page components:
584
+ ## 🛠️ Configuration Reference
409
585
 
410
586
  ```ts
411
- interface PageProps {
412
- data: any; // From load()
413
- params: Record<string, string>; // URL parameters
414
- state: ServerGlobalState; // Request-scoped state
415
- }
587
+ // kilat.config.ts
588
+ import { defineConfig } from "kilatjs";
589
+
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
+ });
416
602
  ```
417
603
 
418
604
  ---
419
605
 
420
- ## 📝 License
606
+ ## Hybrid Approach
421
607
 
422
- MIT
608
+ KilatJS uniquely allows you to mix **HTML-First SSR** with **Full React CSR**:
423
609
 
424
- ---
610
+ - **Use SSR** for public pages, SEO, blogs, and landing pages.
611
+ - **Use CSR** for complex dashboards, interactive editors, or apps requiring shared state.
612
+
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.
425
614
 
426
- ## 🤝 Contributing
615
+ ---
427
616
 
428
- KilatJS is in early development. Contributions welcome!
617
+ ## 📝 License
429
618
 
430
- 1. Fork the repository
431
- 2. Create a feature branch
432
- 3. Submit a pull request
619
+ MIT
433
620
 
434
621
  ---
435
622
 
436
623
  <p align="center">
437
- <strong>Built with 💜 for the real web using Bun's native APIs.</strong>
438
- </p>
624
+ <strong>Built with 💜 for the real web using Bun.</strong>
625
+ </p>