@mateosuarezdev/flash 0.0.1

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 ADDED
@@ -0,0 +1,945 @@
1
+ # ⚡ Flash
2
+
3
+ > Fine-grained reactive JSX framework with zero VDOM overhead
4
+
5
+ Flash is a lightweight, performant JSX framework built on Preact Signals for fine-grained reactivity. Write familiar JSX, get blazing fast updates with direct DOM manipulation.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ ### Client-Side
12
+ - ⚡ **Fine-grained reactivity** - Powered by Preact Signals, updates only what changed
13
+ - 🎯 **No Virtual DOM** - Direct DOM manipulation for maximum performance
14
+ - 🎭 **Flexible animations** - Use any library (Framer Motion, GSAP, etc.) or vanilla CSS/classes
15
+ - 🎪 **DOM resurrection** - Automatic animation reversal for rapid toggling
16
+ - 🔑 **Keyed list rendering** - SolidJS-style efficient list updates (insert/update/delete/move)
17
+ - 🎨 **View Transitions API** - Smooth page transitions out of the box
18
+ - 🏎️ **Frame scheduler** - Prevent layout thrashing with read/update/render phases
19
+ - 🎬 **FLIP animations** - Built-in utilities for performant layout animations
20
+ - 🪄 **Auto-animate** - Automatic layout animations (in progress)
21
+ - 🪝 **First-class exit animations** - `onBeforeExit` pauses unmounting (save data, animations, etc.)
22
+ - 🌳 **Context API** - Share state across component trees
23
+ - 🧭 **Built-in Router** - File-based routing with lazy loading (work in progress)
24
+ - 📦 **Tiny bundle size** - No compiler required, minimal runtime
25
+ - 🔄 **Reactive props** - Props can be signals for automatic updates
26
+
27
+ ### Server-Side
28
+ - 🌐 **Server-Side Rendering** - Three rendering strategies (sync, async, streaming)
29
+ - ⚡ **Parallel async resolution** - Resolves async components efficiently
30
+ - 📦 **Pre-rendering & caching** - Built-in static site generation
31
+ - 🌊 **Progressive enhancement** - Stream HTML for better perceived performance
32
+ - 🔒 **Automatic XSS protection** - HTML escaping by default
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @mateosuarezdev/flash @preact/signals-core
40
+ ```
41
+
42
+ Configure your `tsconfig.json`:
43
+
44
+ ```json
45
+ {
46
+ "compilerOptions": {
47
+ "jsx": "react-jsx",
48
+ "jsxImportSource": "@mateosuarezdev/flash/runtime"
49
+ }
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ```tsx
58
+ import { render, onMount } from "@mateosuarezdev/flash";
59
+ import { signal } from "@preact/signals-core";
60
+
61
+ const count = signal(0);
62
+
63
+ function Counter() {
64
+ onMount(() => {
65
+ console.log("Counter mounted!");
66
+ });
67
+
68
+ return (
69
+ <div>
70
+ <h1>Count: {() => count.value}</h1>
71
+ <button onClick={() => count.value++}>Increment</button>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ render(<Counter />, document.getElementById("app"));
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Core Concepts
82
+
83
+ ### Reactive Boundaries
84
+
85
+ Wrap expressions in functions to create reactive boundaries that update automatically when signals change:
86
+
87
+ ```tsx
88
+ import { signal } from "@preact/signals-core";
89
+
90
+ const name = signal("World");
91
+
92
+ function Greeting() {
93
+ return (
94
+ <div>
95
+ {/* This updates when name changes */}
96
+ <h1>Hello {() => name.value}!</h1>
97
+
98
+ {/* Conditional rendering */}
99
+ {() =>
100
+ name.value === "World" ? (
101
+ <p>Welcome!</p>
102
+ ) : (
103
+ <p>Hello, {() => name.value}!</p>
104
+ )
105
+ }
106
+ </div>
107
+ );
108
+ }
109
+ ```
110
+
111
+ ### Reactive Props
112
+
113
+ Props can be functions that automatically update:
114
+
115
+ ```tsx
116
+ const isDark = signal(false)
117
+
118
+ <button
119
+ className={() => isDark.value ? 'dark' : 'light'}
120
+ disabled={() => !isDark.value}
121
+ >
122
+ Toggle
123
+ </button>
124
+ ```
125
+
126
+ ### Keyed Lists
127
+
128
+ Use the `key` prop for efficient list rendering:
129
+
130
+ ```tsx
131
+ const items = signal([
132
+ { id: 1, name: "Apple" },
133
+ { id: 2, name: "Banana" },
134
+ { id: 3, name: "Cherry" },
135
+ ]);
136
+
137
+ function List() {
138
+ return (
139
+ <ul>
140
+ {() => items.value.map((item) => <li key={item.id}>{item.name}</li>)}
141
+ </ul>
142
+ );
143
+ }
144
+ ```
145
+
146
+ Flash efficiently handles:
147
+
148
+ - ✅ **Inserts** - New items are rendered and inserted
149
+ - ✅ **Removals** - Deleted items are unmounted and removed
150
+ - ✅ **Reordering** - DOM nodes are moved to match new order
151
+ - ✅ **Updates** - Existing items are reused (no re-render)
152
+
153
+ ---
154
+
155
+ ## Lifecycle Hooks
156
+
157
+ ### onMount
158
+
159
+ Runs after the component is mounted to the DOM:
160
+
161
+ ```tsx
162
+ function Component() {
163
+ let ref: HTMLDivElement;
164
+
165
+ onMount(() => {
166
+ console.log("Element:", ref);
167
+ // Fetch data, start animations, etc.
168
+ });
169
+
170
+ return <div ref={(el) => (ref = el)}>Content</div>;
171
+ }
172
+ ```
173
+
174
+ ### onUnmount
175
+
176
+ Runs when the component is removed from the DOM:
177
+
178
+ ```tsx
179
+ function Component() {
180
+ onUnmount(() => {
181
+ console.log("Cleaning up...");
182
+ // Cancel subscriptions, clear timers, etc.
183
+ });
184
+
185
+ return <div>Content</div>;
186
+ }
187
+ ```
188
+
189
+ ### onBeforeExit
190
+
191
+ Runs before unmounting - a **first-class feature**, not a workaround. You can pause the entire unmount process to:
192
+ - Play exit animations
193
+ - Save form data or state
194
+ - Confirm user actions
195
+ - Clean up async operations
196
+ - Anything you need before removal
197
+
198
+ The unmount tree is held until your async callback completes:
199
+
200
+ ```tsx
201
+ import { animate } from "framer-motion";
202
+
203
+ function FadeBox() {
204
+ let ref: HTMLElement;
205
+
206
+ onBeforeExit(async (token) => {
207
+ // Play exit animation
208
+ const animation = animate(ref, { opacity: 0 }, { duration: 0.3 });
209
+
210
+ // Handle cancellation (e.g., rapid toggling)
211
+ token.onCancel(() => {
212
+ animation.stop();
213
+ animate(ref, { opacity: 1 }, { duration: 0.3 });
214
+ });
215
+
216
+ await animation.finished;
217
+ // Component won't unmount until animation completes
218
+ });
219
+
220
+ return <div ref={(el) => (ref = el)}>Fading content</div>;
221
+ }
222
+ ```
223
+
224
+ **Save data before unmounting:**
225
+
226
+ ```tsx
227
+ function Form() {
228
+ const formData = signal({ name: "", email: "" });
229
+
230
+ onBeforeExit(async () => {
231
+ // Save to localStorage or API
232
+ await saveFormData(formData.value);
233
+ console.log("Data saved before unmount!");
234
+ });
235
+
236
+ return <form>...</form>;
237
+ }
238
+ ```
239
+
240
+ **Cancellation Example:**
241
+
242
+ ```tsx
243
+ const show = signal(true)
244
+
245
+ // Rapid toggling: true → false → true
246
+ // Flash will cancel the exit animation and reuse the DOM!
247
+ <button onClick={() => show.value = !show.value}>
248
+ Toggle
249
+ </button>
250
+
251
+ {() => show.value && <FadeBox />}
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Context API
257
+
258
+ Share state across component trees without prop drilling:
259
+
260
+ ```tsx
261
+ import { createContext, useContext } from "@mateosuarezdev/flash";
262
+
263
+ const ThemeContext = createContext({ theme: "light" });
264
+
265
+ function App() {
266
+ ThemeContext.provide({ theme: "dark" });
267
+
268
+ return <Child />;
269
+ }
270
+
271
+ function Child() {
272
+ const theme = useContext(ThemeContext);
273
+ console.log(theme); // { theme: 'dark' }
274
+
275
+ return <div>Theme: {theme.theme}</div>;
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Router (Work in Progress)
282
+
283
+ Flash includes a built-in router with reactive pathname tracking and custom URL change events:
284
+
285
+ ```tsx
286
+ import { Router, pathname, push, replace, back, onUrlChange } from '@mateosuarezdev/flash/router'
287
+
288
+ function App() {
289
+ return (
290
+ <Router>
291
+ <nav>
292
+ <a href="/" onClick={(e) => { e.preventDefault(); push('/') }}>Home</a>
293
+ <a href="/about" onClick={(e) => { e.preventDefault(); push('/about') }}>About</a>
294
+ </nav>
295
+
296
+ {/* Reactive routing based on pathname signal */}
297
+ {() => {
298
+ switch (pathname.value) {
299
+ case '/':
300
+ return <Home />
301
+ case '/about':
302
+ return <About />
303
+ default:
304
+ return <NotFound />
305
+ }
306
+ }}
307
+ </Router>
308
+ )
309
+ }
310
+ ```
311
+
312
+ ### Core Router Features
313
+
314
+ **Reactive Pathname Tracking:**
315
+ ```tsx
316
+ import { pathname } from '@mateosuarezdev/flash/router'
317
+
318
+ // pathname is a signal that updates on navigation
319
+ function NavBar() {
320
+ return (
321
+ <nav>
322
+ <a
323
+ class={() => pathname.value === '/' ? 'active' : ''}
324
+ href="/"
325
+ >
326
+ Home
327
+ </a>
328
+ </nav>
329
+ )
330
+ }
331
+ ```
332
+
333
+ **Programmatic Navigation:**
334
+ ```tsx
335
+ import { push, replace, back } from '@mateosuarezdev/flash/router'
336
+
337
+ // Navigate to a new route
338
+ push('/dashboard')
339
+
340
+ // Replace current route (no history entry)
341
+ replace('/login')
342
+
343
+ // Go back in history
344
+ back()
345
+ ```
346
+
347
+ **URL Change Events:**
348
+ ```tsx
349
+ import { onUrlChange } from '@mateosuarezdev/flash/router'
350
+
351
+ function Component() {
352
+ onUrlChange((event) => {
353
+ if (event) {
354
+ console.log('Navigation:', event.action) // 'pushState' | 'replaceState' | 'popstate' | 'beforeunload'
355
+ console.log('From:', event.oldURL?.pathname)
356
+ console.log('To:', event.newURL?.pathname)
357
+
358
+ // Prevent navigation by calling event.preventDefault()
359
+ // Great for unsaved changes warnings
360
+ }
361
+ })
362
+
363
+ return <div>Content</div>
364
+ }
365
+ ```
366
+
367
+ **Custom UrlChangeEvent:**
368
+
369
+ The router automatically intercepts and dispatches custom `urlchangeevent` for all navigation:
370
+ - `pushState` - New history entry
371
+ - `replaceState` - Replace current entry
372
+ - `popstate` - Back/forward navigation
373
+ - `beforeunload` - Page close/reload
374
+
375
+ Events can be prevented to block navigation (useful for form guards, unsaved changes, etc.)
376
+
377
+ **Current Features:**
378
+ - ✅ Reactive pathname signal
379
+ - ✅ Programmatic navigation (push, replace, back)
380
+ - ✅ Custom URL change events with prevention
381
+ - ✅ History state tracking
382
+ - ✅ Lifecycle integration (auto-cleanup with onUnmount)
383
+
384
+ **Planned Features:**
385
+ - 📁 File-based routing with automatic route generation
386
+ - 🔀 Lazy loading and code splitting helpers
387
+ - 🎨 Integrated View Transitions API support
388
+ - 📱 Nested routes and layouts
389
+ - 🎯 Route guards and middleware
390
+ - 🔍 Path parameter extraction
391
+ - 🔗 Link component with active state
392
+
393
+ **Note:** The router is currently in active development. The current implementation provides low-level primitives for building routing solutions!
394
+
395
+ ---
396
+
397
+ ## Animations & Performance
398
+
399
+ Flash is designed to be a **batteries-included framework** with powerful animation and performance utilities built right in.
400
+
401
+ ### Flexible Animation Options
402
+
403
+ Flash gives you **complete freedom** to animate however you want:
404
+
405
+ #### 1. Use Any Animation Library
406
+
407
+ ```tsx
408
+ import { animate } from 'framer-motion'
409
+ import { animate as animateJsAnimate } from 'animejs'
410
+
411
+ function Component() {
412
+ let ref: HTMLElement
413
+
414
+ onMount(() => {
415
+ // Framer Motion
416
+ animate(ref, { x: 100 }, { duration: 0.3 })
417
+
418
+ // Anime.js, GSAP, Motion One, or any library!
419
+ })
420
+
421
+ onBeforeExit(async (token) => {
422
+ const animation = animate(ref, { opacity: 0 }, { duration: 0.3 })
423
+
424
+ token.onCancel(() => {
425
+ animation.stop()
426
+ animate(ref, { opacity: 1 }, { duration: 0.3 })
427
+ })
428
+
429
+ await animation.finished
430
+ })
431
+
432
+ return <div ref={(el) => ref = el}>Animated</div>
433
+ }
434
+ ```
435
+
436
+ #### 2. Vanilla CSS Transitions
437
+
438
+ ```tsx
439
+ function Component() {
440
+ let ref: HTMLElement
441
+
442
+ onMount(() => {
443
+ ref.style.transition = 'opacity 300ms'
444
+ ref.style.opacity = '0'
445
+ setTimeout(() => ref.style.opacity = '1', 10)
446
+ })
447
+
448
+ onBeforeExit(async () => {
449
+ ref.style.opacity = '0'
450
+ await new Promise(resolve => setTimeout(resolve, 300))
451
+ })
452
+
453
+ return <div ref={(el) => ref = el}>CSS Animated</div>
454
+ }
455
+ ```
456
+
457
+ #### 3. Toggle CSS Classes
458
+
459
+ ```tsx
460
+ function Component() {
461
+ let ref: HTMLElement
462
+
463
+ onMount(() => {
464
+ requestAnimationFrame(() => {
465
+ ref.classList.add('enter-active')
466
+ setTimeout(() => ref.classList.remove('enter-active'), 300)
467
+ })
468
+ })
469
+
470
+ onBeforeExit(async () => {
471
+ ref.classList.add('exit-active')
472
+ await new Promise(resolve => setTimeout(resolve, 300))
473
+ })
474
+
475
+ return <div ref={(el) => ref = el} class="animated">Content</div>
476
+ }
477
+ ```
478
+
479
+ ### Built-in Performance Utilities
480
+
481
+ #### Frame Scheduler (Prevent Layout Thrashing)
482
+
483
+ Inspired by Framer Motion's frame loop, Flash includes a high-performance scheduler that prevents layout thrashing by separating read/update/render phases:
484
+
485
+ ```tsx
486
+ import { frame } from '@mateosuarezdev/flash'
487
+
488
+ // Simple usage
489
+ frame.read(() => {
490
+ const height = element.offsetHeight // DOM reads
491
+ })
492
+
493
+ frame.update(() => {
494
+ position += velocity // Calculations
495
+ })
496
+
497
+ frame.render(() => {
498
+ element.style.transform = `translateY(${position}px)` // DOM writes
499
+ })
500
+
501
+ // Chained operations with type-safe data flow
502
+ frame.chain({
503
+ read: () => element.offsetHeight,
504
+ update: (height) => height * 2,
505
+ render: (doubled) => element.style.height = `${doubled}px`
506
+ })
507
+
508
+ // Keep-alive for continuous animations
509
+ const animate = frame.render(() => {
510
+ element.style.transform = `rotate(${rotation}deg)`
511
+ }, true) // true = runs every frame
512
+
513
+ // Cancel when done
514
+ frame.cancel(animate)
515
+ ```
516
+
517
+ #### FLIP Animations
518
+
519
+ Built-in FLIP (First, Last, Invert, Play) utilities for performant layout animations:
520
+
521
+ ```tsx
522
+ import { flip, flipGroup } from '@mateosuarezdev/flash'
523
+
524
+ // Animate a single element
525
+ flip(element, () => {
526
+ // Make DOM changes
527
+ element.classList.add('expanded')
528
+ element.style.width = '400px'
529
+ }, {
530
+ duration: 300,
531
+ easing: 'ease-out-cubic'
532
+ })
533
+
534
+ // Animate list reordering
535
+ const items = document.querySelectorAll('.item')
536
+
537
+ flipGroup(items, () => {
538
+ // Reorder items
539
+ container.appendChild(items[2])
540
+ }, {
541
+ duration: 400,
542
+ easing: 'ease-out-cubic'
543
+ })
544
+ ```
545
+
546
+ #### Auto-Animate (Work in Progress)
547
+
548
+ Automatically animate layout changes (inspired by Framer Motion's layout animations):
549
+
550
+ ```tsx
551
+ <div autoanimate>
552
+ {/* Children automatically animate when added/removed/reordered */}
553
+ {() => items.value.map(item => (
554
+ <div key={item.id}>{item.name}</div>
555
+ ))}
556
+ </div>
557
+ ```
558
+
559
+ **Note:** Auto-animate is currently in development and will provide automatic FLIP animations for layout changes without manual setup.
560
+
561
+ ### View Transitions API
562
+
563
+ Built-in support for the browser's View Transitions API:
564
+
565
+ ```tsx
566
+ import { startViewTransition } from '@mateosuarezdev/flash'
567
+
568
+ const expanded = signal(false)
569
+
570
+ <button onClick={() => {
571
+ startViewTransition(() => {
572
+ expanded.value = !expanded.value
573
+ })
574
+ }}>
575
+ Toggle
576
+ </button>
577
+
578
+ <div
579
+ className={() => expanded.value ? 'expanded' : 'collapsed'}
580
+ viewTransitionName="container"
581
+ >
582
+ Content
583
+ </div>
584
+ ```
585
+
586
+ ### Performance Best Practices
587
+
588
+ **Use the Frame Scheduler:**
589
+ ```tsx
590
+ // ❌ Bad: Layout thrashing
591
+ const height = element.offsetHeight // Read
592
+ element.style.height = `${height * 2}px` // Write
593
+ const width = element.offsetWidth // Read (forces reflow!)
594
+ element.style.width = `${width * 2}px` // Write
595
+
596
+ // ✅ Good: Batched reads and writes
597
+ frame.chain({
598
+ read: () => ({
599
+ height: element.offsetHeight,
600
+ width: element.offsetWidth
601
+ }),
602
+ render: ({ height, width }) => {
603
+ element.style.height = `${height * 2}px`
604
+ element.style.width = `${width * 2}px`
605
+ }
606
+ })
607
+ ```
608
+
609
+ **Use FLIP for Layout Changes:**
610
+ ```tsx
611
+ // ❌ Bad: Animating layout properties directly
612
+ element.animate({ width: '400px', height: '300px' }, { duration: 300 })
613
+
614
+ // ✅ Good: Use FLIP to transform instead
615
+ flip(element, () => {
616
+ element.style.width = '400px'
617
+ element.style.height = '300px'
618
+ }, { duration: 300 })
619
+ ```
620
+
621
+ ---
622
+
623
+ ## Advanced Examples
624
+
625
+ ### Counter with Computed Values
626
+
627
+ ```tsx
628
+ import { signal, computed } from "@preact/signals-core";
629
+
630
+ const count = signal(0);
631
+ const double = computed(() => count.value * 2);
632
+
633
+ function Counter() {
634
+ return (
635
+ <div>
636
+ <p>Count: {() => count.value}</p>
637
+ <p>Double: {() => double.value}</p>
638
+ <button onClick={() => count.value++}>Increment</button>
639
+ </div>
640
+ );
641
+ }
642
+ ```
643
+
644
+ ### Dynamic List with Add/Remove
645
+
646
+ ```tsx
647
+ import { signal } from "@preact/signals-core";
648
+
649
+ const items = signal([
650
+ { id: 1, name: "Task 1" },
651
+ { id: 2, name: "Task 2" },
652
+ ]);
653
+
654
+ let nextId = 3;
655
+
656
+ function TodoList() {
657
+ const addItem = () => {
658
+ items.value = [...items.value, { id: nextId++, name: `Task ${nextId}` }];
659
+ };
660
+
661
+ const removeItem = (id: number) => {
662
+ items.value = items.value.filter((item) => item.id !== id);
663
+ };
664
+
665
+ return (
666
+ <div>
667
+ <button onClick={addItem}>Add Task</button>
668
+ <ul>
669
+ {() =>
670
+ items.value.map((item) => (
671
+ <li key={item.id}>
672
+ {item.name}
673
+ <button onClick={() => removeItem(item.id)}>Delete</button>
674
+ </li>
675
+ ))
676
+ }
677
+ </ul>
678
+ </div>
679
+ );
680
+ }
681
+ ```
682
+
683
+ ### Nested Reactive Updates
684
+
685
+ ```tsx
686
+ const user = signal({ name: "John", age: 25 });
687
+
688
+ function Profile() {
689
+ return (
690
+ <div>
691
+ <h1>{() => user.value.name}</h1>
692
+ <p>Age: {() => user.value.age}</p>
693
+ <button
694
+ onClick={() => {
695
+ user.value = { ...user.value, age: user.value.age + 1 };
696
+ }}
697
+ >
698
+ Birthday
699
+ </button>
700
+ </div>
701
+ );
702
+ }
703
+ ```
704
+
705
+ ### Conditional Rendering with Animations
706
+
707
+ ```tsx
708
+ import { animate } from "framer-motion";
709
+
710
+ const show = signal(true);
711
+
712
+ function AnimatedBox() {
713
+ let ref: HTMLElement;
714
+
715
+ onMount(() => {
716
+ animate(ref, { opacity: [0, 1], y: [-20, 0] }, { duration: 0.3 });
717
+ });
718
+
719
+ onBeforeExit(async (token) => {
720
+ const animation = animate(ref, { opacity: 0, y: -20 }, { duration: 0.3 });
721
+
722
+ token.onCancel(() => {
723
+ animation.stop();
724
+ animate(ref, { opacity: 1, y: 0 }, { duration: 0.3 });
725
+ });
726
+
727
+ await animation.finished;
728
+ });
729
+
730
+ return <div ref={(el) => (ref = el)}>Animated content</div>;
731
+ }
732
+
733
+ function App() {
734
+ return (
735
+ <div>
736
+ <button onClick={() => (show.value = !show.value)}>Toggle</button>
737
+ {() => show.value && <AnimatedBox />}
738
+ </div>
739
+ );
740
+ }
741
+ ```
742
+
743
+ ---
744
+
745
+ ## Server-Side Rendering
746
+
747
+ Flash provides three rendering strategies for different use cases:
748
+
749
+ ### renderToString() - Synchronous
750
+
751
+ Fast synchronous rendering for static content:
752
+
753
+ ```typescript
754
+ import { renderToString } from '@mateosuarezdev/flash/server'
755
+
756
+ const html = renderToString(<App />)
757
+ // Returns: Complete HTML string (no async support)
758
+ ```
759
+
760
+ ### renderToStringAsync() - Complete HTML
761
+
762
+ Waits for all async components, perfect for SEO and pre-rendering:
763
+
764
+ ```typescript
765
+ import { renderToStringAsync } from '@mateosuarezdev/flash/server'
766
+
767
+ const html = await renderToStringAsync(<App />)
768
+ // Returns: Complete HTML with all async resolved
769
+ ```
770
+
771
+ ### renderToStream() - Progressive Enhancement
772
+
773
+ Stream HTML for better perceived performance:
774
+
775
+ ```typescript
776
+ import { renderToStream } from '@mateosuarezdev/flash/server'
777
+
778
+ const stream = renderToStream(<App />)
779
+
780
+ for await (const chunk of stream) {
781
+ response.write(chunk)
782
+ }
783
+ // Streams: Initial HTML + progressive updates
784
+ ```
785
+
786
+ ### Pre-rendering & Caching
787
+
788
+ ```typescript
789
+ import { prerenderer } from '@mateosuarezdev/flash/server/prerender'
790
+
791
+ // Save pre-rendered HTML
792
+ await prerenderer.save('/', html)
793
+
794
+ // Load from cache
795
+ const cached = await prerenderer.load('/')
796
+ if (cached) return new Response(cached)
797
+ ```
798
+
799
+ **Learn more:** Check out the [Server Architecture Guide](./docs/server-architecture.md) for detailed information about:
800
+ - Rendering strategies comparison
801
+ - Async component resolution
802
+ - Streaming architecture
803
+ - Caching and pre-rendering
804
+ - Security best practices
805
+
806
+ ---
807
+
808
+ ## API Reference
809
+
810
+ ### Core
811
+
812
+ - `render(element, container)` - Mount your app to the DOM
813
+ - `Fragment` - Render multiple children without a wrapper
814
+
815
+ ### Lifecycle
816
+
817
+ - `onMount(callback)` - Run after component mounts
818
+ - `onUnmount(callback)` - Run when component unmounts
819
+ - `onBeforeExit(callback)` - Run before unmounting (pauses unmount tree for animations, data saving, etc.)
820
+
821
+ ### Context
822
+
823
+ - `createContext(defaultValue)` - Create a context
824
+ - `useContext(context)` - Consume context value
825
+
826
+ ### Animations & Performance
827
+
828
+ - `startViewTransition(callback)` - Trigger View Transition API
829
+ - `frame.read(callback)` - Schedule DOM reads (measurements)
830
+ - `frame.update(callback)` - Schedule calculations
831
+ - `frame.render(callback)` - Schedule DOM writes (mutations)
832
+ - `frame.chain({ read, update, render })` - Chain operations with data flow
833
+ - `flip(element, applyChanges, options)` - FLIP animation for single element
834
+ - `flipGroup(elements, applyChanges, options)` - FLIP animation for groups
835
+ - `flipMove(element, newParent, options)` - Animate element to new container
836
+ - `autoAnimate(element, options)` - Enable auto layout animations (WIP)
837
+
838
+ ### Special Props
839
+
840
+ - `ref={(el) => ...}` - Get reference to DOM element
841
+ - `key={value}` - Unique identifier for list items
842
+ - `viewTransitionName={name}` - Named view transition target
843
+ - `autoanimate={true}` - Enable auto-layout animations (WIP)
844
+
845
+ ---
846
+
847
+ ## Performance Tips
848
+
849
+ 1. **Use signals at module level** for shared state
850
+ 2. **Wrap dynamic expressions in functions** `{() => signal.value}` not `{signal.value}`
851
+ 3. **Always use keys** for list items
852
+ 4. **Minimize reactive boundaries** - only wrap what needs to update
853
+ 5. **Use computed signals** for derived state
854
+ 6. **Use `frame` for DOM operations** - Prevent layout thrashing by batching reads/writes
855
+ 7. **Use FLIP for layout animations** - Animate transforms instead of layout properties
856
+ 8. **Leverage DOM resurrection** - Flash automatically reuses DOM for rapid toggles
857
+
858
+ ---
859
+
860
+ ## Comparison to Other Frameworks
861
+
862
+ | Feature | Flash | React | SolidJS | Vue |
863
+ | ------------------------ | ------- | ----- | ------- | ------- |
864
+ | Reactivity | Signals | VDOM | Signals | Proxies |
865
+ | Bundle Size | ~10KB | ~40KB | ~7KB | ~30KB |
866
+ | Fine-grained Updates | ✅ | ❌ | ✅ | ✅ |
867
+ | Keyed Lists | ✅ | ✅ | ✅ | ✅ |
868
+ | Built-in FLIP Utils | ✅ | ❌ | ❌ | ❌ |
869
+ | Frame Scheduler | ✅ | ❌ | ❌ | ❌ |
870
+ | DOM Resurrection | ✅ | ❌ | ❌ | ❌ |
871
+ | Animation Flexibility | Any lib | Any lib | Any lib | Any lib |
872
+ | SSR Support | ✅ | ✅ | ✅ | ✅ |
873
+ | SSR Streaming | ✅ | ✅ | ✅ | ✅ |
874
+ | No Compiler | ✅ | ✅ | ❌ | ❌ |
875
+
876
+ ---
877
+
878
+ ## FAQ
879
+
880
+ **Q: Do I need a compiler?**
881
+ A: No! Flash works with standard JSX transformation. Just configure `jsxImportSource`.
882
+
883
+ **Q: Can I use TypeScript?**
884
+ A: Yes! Flash is written in TypeScript with full type support.
885
+
886
+ **Q: How does reactivity work?**
887
+ A: Flash uses Preact Signals. When you wrap an expression in a function `{() => signal.value}`, Flash creates a reactive boundary that auto-updates when the signal changes.
888
+
889
+ **Q: What about SSR?**
890
+ A: Yes! Flash has full SSR support with three rendering strategies (sync, async, streaming). See the [Server Architecture Guide](./docs/server-architecture.md).
891
+
892
+ **Q: Why functions for reactive values?**
893
+ A: Functions create clear boundaries for reactivity and work without a compiler. It's explicit and simple.
894
+
895
+ **Q: What is DOM resurrection?**
896
+ A: When a component is exiting (playing exit animation) but gets toggled back on, Flash cancels the animation and reuses the existing DOM instead of creating a new one. This provides smooth animation reversals without any setup.
897
+
898
+ ---
899
+
900
+ ## Examples
901
+
902
+ Check out the [examples](./src/main.tsx) directory for more demos:
903
+
904
+ - Basic counter and computed values
905
+ - Enter/exit animations with cancellation
906
+ - View Transitions API integration
907
+ - Keyed list rendering with add/remove
908
+ - Context API usage
909
+ - Reactive props and class names
910
+
911
+ ---
912
+
913
+ ## Architecture
914
+
915
+ Want to understand how Flash works under the hood?
916
+
917
+ **[Client Architecture Guide](./docs/architecture.md)** - Deep dive into:
918
+ - JSX transformation flow
919
+ - VNode types and rendering pipeline
920
+ - Reactivity system internals
921
+ - Content replacement strategies (resurrection, text optimization)
922
+ - Keyed list reconciliation algorithm
923
+
924
+ **[Server Architecture Guide](./docs/server-architecture.md)** - Deep dive into:
925
+ - Three rendering strategies (sync, async, streaming)
926
+ - Async component resolution (parallel execution)
927
+ - Streaming architecture and progressive enhancement
928
+ - Pre-rendering and caching system
929
+ - Security best practices (XSS protection)
930
+
931
+ ---
932
+
933
+ ## Contributing
934
+
935
+ Flash is in active development. Contributions are welcome!
936
+
937
+ ---
938
+
939
+ ## License
940
+
941
+ MIT © Mateo Suarez
942
+
943
+ ---
944
+
945
+ **⚡ Built with Flash - Fine-grained reactivity meets familiar JSX**