@cfdez11/vex 0.1.0

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 (35) hide show
  1. package/README.md +1383 -0
  2. package/client/app.webmanifest +14 -0
  3. package/client/favicon.ico +0 -0
  4. package/client/services/cache.js +55 -0
  5. package/client/services/hmr-client.js +22 -0
  6. package/client/services/html.js +377 -0
  7. package/client/services/hydrate-client-components.js +97 -0
  8. package/client/services/hydrate.js +25 -0
  9. package/client/services/index.js +9 -0
  10. package/client/services/navigation/create-layouts.js +172 -0
  11. package/client/services/navigation/create-navigation.js +103 -0
  12. package/client/services/navigation/index.js +8 -0
  13. package/client/services/navigation/link-interceptor.js +39 -0
  14. package/client/services/navigation/metadata.js +23 -0
  15. package/client/services/navigation/navigate.js +64 -0
  16. package/client/services/navigation/prefetch.js +43 -0
  17. package/client/services/navigation/render-page.js +45 -0
  18. package/client/services/navigation/render-ssr.js +157 -0
  19. package/client/services/navigation/router.js +48 -0
  20. package/client/services/navigation/use-query-params.js +225 -0
  21. package/client/services/navigation/use-route-params.js +76 -0
  22. package/client/services/reactive.js +231 -0
  23. package/package.json +24 -0
  24. package/server/index.js +115 -0
  25. package/server/prebuild.js +12 -0
  26. package/server/root.html +15 -0
  27. package/server/utils/cache.js +89 -0
  28. package/server/utils/component-processor.js +1526 -0
  29. package/server/utils/data-cache.js +62 -0
  30. package/server/utils/delay.js +1 -0
  31. package/server/utils/files.js +723 -0
  32. package/server/utils/hmr.js +21 -0
  33. package/server/utils/router.js +373 -0
  34. package/server/utils/streaming.js +315 -0
  35. package/server/utils/template.js +263 -0
package/README.md ADDED
@@ -0,0 +1,1383 @@
1
+ # Vanilla JS Framework
2
+
3
+ [![pnpm](https://img.shields.io/badge/pnpm-F69220?logo=pnpm&logoColor=fff)](#)
4
+ [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#)
5
+ [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#)
6
+
7
+ A minimalist vanilla JavaScript framework with support for Server-Side Rendering (SSR), Client-Side Rendering (CSR), reactive components, and streaming with suspense.
8
+
9
+ ## οΏ½ Table of Contents
10
+
11
+ - [Vanilla JS Framework](#vanilla-js-framework)
12
+ - [οΏ½ Table of Contents](#-table-of-contents)
13
+ - [✨ Key Features](#-key-features)
14
+ - [οΏ½πŸ“ Project Structure](#-project-structure)
15
+ - [πŸš€ Quick Start](#-quick-start)
16
+ - [πŸ“„ Creating a Page](#-creating-a-page)
17
+ - [🧩 Components](#-components)
18
+ - [Component Structure](#component-structure)
19
+ - [Server Components](#server-components)
20
+ - [Using Components](#using-components)
21
+ - [🎭 Rendering Strategies](#-rendering-strategies)
22
+ - [SSR - Server-Side Rendering](#ssr---server-side-rendering)
23
+ - [CSR - Client-Side Rendering](#csr---client-side-rendering)
24
+ - [SSG - Static Site Generation](#ssg---static-site-generation)
25
+ - [ISR - Incremental Static Regeneration](#isr---incremental-static-regeneration)
26
+ - [πŸ“ Layouts](#-layouts)
27
+ - [Root Layout](#root-layout)
28
+ - [Custom Nested Layouts](#custom-nested-layouts)
29
+ - [⏳ Suspense (Streaming)](#-suspense-streaming)
30
+ - [πŸ”„ Reactive System](#-reactive-system)
31
+ - [Available Functions](#available-functions)
32
+ - [`reactive(value)`](#reactivevalue)
33
+ - [`effect(fn)`](#effectfn)
34
+ - [`computed(getter)`](#computedgetter)
35
+ - [`watch(source, callback, options)`](#watchsource-callback-options)
36
+ - [In Components](#in-components)
37
+ - [Reactivity Comparison](#reactivity-comparison)
38
+ - [πŸ“ Template Syntax](#-template-syntax)
39
+ - [Interpolation](#interpolation)
40
+ - [Conditionals](#conditionals)
41
+ - [Lists](#lists)
42
+ - [Event Handlers](#event-handlers)
43
+ - [Attributes](#attributes)
44
+ - [πŸ›£οΈ Routing](#️-routing)
45
+ - [Auto-Generated Routes](#auto-generated-routes)
46
+ - [Dynamic Routes](#dynamic-routes)
47
+ - [Client-Side Navigation](#client-side-navigation)
48
+ - [Accessing Route Parameters](#accessing-route-parameters)
49
+ - [⚑ Prefetching](#-prefetching)
50
+ - [Automatic Prefetching](#automatic-prefetching)
51
+ - [🎨 Styling](#-styling)
52
+ - [πŸ”§ Framework API](#-framework-api)
53
+ - [Component Props](#component-props)
54
+ - [Reactive State](#reactive-state)
55
+ - [Navigation Utilities](#navigation-utilities)
56
+ - [πŸ“¦ Available Scripts](#-available-scripts)
57
+ - [πŸ—οΈ Rendering Flow](#️-rendering-flow)
58
+ - [SSR (Server-Side Rendering)](#ssr-server-side-rendering)
59
+ - [CSR (Client-Side Rendering)](#csr-client-side-rendering)
60
+ - [ISR (Incremental Static Regeneration)](#isr-incremental-static-regeneration)
61
+ - [Server Startup](#server-startup)
62
+ - [Build Process](#build-process)
63
+ - [Client Hydration](#client-hydration)
64
+ - [πŸ—ΊοΈ Roadmap](#️-roadmap)
65
+
66
+ ## ✨ Key Features
67
+
68
+ - πŸš€ **Multiple Rendering Strategies**: SSR, CSR, SSG, and ISR support
69
+ - ⚑ **Auto-Generated Routes**: File-based routing with dynamic routes `[param]`
70
+ - πŸ”„ **Reactive System**: Vue-like reactivity with `reactive()` and `computed()`
71
+ - 🧩 **Component-Based**: Reusable `.html` components (server & client)
72
+ - 🎭 **Streaming & Suspense**: Progressive loading with fallback UI
73
+ - πŸ“ **Nested Layouts**: Custom layouts per route
74
+ - πŸ”— **Smart Prefetching**: Automatic page prefetching on link hover
75
+ - πŸ’Ύ **Built-in Caching**: Server and client-side caching
76
+ - 🎨 **Tailwind CSS**: Integrated styling solution
77
+ - πŸ“ **Template Syntax**: Familiar directives (`v-if`, `v-for`, `@click`, etc.)
78
+ - 🌐 **SPA Navigation**: Client-side routing without page reloads
79
+ - πŸ”Œ **Zero Config**: No manual route registration needed
80
+ - πŸ“¦ **Pure JavaScript**: No TypeScript to focus on core functionality without build complexity
81
+
82
+ ## οΏ½πŸ“ Project Structure
83
+
84
+ ```
85
+ β”œβ”€β”€ pages/ # Application pages
86
+ β”‚ β”œβ”€β”€ layout.html # Main layout (header, footer, etc.)
87
+ β”‚ β”œβ”€β”€ page.html # Home page
88
+ β”‚ β”œβ”€β”€ error/page.html # Error page
89
+ β”‚ β”œβ”€β”€ not-found/page.html # 404 page
90
+ β”‚ β”œβ”€β”€ page-csr/ # CSR example
91
+ β”‚ β”‚ β”œβ”€β”€ page.html # CSR main page
92
+ β”‚ β”‚ └── [city]/page.html # Dynamic CSR route
93
+ β”‚ β”œβ”€β”€ page-ssr/ # SSR example
94
+ β”‚ β”‚ β”œβ”€β”€ page.html # SSR main page
95
+ β”‚ β”‚ └── [city]/page.html # Dynamic SSR route
96
+ β”‚ β”œβ”€β”€ static/ # Static page example
97
+ β”‚ β”‚ β”œβ”€β”€ layout.html # Static layout
98
+ β”‚ β”‚ └── page.html # Static page
99
+ β”‚ └── static-with-data/ # Static with data example
100
+ β”‚ └── page.html # Static page with data fetching
101
+ β”œβ”€β”€ components/ # Component definitions (.html files)
102
+ β”‚ β”œβ”€β”€ counter.html # Counter component
103
+ β”‚ β”œβ”€β”€ user-card.html # User card component
104
+ β”‚ β”œβ”€β”€ user-card-delayed.html # Delayed user card (for suspense demo)
105
+ β”‚ β”œβ”€β”€ user-card-skeleton.html # Skeleton placeholder
106
+ β”‚ β”œβ”€β”€ weather.html # Weather component
107
+ β”‚ └── weather/ # Weather sub-components
108
+ β”‚ β”œβ”€β”€ weather-links.html
109
+ β”‚ β”œβ”€β”€ weather-params.html
110
+ β”‚ └── weather-state.html
111
+ └── .app/ # Framework files (do not edit)
112
+ β”œβ”€β”€ client/ # Client-side framework
113
+ β”‚ β”œβ”€β”€ services/ # Client framework core
114
+ β”‚ β”‚ β”œβ”€β”€ reactive.js # Reactivity system
115
+ β”‚ β”‚ β”œβ”€β”€ html.js # Template literal helpers
116
+ β”‚ β”‚ β”œβ”€β”€ hydrate.js # Component hydration
117
+ β”‚ β”‚ β”œβ”€β”€ cache.js # Client-side caching
118
+ β”‚ β”‚ β”œβ”€β”€ navigation/ # Navigation utilities
119
+ β”‚ β”‚ β”‚ β”œβ”€β”€ router.js # Client router
120
+ β”‚ β”‚ β”‚ β”œβ”€β”€ navigate.js # Navigation API
121
+ β”‚ β”‚ β”‚ β”œβ”€β”€ prefetch.js # Page prefetching
122
+ β”‚ β”‚ β”‚ β”œβ”€β”€ metadata.js # Dynamic metadata
123
+ β”‚ β”‚ β”‚ └── ...
124
+ β”‚ β”‚ └── _routes.js # Auto-generated routes
125
+ β”‚ β”œβ”€β”€ _components/ # Auto-generated component scripts
126
+ β”‚ β”œβ”€β”€ styles.css # Compiled Tailwind styles
127
+ β”‚ └── favicon.ico # Favicon
128
+ └── server/ # Server-side framework
129
+ β”œβ”€β”€ index.js # Entry point
130
+ β”œβ”€β”€ root.html # Root HTML template
131
+ β”œβ”€β”€ _cache/ # Server-side cache
132
+ └── utils/ # Server utilities
133
+ β”œβ”€β”€ router.js # Router and SSR rendering
134
+ β”œβ”€β”€ component-processor.js # Component processing
135
+ β”œβ”€β”€ template.js # Template rendering
136
+ β”œβ”€β”€ streaming.js # Suspense and streaming
137
+ β”œβ”€β”€ cache.js # Server-side caching
138
+ β”œβ”€β”€ files.js # File system utilities
139
+ └── _routes.js # Auto-generated routes
140
+ ```
141
+
142
+ ## πŸš€ Quick Start
143
+
144
+ ```bash
145
+ # Install dependencies
146
+ pnpm install
147
+
148
+ # Start development server
149
+ pnpm dev
150
+
151
+ # Start production server
152
+ pnpm start
153
+ ```
154
+
155
+ The server will be available at `http://localhost:3000`
156
+
157
+ ## πŸ“„ Creating a Page
158
+
159
+ Pages are created in `pages/` with the following structure:
160
+
161
+ ```html
162
+ <!-- pages/example/page.html -->
163
+ <script server>
164
+ // Server-side imports (components)
165
+ import UserCard from "components/user-card.html";
166
+
167
+ // Server-side data fetching
168
+ async function getData() {
169
+ return { message: "Hello from the server" };
170
+ }
171
+
172
+ // Page metadata
173
+ const metadata = {
174
+ title: "My Page",
175
+ description: "Page description",
176
+ };
177
+ </script>
178
+
179
+ <script client>
180
+ // Client-side component imports
181
+ import Counter from "components/counter.html";
182
+ </script>
183
+
184
+ <template>
185
+ <h1>{{message}}</h1>
186
+ <Counter start="0" />
187
+ <UserCard userId="123" />
188
+ </template>
189
+ ```
190
+
191
+ **Routes are auto-generated** from the `pages/` folder structure. No need to manually register routes!
192
+
193
+ ## 🧩 Components
194
+
195
+ Components are defined in `.html` files within the `components/` folder. They can be either client-side or server-side components.
196
+
197
+ ### Component Structure
198
+
199
+ ```html
200
+ <!-- components/counter.html -->
201
+ <script client>
202
+ import { reactive, computed } from ".app/reactive.js";
203
+
204
+ // Component props
205
+ const props = vprops({
206
+ start: { default: 10 },
207
+ });
208
+
209
+ // Reactive state
210
+ const counter = reactive(props.start);
211
+
212
+ // Methods
213
+ function increment() {
214
+ counter.value++;
215
+ }
216
+
217
+ function decrement() {
218
+ counter.value--;
219
+ }
220
+
221
+ // Computed values
222
+ const stars = computed(() => Array.from({ length: counter.value }, () => "⭐"));
223
+ </script>
224
+
225
+ <template>
226
+ <div class="flex items-center gap-4">
227
+ <button @click="decrement" :disabled="counter <= 0">
228
+ Sub
229
+ </button>
230
+ <span>{{counter}}</span>
231
+ <button @click="increment">
232
+ Add
233
+ </button>
234
+ <div>{{stars.join('')}}</div>
235
+ </div>
236
+ </template>
237
+ ```
238
+
239
+ ### Server Components
240
+
241
+ Server components are rendered on the backend and support async data fetching:
242
+
243
+ ```html
244
+ <!-- components/user-card.html -->
245
+ <script server>
246
+ const props = vprops({
247
+ userId: { required: true },
248
+ });
249
+
250
+ async function getData() {
251
+ const user = await fetch(`https://api.example.com/users/${props.userId}`)
252
+ .then(res => res.json());
253
+ return { user };
254
+ }
255
+ </script>
256
+
257
+ <template>
258
+ <div class="user-card">
259
+ <h3>{{user.name}}</h3>
260
+ <p>{{user.email}}</p>
261
+ </div>
262
+ </template>
263
+ ```
264
+
265
+ ### Using Components
266
+
267
+ Import and use components in your pages:
268
+
269
+ ```html
270
+ <!-- In pages/page.html -->
271
+ <script client>
272
+ import Counter from "components/counter.html";
273
+ </script>
274
+
275
+ <script server>
276
+ import UserCard from "components/user-card.html";
277
+ </script>
278
+
279
+ <template>
280
+ <Counter start="5" />
281
+ <UserCard userId="123" />
282
+ </template>
283
+ ```
284
+
285
+ ## 🎭 Rendering Strategies
286
+
287
+ The framework supports multiple rendering strategies to optimize performance and user experience based on your needs.
288
+
289
+ ### SSR - Server-Side Rendering
290
+
291
+ **When to use**: Dynamic content that changes frequently, SEO-critical pages, personalized content.
292
+
293
+ Pages are rendered on the server for each request. HTML is generated with fresh data and sent to the client.
294
+
295
+ ```html
296
+ <!-- pages/page-ssr/page.html -->
297
+ <script server>
298
+ async function getData() {
299
+ // Fresh data on every request
300
+ const data = await fetch('https://api.example.com/data').then(r => r.json());
301
+ return { data };
302
+ }
303
+
304
+ const metadata = {
305
+ title: "SSR Page",
306
+ description: "Server-rendered on every request"
307
+ };
308
+ </script>
309
+
310
+ <template>
311
+ <h1>{{data.title}}</h1>
312
+ <p>Generated at: {{new Date().toISOString()}}</p>
313
+ </template>
314
+ ```
315
+
316
+ **Characteristics:**
317
+ - βœ… Fresh data on every request
318
+ - βœ… Best for SEO (fully rendered HTML)
319
+ - βœ… Fast initial page load
320
+ - ⚠️ Server load on every request
321
+
322
+ ### CSR - Client-Side Rendering
323
+
324
+ **When to use**: Highly interactive dashboards, authenticated areas, apps with frequent updates.
325
+
326
+ Minimal HTML is sent from the server. All rendering happens in the browser using JavaScript.
327
+
328
+ ```html
329
+ <!-- pages/page-csr/page.html -->
330
+ <script client>
331
+ import { reactive } from ".app/reactive.js";
332
+
333
+ const data = reactive(null);
334
+
335
+ // Fetch data on client
336
+ async function loadData() {
337
+ const response = await fetch('https://api.example.com/data');
338
+ data.value = await response.json();
339
+ }
340
+
341
+ loadData();
342
+ </script>
343
+
344
+ <template>
345
+ <div v-if="data">
346
+ <h1>{{data.title}}</h1>
347
+ </div>
348
+ <div v-else>
349
+ Loading...
350
+ </div>
351
+ </template>
352
+ ```
353
+
354
+ **Characteristics:**
355
+ - βœ… Highly interactive
356
+ - βœ… Reduced server load
357
+ - βœ… Instant navigation after first load
358
+ - ⚠️ Slower initial render
359
+ - ⚠️ Less SEO-friendly
360
+
361
+ ### SSG - Static Site Generation
362
+
363
+ **When to use**: Content that rarely changes (docs, blogs, marketing pages).
364
+
365
+ Pages are pre-rendered at build time and served as static HTML. No server processing on requests.
366
+
367
+ ```html
368
+ <!-- pages/static-with-data/page.html -->
369
+ <script server>
370
+ async function getData() {
371
+ // Fetched once at build time
372
+ const data = await fetch('https://api.example.com/content').then(r => r.json());
373
+ return { data };
374
+ }
375
+
376
+ const metadata = {
377
+ title: "Static Page",
378
+ description: "Pre-rendered at build time",
379
+ revalidate: 'never' // Never regenerate
380
+ };
381
+ </script>
382
+
383
+ <template>
384
+ <h1>{{data.title}}</h1>
385
+ <p>Built at: {{new Date().toISOString()}}</p>
386
+ </template>
387
+ ```
388
+
389
+ **Characteristics:**
390
+ - βœ… Fastest possible delivery (static files)
391
+ - βœ… Lowest server cost
392
+ - βœ… Perfect for SEO
393
+ - βœ… Can be served from CDN
394
+ - ⚠️ Content only updates on rebuild
395
+
396
+ ### ISR - Incremental Static Regeneration
397
+
398
+ **When to use**: Content that changes occasionally (product pages, articles with comments).
399
+
400
+ Pages are statically generated but automatically regenerate after a specified time period.
401
+
402
+ ```html
403
+ <!-- pages/page-ssr/[city]/page.html -->
404
+ <script server>
405
+ import { useRouteParams } from ".app/navigation/use-route-params.js";
406
+
407
+ async function getData() {
408
+ const { city } = useRouteParams();
409
+ const weather = await fetch(`https://api.weather.com/${city}`).then(r => r.json());
410
+ return { city, weather };
411
+ }
412
+
413
+ const metadata = {
414
+ title: "Weather",
415
+ description: "Weather with ISR",
416
+ revalidate: 10 // Regenerate every 10 seconds
417
+ };
418
+ </script>
419
+
420
+ <template>
421
+ <h1>Weather in {{city}}</h1>
422
+ <p>Temperature: {{weather.temp}}Β°C</p>
423
+ <p class="text-sm text-gray-500">Updates every 10 seconds</p>
424
+ </template>
425
+ ```
426
+
427
+ **Characteristics:**
428
+ - βœ… Static performance with fresh content
429
+ - βœ… Automatic background regeneration
430
+ - βœ… Best of both worlds (speed + freshness)
431
+ - βœ… Reduces API calls
432
+ - ⚠️ Slightly stale data possible (within revalidation window)
433
+
434
+ **Revalidation options:**
435
+ ```js
436
+ const metadata = {
437
+ revalidate: 'never', // Pure static (SSG)
438
+ revalidate: 10, // Regenerate every 10 seconds (ISR)
439
+ // No revalidate // Server-side rendering on every request (SSR)
440
+ };
441
+ ```
442
+
443
+ ## πŸ“ Layouts
444
+
445
+ The framework supports nested layouts for consistent page structure.
446
+
447
+ ### Root Layout
448
+
449
+ The main layout is defined in `pages/layout.html` and wraps all pages:
450
+
451
+ ```html
452
+ <!-- pages/layout.html -->
453
+ <script client>
454
+ const props = vprops({
455
+ children: { default: null },
456
+ });
457
+ </script>
458
+
459
+ <template>
460
+ <div>
461
+ <header class="bg-white shadow">
462
+ <nav>
463
+ <a href="/">Home</a>
464
+ <a href="/page-ssr">SSR</a>
465
+ <a href="/page-csr">CSR</a>
466
+ </nav>
467
+ </header>
468
+
469
+ <main>
470
+ {{children}} <!-- Page content injected here -->
471
+ </main>
472
+
473
+ <footer class="bg-gray-800 text-white">
474
+ <p>&copy; 2026 My App</p>
475
+ </footer>
476
+ </div>
477
+ </template>
478
+ ```
479
+
480
+ ### Custom Nested Layouts
481
+
482
+ You can create custom layouts for specific routes:
483
+
484
+ ```html
485
+ <!-- pages/static/layout.html -->
486
+ <script client>
487
+ const props = vprops({
488
+ children: { default: null },
489
+ });
490
+ </script>
491
+
492
+ <template>
493
+ <div class="static-layout">
494
+ <aside class="sidebar">
495
+ <!-- Sidebar navigation -->
496
+ </aside>
497
+ <div class="content">
498
+ {{children}} <!-- Page content -->
499
+ </div>
500
+ </div>
501
+ </template>
502
+ ```
503
+
504
+ **Layout Hierarchy:**
505
+ ```
506
+ pages/layout.html (root layout)
507
+ └─> pages/static/layout.html (custom layout for /static/*)
508
+ └─> pages/static/page.html (page content)
509
+ ```
510
+
511
+ ## ⏳ Suspense (Streaming)
512
+
513
+ Allows showing a fallback while content loads asynchronously:
514
+
515
+ ```html
516
+ <script server>
517
+ import UserCardDelayed from "components/user-card-delayed.html";
518
+ import UserCardSkeleton from "components/user-card-skeleton.html";
519
+ </script>
520
+
521
+ <template>
522
+ <Suspense :fallback="<UserCardSkeleton />">
523
+ <UserCardDelayed userId="123" />
524
+ </Suspense>
525
+ </template>
526
+ ```
527
+
528
+ **Benefits:**
529
+
530
+ - The rest of the page is shown immediately
531
+ - Slow components load via streaming
532
+ - Improves perceived performance
533
+ - Better user experience with progressive loading
534
+
535
+ ## πŸ”„ Reactive System
536
+
537
+ The reactive system provides a Vue-like reactivity API with automatic dependency tracking and UI updates.
538
+
539
+ ### Available Functions
540
+
541
+ #### `reactive(value)`
542
+
543
+ Creates a reactive proxy that automatically tracks dependencies and triggers effects when changed.
544
+
545
+ **Use when:** You need reactive state that automatically updates the UI.
546
+
547
+ ```js
548
+ import { reactive } from ".app/reactive.js";
549
+
550
+ // Reactive primitives (wrapped in .value)
551
+ const counter = reactive(0);
552
+ const name = reactive("Alice");
553
+ counter.value++; // Triggers UI update
554
+
555
+ // Reactive objects (direct property access)
556
+ const state = reactive({ count: 0, user: "Alice" });
557
+ state.count++; // Triggers UI update
558
+ state.user = "Bob"; // Triggers UI update
559
+ ```
560
+
561
+ #### `effect(fn)`
562
+
563
+ Creates a side effect that automatically re-runs when its reactive dependencies change.
564
+
565
+ **Use when:** You need to perform side effects (logging, API calls, DOM manipulation) based on reactive state.
566
+
567
+ ```js
568
+ import { reactive, effect } from ".app/reactive.js";
569
+
570
+ const count = reactive(0);
571
+
572
+ // Effect runs immediately and on every count change
573
+ const cleanup = effect(() => {
574
+ console.log(`Count is: ${count.value}`);
575
+ document.title = `Count: ${count.value}`;
576
+ });
577
+
578
+ count.value++; // Effect runs again
579
+ count.value++; // Effect runs again
580
+
581
+ cleanup(); // Stop the effect
582
+ ```
583
+
584
+ #### `computed(getter)`
585
+
586
+ Creates a computed reactive value that automatically recalculates when its dependencies change.
587
+
588
+ **Use when:** You need derived state that depends on other reactive values.
589
+
590
+ ```js
591
+ import { reactive, computed } from ".app/reactive.js";
592
+
593
+ const price = reactive(100);
594
+ const quantity = reactive(2);
595
+
596
+ // Computed value automatically updates
597
+ const total = computed(() => price.value * quantity.value);
598
+
599
+ console.log(total.value); // 200
600
+ price.value = 150;
601
+ console.log(total.value); // 300 (automatically recalculated)
602
+ ```
603
+
604
+ #### `watch(source, callback, options)`
605
+
606
+ Watches a reactive source and runs a callback when its value changes.
607
+
608
+ **Use when:** You need to react to specific state changes with custom logic (different from `effect`).
609
+
610
+ ```js
611
+ import { reactive, watch } from ".app/reactive.js";
612
+
613
+ const count = reactive(0);
614
+
615
+ // Watch runs only when count changes (not immediately)
616
+ watch(
617
+ () => count.value,
618
+ (newValue, oldValue, onCleanup) => {
619
+ console.log(`Count changed from ${oldValue} to ${newValue}`);
620
+
621
+ // Cleanup function for previous effect
622
+ onCleanup(() => {
623
+ console.log('Cleaning up previous watch effect');
624
+ });
625
+ },
626
+ { immediate: false } // Run immediately on setup
627
+ );
628
+
629
+ count.value++; // Callback runs
630
+ ```
631
+
632
+ ### In Components
633
+
634
+ ```html
635
+ <script client>
636
+ import { reactive, computed, effect, watch } from ".app/reactive.js";
637
+
638
+ // Reactive state
639
+ const count = reactive(0);
640
+ const step = reactive(1);
641
+
642
+ // Computed value
643
+ const doubled = computed(() => count.value * 2);
644
+
645
+ // Effect for side effects
646
+ effect(() => {
647
+ console.log(`Count changed to: ${count.value}`);
648
+ });
649
+
650
+ // Watcher for specific logic
651
+ watch(
652
+ () => count.value,
653
+ (newVal, oldVal) => {
654
+ if (newVal > 10) {
655
+ console.warn('Count is getting high!');
656
+ }
657
+ }
658
+ );
659
+
660
+ function increment() {
661
+ count.value += step.value;
662
+ }
663
+ </script>
664
+
665
+ <template>
666
+ <div>
667
+ <p>Count: {{count}}</p>
668
+ <p>Doubled: {{doubled}}</p>
669
+ <p>Step: {{step}}</p>
670
+ <button @click="increment">Increment by {{step}}</button>
671
+ </div>
672
+ </template>
673
+ ```
674
+
675
+ ### Reactivity Comparison
676
+
677
+ | Function | When to Use | Auto-runs | Returns |
678
+ |----------|-------------|-----------|----------|
679
+ | `reactive()` | Create reactive state | No | Proxy object |
680
+ | `effect()` | Side effects (logging, DOM, etc.) | Yes (immediately + on changes) | Cleanup function |
681
+ | `computed()` | Derived/calculated values | Yes (on dependency change) | Reactive value |
682
+ | `watch()` | React to specific changes | Optional (with `immediate`) | Nothing |
683
+
684
+ ## πŸ“ Template Syntax
685
+
686
+ Components use a template syntax that supports interpolation, directives, and event handlers.
687
+
688
+ > **Note:** The template syntax is inspired by Vue.js for educational purposes. This framework was created as a learning exercise to understand how modern frameworks work internally while practicing Vue.js concepts.
689
+
690
+ ### Interpolation
691
+
692
+ ```html
693
+ <template>
694
+ <h1>Hello, {{name}}</h1>
695
+ <p>Count: {{counter}}</p>
696
+ </template>
697
+ ```
698
+
699
+ ### Conditionals
700
+
701
+ ```html
702
+ <template>
703
+ <div v-if="isVisible">
704
+ This is visible
705
+ </div>
706
+ <div v-else>
707
+ This is hidden
708
+ </div>
709
+ </template>
710
+ ```
711
+
712
+ ### Lists
713
+
714
+ ```html
715
+ <template>
716
+ <ul>
717
+ <li v-for="item in items">
718
+ {{item}}
719
+ </li>
720
+ </ul>
721
+ </template>
722
+ ```
723
+
724
+ ### Event Handlers
725
+
726
+ ```html
727
+ <template>
728
+ <button @click="increment">Click me</button>
729
+ <input @input="handleInput" />
730
+ </template>
731
+ ```
732
+
733
+ ### Attributes
734
+
735
+ ```html
736
+ <template>
737
+ <button :disabled="counter <= 0">Decrement</button>
738
+ <div :class="isActive ? 'active' : ''">Content</div>
739
+ </template>
740
+ ```
741
+
742
+ ## πŸ›£οΈ Routing
743
+
744
+ ### Auto-Generated Routes
745
+
746
+ Routes are automatically generated from the `pages/` folder structure:
747
+
748
+ ```
749
+ pages/
750
+ β”œβ”€β”€ page.html β†’ /
751
+ β”œβ”€β”€ page-ssr/page.html β†’ /page-ssr
752
+ β”œβ”€β”€ page-csr/page.html β†’ /page-csr
753
+ └── page-ssr/[city]/page.html β†’ /page-ssr/:city (dynamic)
754
+ ```
755
+
756
+ ### Dynamic Routes
757
+
758
+ Create dynamic routes using `[param]` syntax:
759
+
760
+ ```html
761
+ <!-- pages/page-ssr/[city]/page.html -->
762
+ <script server>
763
+ import { useRouteParams } from ".app/navigation/use-route-params.js";
764
+
765
+ async function getData() {
766
+ const { city } = useRouteParams();
767
+ // Fetch data based on city parameter
768
+ return { city };
769
+ }
770
+ </script>
771
+
772
+ <template>
773
+ <h1>Weather for {{city}}</h1>
774
+ </template>
775
+ ```
776
+
777
+ ### Client-Side Navigation
778
+
779
+ ```js
780
+ import { navigate } from ".app/navigation.js";
781
+
782
+ // Navigate without page reload
783
+ navigate("/page-ssr");
784
+ ```
785
+
786
+ ### Accessing Route Parameters
787
+
788
+ ```js
789
+ import { useRouteParams } from ".app/navigation/use-route-params.js";
790
+ import { useQueryParams } from ".app/navigation/use-query-params.js";
791
+
792
+ // Get route parameters (/page/:id)
793
+ const { id } = useRouteParams();
794
+
795
+ // Get query parameters (?search=query)
796
+ const { search } = useQueryParams();
797
+ ```
798
+
799
+ ## ⚑ Prefetching
800
+
801
+ The framework automatically prefetches pages to improve navigation performance.
802
+
803
+ ### Automatic Prefetching
804
+
805
+ Add the `data-prefetch` attribute to any link to prefetch the page when it enters the viewport:
806
+
807
+ ```html
808
+ <template>
809
+ <nav>
810
+ <a href="/page-ssr" data-prefetch>SSR Page (Prefetched)</a>
811
+ <a href="/page-csr" data-prefetch>CSR Page (Prefetched)</a>
812
+ <a href="/static">Static Page (No prefetch)</a>
813
+ </nav>
814
+ </template>
815
+ ```
816
+
817
+ **How it works:**
818
+ 1. Links with `data-prefetch` are observed using IntersectionObserver
819
+ 2. When a link becomes visible, the page component is loaded in the background
820
+ 3. Navigation to prefetched pages is instant (no loading delay)
821
+ 4. Components are cached for subsequent navigations
822
+
823
+ **Benefits:**
824
+ - ⚑ Near-instant page transitions
825
+ - 🎯 Smart loading (only when visible)
826
+ - πŸ’Ύ Automatic caching
827
+ - πŸ”„ Works with SPA navigation
828
+
829
+ **Example in layout:**
830
+ ```html
831
+ <!-- pages/layout.html -->
832
+ <template>
833
+ <header>
834
+ <nav>
835
+ <a href="/" data-prefetch>Home</a>
836
+ <a href="/page-ssr" data-prefetch>SSR</a>
837
+ <a href="/page-csr" data-prefetch>CSR</a>
838
+ <a href="/static" data-prefetch>Static</a>
839
+ </nav>
840
+ </header>
841
+ </template>
842
+ ```
843
+
844
+ **Performance tip:** Use prefetching for frequently accessed pages or important navigation paths.
845
+
846
+ ## 🎨 Styling
847
+
848
+ The project uses **Tailwind CSS v4** via CDN for simplicity and zero configuration.
849
+
850
+ **Why CDN?**
851
+ - βœ… No build step required
852
+ - βœ… Instant setup
853
+ - βœ… Automatic updates
854
+ - βœ… Perfect for prototyping and learning
855
+
856
+ **Usage:**
857
+
858
+ ```html
859
+ <div class="flex items-center justify-center p-4 bg-blue-500">
860
+ <h1 class="text-white text-2xl">Title</h1>
861
+ </div>
862
+ ```
863
+
864
+ **Compilation:**
865
+
866
+ During development, Tailwind CLI watches your files and compiles styles:
867
+
868
+ ```bash
869
+ pnpm dev # Runs Tailwind in watch mode + server
870
+ ```
871
+
872
+ The compiled CSS is automatically generated in `.app/client/styles.css`.
873
+
874
+ ## πŸ”§ Framework API
875
+
876
+ ### Component Props
877
+
878
+ ```js
879
+ const props = vprops({
880
+ userId: { required: true },
881
+ count: { default: 0 },
882
+ name: { default: "Guest" },
883
+ });
884
+ ```
885
+
886
+ ### Reactive State
887
+
888
+ ```js
889
+ import { reactive, computed } from ".app/reactive.js";
890
+
891
+ // Reactive primitive
892
+ const count = reactive(0);
893
+ count.value++;
894
+
895
+ // Reactive object
896
+ const state = reactive({ name: "Alice", age: 25 });
897
+ state.name = "Bob";
898
+
899
+ // Computed values
900
+ const doubled = computed(() => count.value * 2);
901
+ ```
902
+
903
+ ### Navigation Utilities
904
+
905
+ ```js
906
+ import { navigate } from ".app/navigation.js";
907
+ import { useRouteParams } from ".app/navigation/use-route-params.js";
908
+ import { useQueryParams } from ".app/navigation/use-query-params.js";
909
+
910
+ // Navigate to a route
911
+ navigate("/page-ssr/madrid");
912
+
913
+ // Access route params
914
+ const { city } = useRouteParams();
915
+
916
+ // Access query params
917
+ const { search } = useQueryParams();
918
+ ```
919
+
920
+ ## πŸ“¦ Available Scripts
921
+
922
+ ```bash
923
+ pnpm dev # Development server: builds + watches (auto-reloads on changes)
924
+ pnpm build # Production build: generates routes, client bundles and minifies CSS
925
+ pnpm start # Production server: requires a prior pnpm build
926
+ pnpm biome check --write . # Format and lint with Biome
927
+ ```
928
+
929
+ > ⚠️ `pnpm start` requires `pnpm build` to have been run first. In production the server loads pre-built routes from `_routes.js` without executing the build pipeline.
930
+
931
+ ## πŸ—οΈ Rendering Flow
932
+
933
+ ### SSR (Server-Side Rendering)
934
+
935
+ ```mermaid
936
+ ---
937
+ config:
938
+ theme: mc
939
+ ---
940
+ sequenceDiagram
941
+ autonumber
942
+ participant Client
943
+ participant Server
944
+ participant Router
945
+ participant ComponentProcessor
946
+ participant Streaming
947
+ participant Template
948
+ participant Cache
949
+
950
+ Client ->> Server: Request page (e.g., /page-ssr)
951
+ Server ->> Router: handlePageRequest(req, res, route)
952
+ Router ->> Router: Check if ISR enabled (route.meta.revalidate)
953
+
954
+ alt ISR enabled and cache valid
955
+ Router ->> Cache: getCachedComponentHtml(url, revalidateSeconds)
956
+ Cache -->> Router: Cached HTML (if not stale)
957
+ Router ->> Client: Send cached HTML
958
+ else No cache or stale
959
+ Router ->> ComponentProcessor: renderPageWithLayout(pagePath, context)
960
+ ComponentProcessor ->> ComponentProcessor: renderPage(pagePath, context)
961
+ ComponentProcessor ->> ComponentProcessor: processHtmlFile(filePath)
962
+ Note over ComponentProcessor: Extract getData, metadata,<br/>template, clientCode,<br/>serverComponents, clientComponents
963
+ ComponentProcessor ->> ComponentProcessor: getData(context)
964
+ Note over ComponentProcessor: Fetch server-side data
965
+ ComponentProcessor ->> Template: compileTemplateToHTML(template, data)
966
+ Template ->> Template: parseHTMLToNodes & processNode
967
+ Note over Template: Process Vue-like syntax:<br/>{{interpolation}}, v-if, v-for, etc.
968
+ Template -->> ComponentProcessor: Compiled HTML
969
+ ComponentProcessor ->> Streaming: renderComponents(html, serverComponents, clientComponents)
970
+ Streaming ->> Streaming: renderServerComponents(html, serverComponents)
971
+ Note over Streaming: Process <Suspense> boundaries<br/>Extract suspense components<br/>Render fallback content
972
+ Streaming ->> Streaming: renderClientComponents(html, clientComponents)
973
+ Note over Streaming: Replace client components with<br/>hydration templates<br/>Generate component scripts
974
+ Streaming -->> ComponentProcessor: { html, suspenseComponents, clientComponentsScripts }
975
+ ComponentProcessor ->> ComponentProcessor: generateClientScriptTags(...)
976
+ Note over ComponentProcessor: Generate <script> tags for<br/>client code and components
977
+ ComponentProcessor ->> ComponentProcessor: renderLayouts(pagePath, html, metadata)
978
+ Note over ComponentProcessor: Wrap page in nested layouts<br/>(innermost to outermost)<br/>Then wrap in root.html
979
+ ComponentProcessor -->> Router: { html, metadata, suspenseComponents, serverComponents }
980
+
981
+ alt No suspense components
982
+ Router ->> Client: sendResponse(res, statusCode, html)
983
+ Router ->> Cache: saveCachedComponentHtml (if ISR)
984
+ else Has suspense components (streaming)
985
+ Router ->> Client: sendStartStreamChunkResponse(res, html_before_closing)
986
+ Note over Router,Client: Stream initial HTML (before </body>)
987
+ loop For each suspense component
988
+ Router ->> Streaming: renderSuspenseComponent(suspense, serverComponents)
989
+ Streaming ->> Streaming: processServerComponents(content, serverComponents)
990
+ Note over Streaming: Render server components<br/>inside suspense boundary
991
+ Streaming -->> Router: Rendered HTML content
992
+ Router ->> Router: generateReplacementContent(suspenseId, html)
993
+ Note over Router: Generate <template> + hydration script
994
+ Router ->> Client: sendStreamChunkResponse(res, replacement_html)
995
+ end
996
+ Router ->> Client: endStreamResponse(res) - Send </body></html>
997
+ Router ->> Cache: saveCachedComponentHtml (if ISR and no errors)
998
+ end
999
+ end
1000
+ ```
1001
+
1002
+ ### CSR (Client-Side Rendering)
1003
+
1004
+ ```mermaid
1005
+ ---
1006
+ config:
1007
+ theme: mc
1008
+ ---
1009
+ sequenceDiagram
1010
+ autonumber
1011
+ participant User
1012
+ participant Browser
1013
+ participant Navigation
1014
+ participant Router
1015
+ participant Cache
1016
+ participant LayoutRenderer
1017
+ participant Component
1018
+ participant Hydrator
1019
+
1020
+ User ->> Browser: Click link or initial page load
1021
+ Browser ->> Navigation: navigate(path)
1022
+ Navigation ->> Navigation: abortPrevious()
1023
+ Note over Navigation: Cancel any in-progress navigation
1024
+ Navigation ->> Router: findRouteWithParams(path)
1025
+ Router -->> Navigation: { route, params }
1026
+ Navigation ->> Navigation: updateRouteParams(params)
1027
+ Navigation ->> Navigation: history.pushState({}, "", path)
1028
+
1029
+ alt Route is SSR (meta.ssr = true)
1030
+ Navigation ->> Navigation: layoutRenderer.reset()
1031
+ Navigation ->> Browser: fetch(path, { signal })
1032
+ Browser -->> Navigation: Response stream
1033
+ Navigation ->> Navigation: renderSSRPage(path, signal)
1034
+ Note over Navigation: Progressive rendering:<br/>- Parse <main><br/>- Parse <template><br/>- Execute <script><br/>- Update metadata
1035
+ Navigation ->> Hydrator: hydrateComponents()
1036
+ Note over Hydrator: Hydrate streamed components
1037
+ else Route is CSR (meta.ssr = false)
1038
+ alt route.meta.requiresAuth && !app.Store.loggedIn
1039
+ Navigation ->> Browser: location.href = "/account/login"
1040
+ else route.meta.guestOnly && app.Store.loggedIn
1041
+ Navigation ->> Browser: location.href = "/account"
1042
+ end
1043
+ Navigation ->> Cache: loadRouteComponent(route.path, route.component)
1044
+
1045
+ alt Component cached
1046
+ Cache -->> Navigation: Cached module
1047
+ else Component not cached
1048
+ Cache ->> Component: Dynamic import(route.component)
1049
+ Component -->> Cache: Module with hydrateClientComponent
1050
+ Cache ->> Cache: routeCache.set(path, module)
1051
+ Cache -->> Navigation: Fresh module
1052
+ end
1053
+
1054
+ Navigation ->> Component: module.hydrateClientComponent(marker)
1055
+ Component -->> Navigation: pageNode (DOM node)
1056
+
1057
+ Navigation ->> LayoutRenderer: generate({ routeLayouts, pageNode, metadata })
1058
+
1059
+ alt Layouts already rendered
1060
+ LayoutRenderer ->> LayoutRenderer: getNearestRendered(routeLayouts)
1061
+ Note over LayoutRenderer: Find nearest cached layout
1062
+ LayoutRenderer ->> LayoutRenderer: getLayoutsToRender()
1063
+ Note over LayoutRenderer: Only render new/changed layouts
1064
+ else No cached layouts
1065
+ LayoutRenderer ->> LayoutRenderer: loadLayoutModules(layouts)
1066
+ Note over LayoutRenderer: Import all layout modules
1067
+ end
1068
+
1069
+ loop For each layout (innermost to outermost)
1070
+ LayoutRenderer ->> Component: layout.hydrateClientComponent(marker, { children })
1071
+ Component -->> LayoutRenderer: layoutNode wrapping children
1072
+ LayoutRenderer ->> LayoutRenderer: renderedLayouts.set(name, { node, children })
1073
+ end
1074
+
1075
+ LayoutRenderer -->> Navigation: { layoutId, node, metadata }
1076
+
1077
+ alt Layout already exists in DOM
1078
+ Navigation ->> LayoutRenderer: patch(layoutId, node)
1079
+ LayoutRenderer ->> Browser: Replace children in existing layout
1080
+ else New layout
1081
+ Navigation ->> Browser: root.appendChild(node)
1082
+ end
1083
+
1084
+ Navigation ->> Browser: addMetadata(metadata)
1085
+ Note over Browser: Update <title> and meta tags
1086
+ Navigation ->> Hydrator: hydrateComponents()
1087
+
1088
+ loop For each component marker
1089
+ Hydrator ->> Hydrator: Check data-hydrated attribute
1090
+ alt Not hydrated
1091
+ Hydrator ->> Component: import(`/.app/client/_components/${name}.js`)
1092
+ Component -->> Hydrator: Module
1093
+ Hydrator ->> Component: module.hydrateClientComponent(marker, props)
1094
+ Component -->> Hydrator: Hydrated component
1095
+ Hydrator ->> Hydrator: marker.dataset.hydrated = "true"
1096
+ end
1097
+ end
1098
+ end
1099
+
1100
+ Navigation ->> Navigation: currentNavigationController = null
1101
+ Navigation -->> User: Page rendered and interactive
1102
+ ```
1103
+
1104
+ ### ISR (Incremental Static Regeneration)
1105
+
1106
+ ```mermaid
1107
+ ---
1108
+ config:
1109
+ theme: mc
1110
+ ---
1111
+ sequenceDiagram
1112
+ autonumber
1113
+ participant Client
1114
+ participant Server
1115
+ participant Router
1116
+ participant Cache
1117
+ participant FileSystem
1118
+ participant ComponentProcessor
1119
+
1120
+ Client ->> Server: Request page (e.g., /page-ssr/madrid)
1121
+ Server ->> Router: handlePageRequest(req, res, route)
1122
+ Router ->> Router: getRevalidateSeconds(route.meta.revalidate)
1123
+
1124
+ alt revalidate = 'never' or false
1125
+ Note over Router: ISR disabled (Pure SSG)<br/>revalidateSeconds = -1
1126
+ else revalidate = number
1127
+ Note over Router: ISR enabled<br/>revalidateSeconds = number
1128
+ else revalidate = true
1129
+ Note over Router: ISR enabled<br/>revalidateSeconds = 60 (default)
1130
+ else revalidate = 0 or undefined
1131
+ Note over Router: No caching (SSR)<br/>revalidateSeconds = 0
1132
+ end
1133
+
1134
+ alt ISR enabled (revalidateSeconds !== 0)
1135
+ Router ->> Cache: getCachedComponentHtml(url, revalidateSeconds)
1136
+ Cache ->> FileSystem: getComponentHtmlDisk(componentPath)
1137
+
1138
+ alt Cache exists on disk
1139
+ FileSystem -->> Cache: { html, meta: { generatedAt, isStale } }
1140
+ Cache ->> Cache: Calculate if stale by time
1141
+ Note over Cache: staleByTime = (now - generatedAt) > revalidateSeconds * 1000
1142
+ Cache ->> Cache: isStale = meta.isStale || staleByTime
1143
+ Cache -->> Router: { html, isStale }
1144
+
1145
+ alt Cache valid (!isStale)
1146
+ Router ->> Client: sendResponse(res, 200, cachedHtml)
1147
+ Note over Router,Client: Instant response from cache<br/>No regeneration needed
1148
+ else Cache stale (isStale = true)
1149
+ Note over Router: Continue to regeneration<br/>Client waits for fresh content (blocking)
1150
+ end
1151
+ else No cache exists
1152
+ FileSystem -->> Cache: { html: null }
1153
+ Cache -->> Router: { html: null }
1154
+ Note over Router: First request - generate and cache
1155
+ end
1156
+ end
1157
+
1158
+ alt Need to regenerate (no cache, stale, or ISR disabled)
1159
+ Router ->> ComponentProcessor: renderPageWithLayout(pagePath, context)
1160
+ ComponentProcessor ->> ComponentProcessor: Process page, fetch data, render
1161
+ ComponentProcessor -->> Router: { html, suspenseComponents, serverComponents }
1162
+
1163
+ alt No suspense components
1164
+ Router ->> Client: sendResponse(res, 200, html)
1165
+
1166
+ alt ISR enabled
1167
+ Router ->> Cache: saveCachedComponentHtml(url, html)
1168
+ Cache ->> FileSystem: saveComponentHtmlDisk(componentPath, html)
1169
+ Note over FileSystem: Save HTML + metadata:<br/>{ generatedAt: Date.now(), isStale: false }
1170
+ FileSystem -->> Cache: Saved successfully
1171
+ Cache -->> Router: Cache updated
1172
+ end
1173
+ else Has suspense components (streaming)
1174
+ Router ->> Client: sendStartStreamChunkResponse(res, html_before_closing)
1175
+ Router ->> Router: Track htmlChunks[], abortedStream, errorStream
1176
+
1177
+ loop For each suspense component
1178
+ Router ->> ComponentProcessor: renderSuspenseComponent(suspense, serverComponents)
1179
+ ComponentProcessor -->> Router: Rendered HTML content
1180
+ Router ->> Router: generateReplacementContent(suspenseId, html)
1181
+ Router ->> Client: sendStreamChunkResponse(res, replacement_html)
1182
+ Router ->> Router: Append to htmlChunks[]
1183
+ end
1184
+
1185
+ Router ->> Client: endStreamResponse(res) - Send </body></html>
1186
+
1187
+ alt ISR enabled and no errors
1188
+ Router ->> Cache: saveCachedComponentHtml(url, htmlChunks.join(''))
1189
+ Cache ->> FileSystem: saveComponentHtmlDisk(componentPath, fullHtml)
1190
+ Note over FileSystem: Save complete streamed HTML<br/>with metadata
1191
+ FileSystem -->> Cache: Saved successfully
1192
+ Cache -->> Router: Cache updated for next request
1193
+ end
1194
+ end
1195
+ end
1196
+
1197
+ Note over Client,FileSystem: Next request within revalidation window<br/>will serve cached HTML instantly
1198
+ ```
1199
+
1200
+ **ISR Flow Summary:**
1201
+
1202
+ 1. **Cache Check**: Verifies if cached HTML exists and if it's still valid
1203
+ 2. **Stale Detection**: Compares current time vs. generation time + revalidation seconds
1204
+ 3. **Instant Serve**: If cache is valid, serves immediately without regeneration
1205
+ 4. **Regeneration**: If cache is stale or missing, regenerates the page
1206
+ 5. **Background Save**: After regeneration, saves to cache for future requests
1207
+ 6. **Streaming Support**: Handles suspense components and saves complete HTML
1208
+ 7. **Error Handling**: Prevents caching if errors occur during streaming
1209
+
1210
+ ### Server Startup
1211
+
1212
+ Shows how `index.js` behaves differently in production vs development.
1213
+
1214
+ ```mermaid
1215
+ ---
1216
+ config:
1217
+ theme: mc
1218
+ ---
1219
+ sequenceDiagram
1220
+ autonumber
1221
+ participant CLI
1222
+ participant index.js
1223
+ participant Files
1224
+ participant ComponentProcessor
1225
+ participant RoutesFile as _routes.js (disk)
1226
+
1227
+ CLI ->> index.js: node .app/server/index.js
1228
+
1229
+ index.js ->> Files: initializeDirectories()
1230
+ Files -->> index.js: Directories ready (_cache/, _components/, ...)
1231
+
1232
+ alt NODE_ENV = "production"
1233
+ index.js ->> RoutesFile: import("./utils/_routes.js")
1234
+ alt _routes.js exists (pnpm build was run)
1235
+ RoutesFile -->> index.js: { routes }
1236
+ Note over index.js: Routes loaded instantly<br/>No build work done
1237
+ else _routes.js not found
1238
+ index.js ->> CLI: ERROR: Run 'pnpm build' first
1239
+ index.js ->> index.js: process.exit(1)
1240
+ end
1241
+ else NODE_ENV != "production" (development)
1242
+ index.js ->> ComponentProcessor: generateComponentsAndFillCache()
1243
+ Note over ComponentProcessor: Scan pages/, render static HTML,<br/>generate client JS bundles
1244
+ ComponentProcessor -->> index.js: Components and cache ready
1245
+ index.js ->> ComponentProcessor: generateRoutes()
1246
+ ComponentProcessor ->> RoutesFile: Write server _routes.js
1247
+ ComponentProcessor ->> RoutesFile: Write client _routes.js
1248
+ ComponentProcessor -->> index.js: { serverRoutes }
1249
+ end
1250
+
1251
+ index.js ->> index.js: registerSSRRoutes(app, serverRoutes)
1252
+ index.js ->> index.js: app.listen(PORT)
1253
+ index.js -->> CLI: Server running on port 3000
1254
+ ```
1255
+
1256
+ ### Build Process
1257
+
1258
+ Shows what `pnpm build` does step by step.
1259
+
1260
+ ```mermaid
1261
+ ---
1262
+ config:
1263
+ theme: mc
1264
+ ---
1265
+ flowchart TD
1266
+ A([pnpm build]) --> B[node .app/server/prebuild.js]
1267
+ B --> C[initializeDirectories\nCreate _cache/, _components/]
1268
+ C --> D[generateComponentsAndFillCache]
1269
+
1270
+ D --> E[getPageFiles with layouts=true]
1271
+ E --> F[For each .html file in pages/]
1272
+
1273
+ F --> G[processHtmlFile\nExtract server script, client script, template]
1274
+ G --> H[Execute server script\nget getData, getMetadata, getStaticPaths]
1275
+ H --> I{Has getStaticPaths?}
1276
+
1277
+ I -->|Yes| J[getStaticPaths\nreturns array of params]
1278
+ I -->|No| K[Single path with empty params]
1279
+
1280
+ J --> L[For each param set:\nrenderHtmlFile with req.params]
1281
+ K --> L
1282
+
1283
+ L --> M{canCSR?\nneverRevalidate AND no server components\nAND no getData}
1284
+ M -->|Yes - CSR page| N[saveClientComponent\nGenerate .js bundle in _components/]
1285
+ M -->|No - SSR/ISR/SSG page| O[saveComponentHtmlDisk\nSave rendered HTML to _cache/]
1286
+
1287
+ N --> P{Has nested server\nor client components?}
1288
+ O --> P
1289
+ P -->|Yes| Q[Recursively process\neach referenced component]
1290
+ P -->|No| R[Page done]
1291
+ Q --> R
1292
+
1293
+ R --> S[generateRoutes]
1294
+ S --> T[getPageFiles without layouts]
1295
+ T --> U[For each page: getRouteFileData\nResolve canCSR, metadata, static paths]
1296
+ U --> V[saveServerRoutesFile\n.app/server/utils/_routes.js]
1297
+ U --> W[saveClientRoutesFile\n.app/client/services/_routes.js]
1298
+
1299
+ V --> X[Tailwind CSS minify\n_input.css β†’ styles.css]
1300
+ W --> X
1301
+ X --> Y([Build complete βœ…])
1302
+ ```
1303
+
1304
+ **`canCSR` logic:** A page is treated as CSR (client-only bundle, no server HTML) when all three conditions are true:
1305
+ - `revalidate` is `false` or `"never"` (i.e. never needs server refresh)
1306
+ - No server components (`<script server>`) in the tree
1307
+ - No `getData` function
1308
+
1309
+ Otherwise the page is SSR/ISR/SSG and its HTML is pre-rendered into `_cache/`.
1310
+
1311
+ ### Client Hydration
1312
+
1313
+ Shows how `hydrate-client-components.js` mounts interactive components after the HTML is in the DOM.
1314
+
1315
+ ```mermaid
1316
+ ---
1317
+ config:
1318
+ theme: mc
1319
+ ---
1320
+ sequenceDiagram
1321
+ autonumber
1322
+ participant HTML as Browser DOM
1323
+ participant Script as hydrate-client-components.js
1324
+ participant Observer as MutationObserver
1325
+ participant Module as _components/{name}.js
1326
+
1327
+ HTML ->> Script: <script src="..."> loads (IIFE, non-module)
1328
+ Script ->> Observer: observe(document, childList + subtree)
1329
+ Note over Observer: Watches for nodes added by SSR streaming
1330
+
1331
+ alt document.readyState = "loading"
1332
+ HTML -->> Script: DOMContentLoaded event fires
1333
+ Script ->> Script: hydrateComponents(document)
1334
+ Script ->> Observer: disconnect()
1335
+ Note over Observer: Streaming content already parsed,<br/>observer no longer needed
1336
+ else document already interactive
1337
+ Script ->> Script: hydrateComponents(document) immediately
1338
+ end
1339
+
1340
+ loop For each [data-client:component]:not([data-hydrated="true"])
1341
+ Script ->> Script: Read data-client:component (component name/hash)
1342
+ Script ->> Script: JSON.parse(data-client:props)
1343
+ Script ->> Module: import(/.app/client/_components/${name}.js)
1344
+ Module -->> Script: { hydrateClientComponent }
1345
+ Script ->> Module: hydrateClientComponent(marker, props)
1346
+ Note over Module: Replaces <template> marker with<br/>reactive DOM, binds events
1347
+ Script ->> Script: marker.dataset.hydrated = "true"
1348
+ end
1349
+
1350
+ Note over Script: window.hydrateComponents exposed globally<br/>Called by SPA navigation after each route change
1351
+ ```
1352
+
1353
+ **Key detail:** The `<script>` tag that loads this file has no `type="module"` β€” it's an IIFE that runs synchronously and exposes `window.hydrateComponents` for the SPA router to call after each navigation.
1354
+
1355
+ ## πŸ—ΊοΈ Roadmap
1356
+
1357
+ Future features planned for implementation:
1358
+
1359
+ - [x] **Language sintaxis** - Unify sintaxis to use same conditional tags, lists, etc in server and client.
1360
+ - [x] **Inject client component script** - Use other technique to inject js in client, to avoid have scripts in html, check Nextjs, svelte, vue
1361
+ - [x] **Metadata dynamic** - Optional add export func if the user wants to fetch. Also this func can receive the result of getData to not repeat the same fetch
1362
+ - [x] **Add dynamic pages CSR and SSR** - Add dynamic routes
1363
+ - [x] **Incremental Static Regeneration / Static Pages** - Regenerate static pages on-demand / never
1364
+ - [x] **Generate Static Params** - Pre-generate pages with dynamic routes at build time
1365
+ - [x] **Auto generated routes** - Auto generate server and client routes
1366
+ - [x] **Unify fs methods** - Same constants files, unify fs functions in files, unify comments, etc
1367
+ - [x] **Cache getData** - Implement caching layer for data fetching functions
1368
+ - [x] **Cache Server Pages** - Cache rendered server pages for improved performance
1369
+ - [x] **Link Component** - Custom link component with prefetching capabilities
1370
+ - [x] **Prefetch Pages** - Automatically prefetch pages on link hover/visibility
1371
+ - [x] **Restructure Directories** - Optimize project structure and organization
1372
+ - [x] **Auto-generated Files** - Automatic generation of routes, utility files, and configurations based on code and pages directories
1373
+ - [x] **Auto-generated Components** - Automatic generation components only based on pages imports
1374
+ - [x] **Optimize auto generated routes**
1375
+ - [x] **Layouts** - Layouts inside sub routes
1376
+ - [ ] **Regeneration in background** - Regenerate page after send response (locks)
1377
+ - [ ] **Change syntax**
1378
+ - [ ] **Create NPM extension package** - Extension to recognize sintax
1379
+ - [ ] **Use custom extension** - custom extension to have correct imports, lint, colors, etc.
1380
+ - [ ] **Create NPM package** - Create package to save all logic framework and reused it in other projects
1381
+ - [ ] **Cache with CDN**
1382
+ - [ ] **Fix error replace marker** Only occurs when template has multiple childs no wrapped in div /fragment
1383
+ - [ ] **Authentication** - Built-in authentication system with middleware support