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 +468 -281
- package/dist/adapters/react.d.ts +6 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +2049 -0
- package/dist/core/router.d.ts +10 -7
- package/dist/core/types.d.ts +10 -1
- package/dist/core/vite-router.d.ts +34 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1157 -199
- package/dist/server/dev-server.d.ts +0 -0
- package/dist/server/live-reload.d.ts +27 -0
- package/dist/server/server.d.ts +12 -1
- package/dist/server/vite-dev.d.ts +38 -0
- package/dist/server/vite-server.d.ts +17 -0
- package/package.json +10 -4
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
|
-
##
|
|
33
|
-
|
|
34
|
-
KilatJS leverages Bun's runtime directly instead of abstracting it away:
|
|
23
|
+
## ⚡ Quick Start
|
|
35
24
|
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
### Development Output
|
|
81
52
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
##
|
|
64
|
+
## 📁 Project Structure
|
|
94
65
|
|
|
95
|
-
|
|
66
|
+
KilatJS uses a simplified, root-level structure:
|
|
96
67
|
|
|
97
|
-
```bash
|
|
98
|
-
bun install
|
|
99
68
|
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
##
|
|
82
|
+
## ⚛️ React Client Support (Opt-in)
|
|
112
83
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
173
|
-
|
|
115
|
+
const data = await fetchData();
|
|
116
|
+
return { items: data };
|
|
174
117
|
}
|
|
175
118
|
|
|
176
|
-
// HTTP
|
|
119
|
+
// HTTP method handlers (POST, PUT, DELETE, etc.)
|
|
177
120
|
export async function POST(ctx) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
121
|
+
const formData = await ctx.request.formData();
|
|
122
|
+
// Process form...
|
|
123
|
+
return Response.redirect("/success", 302);
|
|
181
124
|
}
|
|
182
125
|
|
|
183
|
-
//
|
|
126
|
+
// Page component (receives data from load())
|
|
184
127
|
export default function Page({ data, params, state }) {
|
|
185
|
-
|
|
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
|
-
##
|
|
140
|
+
## 🔧 Tutorial: Building a Blog
|
|
141
|
+
|
|
142
|
+
### Step 1: Create Layout Component
|
|
192
143
|
|
|
193
|
-
|
|
144
|
+
**`src/components/Layout.tsx`:**
|
|
194
145
|
|
|
195
146
|
```tsx
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
+
export const meta = {
|
|
187
|
+
title: "Blog - My Site",
|
|
188
|
+
description: "Read our latest articles",
|
|
189
|
+
};
|
|
222
190
|
|
|
223
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
231
|
+
### Step 3: Create Dynamic Blog Post Page
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
283
|
+
**`src/routes/api/posts.ts`:**
|
|
247
284
|
|
|
248
|
-
|
|
285
|
+
```ts
|
|
286
|
+
import { RouteContext } from "kilatjs";
|
|
249
287
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
export
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
###
|
|
326
|
+
### Step 5: Create Contact Form with PRG Pattern
|
|
263
327
|
|
|
264
|
-
|
|
265
|
-
- PHP-style execution
|
|
266
|
-
- Infinite routes allowed
|
|
328
|
+
**`src/routes/contact.tsx`:**
|
|
267
329
|
|
|
268
330
|
```tsx
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
|
|
345
|
+
// Save to database, send email, etc.
|
|
346
|
+
console.log("Contact form:", { name, email, message });
|
|
277
347
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
434
|
+
if (!session) {
|
|
435
|
+
// Redirect to login if not authenticated
|
|
436
|
+
throw Response.redirect("/login", 302);
|
|
437
|
+
}
|
|
299
438
|
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 🛠️ Configuration Reference
|
|
321
457
|
|
|
322
458
|
```ts
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
##
|
|
478
|
+
## 🔥 Why Bun Native?
|
|
334
479
|
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
496
|
+
## ✅ Core Principles
|
|
348
497
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
##
|
|
506
|
+
## ⚡ Client-Side Interactivity
|
|
358
507
|
|
|
359
|
-
KilatJS
|
|
508
|
+
KilatJS is server-first, but you can add client-side interactivity using these patterns:
|
|
360
509
|
|
|
361
|
-
|
|
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
|
-
|
|
541
|
+
The framework automatically injects the script - no manual `<script>` tags needed!
|
|
372
542
|
|
|
373
|
-
###
|
|
543
|
+
### 2. Alpine.js
|
|
374
544
|
|
|
375
|
-
|
|
545
|
+
Lightweight reactivity with declarative HTML attributes:
|
|
376
546
|
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
###
|
|
559
|
+
### 3. HTMX
|
|
394
560
|
|
|
395
|
-
|
|
561
|
+
Server-driven UI updates without JavaScript:
|
|
396
562
|
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
584
|
+
## 🛠️ Configuration Reference
|
|
409
585
|
|
|
410
586
|
```ts
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
##
|
|
606
|
+
## ⚡ Hybrid Approach
|
|
421
607
|
|
|
422
|
-
|
|
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
|
-
|
|
615
|
+
---
|
|
427
616
|
|
|
428
|
-
|
|
617
|
+
## 📝 License
|
|
429
618
|
|
|
430
|
-
|
|
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
|
|
438
|
-
</p>
|
|
624
|
+
<strong>Built with 💜 for the real web using Bun.</strong>
|
|
625
|
+
</p>
|