@fluixi/jsx 1.0.0-alpha.53
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/LICENSE +21 -0
- package/README.md +857 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +73 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +158 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/jsx-dev-runtime.cjs +1 -0
- package/dist/lib/jsx-dev-runtime.d.ts +11 -0
- package/dist/lib/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/lib/jsx-dev-runtime.js +60 -0
- package/dist/lib/jsx-dev-runtime.mjs +1 -0
- package/dist/lib/jsx-runtime.cjs +1 -0
- package/dist/lib/jsx-runtime.d.ts +58 -0
- package/dist/lib/jsx-runtime.d.ts.map +1 -0
- package/dist/lib/jsx-runtime.js +181 -0
- package/dist/lib/jsx-runtime.mjs +1 -0
- package/dist/lib/jsx.d.ts +1460 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/fluixi/assets/main/logos/128x128.png" alt="Fluixi" width="120" height="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @fluixi/jsx
|
|
6
|
+
|
|
7
|
+
**A fine-grained JSX runtime — native JSX and lit-html templates, no virtual DOM.**
|
|
8
|
+
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
A comprehensive JSX runtime with fine-grained reactivity, inspired by SolidJS. This package provides a modern JSX implementation that supports both native JSX and lit-html templates, with seamless integration with signal and store systems for optimal performance.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- 🎯 **Fine-Grained Reactivity**: Surgical DOM updates without virtual DOM overhead
|
|
21
|
+
- 🔄 **Dual-Mode JSX**: Use native JSX or lit-html templates interchangeably
|
|
22
|
+
- 🚀 **Optimal Performance**: Only updates what actually changed
|
|
23
|
+
- 🧩 **Control Flow**: Built-in Show, For, Switch, Portal components
|
|
24
|
+
- 📦 **Signal & Store Support**: Works with any reactive system
|
|
25
|
+
- 🎨 **Lit-HTML Integration**: Full compatibility with lit-html directives
|
|
26
|
+
- 🔧 **Compiler Support**: Includes transformation utilities for build-time optimization
|
|
27
|
+
- 📘 **Full TypeScript**: Complete type safety with JSX intrinsic elements
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @fluixi/jsx @fluixi/dom
|
|
33
|
+
# or
|
|
34
|
+
pnpm add @fluixi/jsx @fluixi/dom
|
|
35
|
+
# or
|
|
36
|
+
yarn add @fluixi/jsx @fluixi/dom
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Configure TypeScript
|
|
42
|
+
|
|
43
|
+
Add to your `tsconfig.json`:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"compilerOptions": {
|
|
48
|
+
"jsx": "react-jsx",
|
|
49
|
+
"jsxImportSource": "@fluixi/jsx",
|
|
50
|
+
"types": ["@fluixi/jsx"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { render } from '@fluixi/jsx';
|
|
59
|
+
import { createSignal } from '@fluixi/reactive/signal';
|
|
60
|
+
|
|
61
|
+
function Counter() {
|
|
62
|
+
const [count, setCount] = createSignal(0);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
<p>Count: {count()}</p>
|
|
67
|
+
<button onClick={() => setCount(count() + 1)}>
|
|
68
|
+
Increment
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
render(<Counter />, document.getElementById('app')!);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With Reactive Values
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { createSignal } from '@fluixi/reactive/signal';
|
|
81
|
+
|
|
82
|
+
function App() {
|
|
83
|
+
const [name, setName] = createSignal('World');
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
{/* Reactive text - updates automatically */}
|
|
88
|
+
<h1>Hello, {name()}!</h1>
|
|
89
|
+
|
|
90
|
+
{/* Reactive attribute */}
|
|
91
|
+
<input
|
|
92
|
+
value={name()}
|
|
93
|
+
onInput={(e) => setName(e.target.value)}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## JSX Syntax
|
|
101
|
+
|
|
102
|
+
### Elements
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Native elements
|
|
106
|
+
<div className="container">Content</div>
|
|
107
|
+
|
|
108
|
+
// Self-closing
|
|
109
|
+
<img src="image.jpg" alt="Image" />
|
|
110
|
+
|
|
111
|
+
// Components
|
|
112
|
+
<MyComponent prop="value" />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Reactive Attributes
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { createSignal } from '@fluixi/reactive/signal';
|
|
119
|
+
|
|
120
|
+
const [color, setColor] = createSignal('red');
|
|
121
|
+
const [size, setSize] = createSignal(16);
|
|
122
|
+
|
|
123
|
+
<div
|
|
124
|
+
style={{
|
|
125
|
+
color: color(), // Reactive
|
|
126
|
+
fontSize: `${size()}px` // Reactive
|
|
127
|
+
}}
|
|
128
|
+
className={`text-${color()}`} // Reactive
|
|
129
|
+
data-color={color()} // Reactive
|
|
130
|
+
>
|
|
131
|
+
Content
|
|
132
|
+
</div>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Event Handlers
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// Click handler
|
|
139
|
+
<button onClick={() => console.log('clicked')}>
|
|
140
|
+
Click me
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
// Input handler
|
|
144
|
+
<input onInput={(e) => console.log(e.target.value)} />
|
|
145
|
+
|
|
146
|
+
// Custom events
|
|
147
|
+
<div onCustomEvent={(e) => handleCustom(e)} />
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Children
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// Text children
|
|
154
|
+
<div>Hello World</div>
|
|
155
|
+
|
|
156
|
+
// Element children
|
|
157
|
+
<div>
|
|
158
|
+
<span>Child 1</span>
|
|
159
|
+
<span>Child 2</span>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
// Reactive children
|
|
163
|
+
<div>{count()}</div>
|
|
164
|
+
|
|
165
|
+
// Array children
|
|
166
|
+
<div>
|
|
167
|
+
{items().map(item => <div key={item.id}>{item.name}</div>)}
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
// Mixed children
|
|
171
|
+
<div>
|
|
172
|
+
Static text
|
|
173
|
+
{dynamic()}
|
|
174
|
+
<span>Element</span>
|
|
175
|
+
</div>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Fragments
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// Fragment shorthand
|
|
182
|
+
<>
|
|
183
|
+
<div>First</div>
|
|
184
|
+
<div>Second</div>
|
|
185
|
+
</>
|
|
186
|
+
|
|
187
|
+
// Named fragment
|
|
188
|
+
<Fragment>
|
|
189
|
+
<div>First</div>
|
|
190
|
+
<div>Second</div>
|
|
191
|
+
</Fragment>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Control Flow Components
|
|
195
|
+
|
|
196
|
+
### Show
|
|
197
|
+
|
|
198
|
+
Conditional rendering with optional fallback.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { Show } from '@fluixi/jsx';
|
|
202
|
+
|
|
203
|
+
function Profile() {
|
|
204
|
+
const [user, setUser] = createSignal(null);
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Show
|
|
208
|
+
when={user()}
|
|
209
|
+
fallback={<div>Loading...</div>}
|
|
210
|
+
>
|
|
211
|
+
{(u) => (
|
|
212
|
+
<div>
|
|
213
|
+
<h1>{u.name}</h1>
|
|
214
|
+
<p>{u.email}</p>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</Show>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### For
|
|
223
|
+
|
|
224
|
+
Keyed list rendering with optimal updates.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { For } from '@fluixi/jsx';
|
|
228
|
+
|
|
229
|
+
function TodoList() {
|
|
230
|
+
const [todos, setTodos] = createSignal([
|
|
231
|
+
{ id: 1, text: 'Learn JSX', done: false },
|
|
232
|
+
{ id: 2, text: 'Build app', done: false },
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<ul>
|
|
237
|
+
<For each={todos()}>
|
|
238
|
+
{(todo, index) => (
|
|
239
|
+
<li>
|
|
240
|
+
<input
|
|
241
|
+
type="checkbox"
|
|
242
|
+
checked={todo.done}
|
|
243
|
+
onChange={(e) => updateTodo(index(), e.target.checked)}
|
|
244
|
+
/>
|
|
245
|
+
{todo.text}
|
|
246
|
+
</li>
|
|
247
|
+
)}
|
|
248
|
+
</For>
|
|
249
|
+
</ul>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Index
|
|
255
|
+
|
|
256
|
+
Index-based list rendering (use when items change but positions don't).
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
import { Index } from '@fluixi/jsx';
|
|
260
|
+
|
|
261
|
+
function NumberList() {
|
|
262
|
+
const [numbers] = createSignal([1, 2, 3, 4, 5]);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<ul>
|
|
266
|
+
<Index each={numbers()}>
|
|
267
|
+
{(num, index) => (
|
|
268
|
+
<li>#{index}: {num()}</li>
|
|
269
|
+
)}
|
|
270
|
+
</Index>
|
|
271
|
+
</ul>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Switch/Match
|
|
277
|
+
|
|
278
|
+
Multi-way conditional rendering.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { Switch, Match } from '@fluixi/jsx';
|
|
282
|
+
|
|
283
|
+
function StatusView() {
|
|
284
|
+
const [status, setStatus] = createSignal('loading');
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<Switch fallback={<div>Unknown status</div>}>
|
|
288
|
+
<Match when={status() === 'loading'}>
|
|
289
|
+
<div>Loading...</div>
|
|
290
|
+
</Match>
|
|
291
|
+
<Match when={status() === 'success'}>
|
|
292
|
+
<div>Success!</div>
|
|
293
|
+
</Match>
|
|
294
|
+
<Match when={status() === 'error'}>
|
|
295
|
+
<div>Error occurred</div>
|
|
296
|
+
</Match>
|
|
297
|
+
</Switch>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Portal
|
|
303
|
+
|
|
304
|
+
Render content in a different DOM location.
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import { Portal } from '@fluixi/jsx';
|
|
308
|
+
|
|
309
|
+
function Modal({ isOpen, children }) {
|
|
310
|
+
return (
|
|
311
|
+
<Show when={isOpen()}>
|
|
312
|
+
<Portal mount={document.body}>
|
|
313
|
+
<div class="modal-backdrop">
|
|
314
|
+
<div class="modal">
|
|
315
|
+
{children}
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</Portal>
|
|
319
|
+
</Show>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Dynamic
|
|
325
|
+
|
|
326
|
+
Dynamically render components based on runtime conditions.
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
import { Dynamic } from '@fluixi/jsx';
|
|
330
|
+
|
|
331
|
+
function DynamicComponent() {
|
|
332
|
+
const [component, setComponent] = createSignal('div');
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<Dynamic
|
|
336
|
+
component={component()}
|
|
337
|
+
className="dynamic"
|
|
338
|
+
>
|
|
339
|
+
Content
|
|
340
|
+
</Dynamic>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### ErrorBoundary
|
|
346
|
+
|
|
347
|
+
Catch and handle errors in component trees.
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
import { ErrorBoundary } from '@fluixi/jsx';
|
|
351
|
+
|
|
352
|
+
function App() {
|
|
353
|
+
return (
|
|
354
|
+
<ErrorBoundary
|
|
355
|
+
fallback={(err, reset) => (
|
|
356
|
+
<div>
|
|
357
|
+
<h1>Error: {err.message}</h1>
|
|
358
|
+
<button onClick={reset}>Retry</button>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
>
|
|
362
|
+
<RiskyComponent />
|
|
363
|
+
</ErrorBoundary>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Lit-HTML Integration
|
|
369
|
+
|
|
370
|
+
Use lit-html templates alongside JSX.
|
|
371
|
+
|
|
372
|
+
### With signal directive
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
import { html } from 'lit';
|
|
376
|
+
import { signal } from '@fluixi/jsx';
|
|
377
|
+
|
|
378
|
+
function Component() {
|
|
379
|
+
const [count, setCount] = createSignal(0);
|
|
380
|
+
|
|
381
|
+
return html`
|
|
382
|
+
<div>
|
|
383
|
+
<p>Count: ${signal(count)}</p>
|
|
384
|
+
<button @click=${() => setCount(count() + 1)}>
|
|
385
|
+
Increment
|
|
386
|
+
</button>
|
|
387
|
+
</div>
|
|
388
|
+
`;
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Helper utilities
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import { template, templateSVG, $, $if } from '@fluixi/jsx';
|
|
396
|
+
|
|
397
|
+
function Component() {
|
|
398
|
+
const [show, setShow] = createSignal(true);
|
|
399
|
+
const [name, setName] = createSignal('World');
|
|
400
|
+
|
|
401
|
+
// Auto-wrapped template
|
|
402
|
+
return template`
|
|
403
|
+
<div>
|
|
404
|
+
${$if(show, `Hello, ${$(name)}!`, 'Hidden')}
|
|
405
|
+
</div>
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Component Patterns
|
|
411
|
+
|
|
412
|
+
### Function Components
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
interface Props {
|
|
416
|
+
name: string;
|
|
417
|
+
age: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function Greeting(props: Props) {
|
|
421
|
+
return <div>Hello, {props.name} ({props.age})</div>;
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### With Children
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
interface CardProps {
|
|
429
|
+
title: string;
|
|
430
|
+
children: JSX.Element;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function Card(props: CardProps) {
|
|
434
|
+
return (
|
|
435
|
+
<div class="card">
|
|
436
|
+
<h2>{props.title}</h2>
|
|
437
|
+
<div class="card-body">
|
|
438
|
+
{props.children}
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Usage
|
|
445
|
+
<Card title="My Card">
|
|
446
|
+
<p>Card content here</p>
|
|
447
|
+
</Card>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### With Signals
|
|
451
|
+
|
|
452
|
+
```tsx
|
|
453
|
+
function Counter(props: { initial?: number }) {
|
|
454
|
+
const [count, setCount] = createSignal(props.initial ?? 0);
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<div>
|
|
458
|
+
<p>Count: {count()}</p>
|
|
459
|
+
<button onClick={() => setCount(c => c + 1)}>+</button>
|
|
460
|
+
<button onClick={() => setCount(c => c - 1)}>-</button>
|
|
461
|
+
</div>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Memoized Components
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
import { memo } from '@fluixi/jsx';
|
|
470
|
+
|
|
471
|
+
const ExpensiveComponent = memo((props: { data: any }) => {
|
|
472
|
+
// Expensive rendering logic
|
|
473
|
+
return <div>{/* ... */}</div>;
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Component Helpers
|
|
478
|
+
|
|
479
|
+
```tsx
|
|
480
|
+
import { jsxComponent, defineComponent } from '@fluixi/jsx';
|
|
481
|
+
|
|
482
|
+
// Simple component wrapper
|
|
483
|
+
const MyComponent = jsxComponent((props: Props) => {
|
|
484
|
+
return <div>{/* ... */}</div>;
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// With name for debugging
|
|
488
|
+
const NamedComponent = defineComponent('MyComponent', (props: Props) => {
|
|
489
|
+
return <div>{/* ... */}</div>;
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Refs
|
|
494
|
+
|
|
495
|
+
### Element Refs
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
import { createRef } from '@fluixi/jsx';
|
|
499
|
+
|
|
500
|
+
function Component() {
|
|
501
|
+
const inputRef = createRef<HTMLInputElement>();
|
|
502
|
+
|
|
503
|
+
const focusInput = () => {
|
|
504
|
+
inputRef.current?.focus();
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
return (
|
|
508
|
+
<div>
|
|
509
|
+
<input ref={inputRef} />
|
|
510
|
+
<button onClick={focusInput}>Focus</button>
|
|
511
|
+
</div>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Callback Refs
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
function Component() {
|
|
520
|
+
let divElement: HTMLDivElement | null = null;
|
|
521
|
+
|
|
522
|
+
const handleRef = (el: HTMLDivElement | null) => {
|
|
523
|
+
divElement = el;
|
|
524
|
+
if (el) {
|
|
525
|
+
console.log('Element mounted:', el);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return <div ref={handleRef}>Content</div>;
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Forward Ref
|
|
534
|
+
|
|
535
|
+
```tsx
|
|
536
|
+
import { forwardRef } from '@fluixi/jsx';
|
|
537
|
+
|
|
538
|
+
const FancyInput = forwardRef<HTMLInputElement, { label: string }>(
|
|
539
|
+
(props, ref) => {
|
|
540
|
+
return (
|
|
541
|
+
<div>
|
|
542
|
+
<label>{props.label}</label>
|
|
543
|
+
<input ref={ref} />
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Usage
|
|
550
|
+
const inputRef = createRef<HTMLInputElement>();
|
|
551
|
+
<FancyInput label="Name" ref={inputRef} />
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Styling
|
|
555
|
+
|
|
556
|
+
### Inline Styles
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
// Object style
|
|
560
|
+
<div style={{ color: 'red', fontSize: '16px' }}>
|
|
561
|
+
Styled text
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
// String style
|
|
565
|
+
<div style="color: red; font-size: 16px;">
|
|
566
|
+
Styled text
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
// Reactive styles
|
|
570
|
+
<div style={{ color: color() }}>
|
|
571
|
+
Dynamic color
|
|
572
|
+
</div>
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### CSS Classes
|
|
576
|
+
|
|
577
|
+
```tsx
|
|
578
|
+
// String
|
|
579
|
+
<div className="btn btn-primary">Button</div>
|
|
580
|
+
|
|
581
|
+
// Array
|
|
582
|
+
<div className={['btn', isActive() && 'active'].filter(Boolean)}>
|
|
583
|
+
Button
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
// Object (with classMap from lit)
|
|
587
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
588
|
+
|
|
589
|
+
<div className={classMap({ btn: true, active: isActive() })}>
|
|
590
|
+
Button
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
// Reactive
|
|
594
|
+
<div className={`btn btn-${type()}`}>Button</div>
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Advanced Usage
|
|
598
|
+
|
|
599
|
+
### Batch Updates
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
import { jsxBatch } from '@fluixi/jsx';
|
|
603
|
+
|
|
604
|
+
function Component() {
|
|
605
|
+
const [a, setA] = createSignal(0);
|
|
606
|
+
const [b, setB] = createSignal(0);
|
|
607
|
+
const [c, setC] = createSignal(0);
|
|
608
|
+
|
|
609
|
+
const updateAll = () => {
|
|
610
|
+
jsxBatch(() => {
|
|
611
|
+
setA(1);
|
|
612
|
+
setB(2);
|
|
613
|
+
setC(3);
|
|
614
|
+
});
|
|
615
|
+
// Only one render cycle
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
return <button onClick={updateAll}>Update All</button>;
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Server-Side Rendering
|
|
623
|
+
|
|
624
|
+
```tsx
|
|
625
|
+
import { renderToString } from '@fluixi/jsx';
|
|
626
|
+
|
|
627
|
+
async function handler() {
|
|
628
|
+
const html = await renderToString(<App />);
|
|
629
|
+
return new Response(html, {
|
|
630
|
+
headers: { 'Content-Type': 'text/html' },
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Hydration
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
import { hydrate } from '@fluixi/jsx';
|
|
639
|
+
|
|
640
|
+
// Hydrate server-rendered content
|
|
641
|
+
hydrate(<App />, document.getElementById('app')!);
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
## Compiler Options
|
|
645
|
+
|
|
646
|
+
The package includes a compiler module for build-time optimizations.
|
|
647
|
+
|
|
648
|
+
### Transform JSX
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
import { transform } from '@fluixi/jsx/compiler';
|
|
652
|
+
|
|
653
|
+
const result = transform(code, {
|
|
654
|
+
useLitHTML: true,
|
|
655
|
+
optimize: true,
|
|
656
|
+
delegateEvents: true,
|
|
657
|
+
delegatedEvents: ['click', 'input', 'change'],
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
console.log(result.code);
|
|
661
|
+
console.log(result.metadata);
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Babel Plugin
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
// babel.config.js
|
|
668
|
+
module.exports = {
|
|
669
|
+
plugins: [
|
|
670
|
+
['@fluixi/jsx/compiler/babel', {
|
|
671
|
+
useLitHTML: true,
|
|
672
|
+
optimize: true,
|
|
673
|
+
}]
|
|
674
|
+
]
|
|
675
|
+
};
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### TypeScript Transformer
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
import { createTypeScriptTransformer } from '@fluixi/jsx/compiler';
|
|
682
|
+
|
|
683
|
+
const transformer = createTypeScriptTransformer({
|
|
684
|
+
optimize: true,
|
|
685
|
+
});
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
## Performance Tips
|
|
689
|
+
|
|
690
|
+
1. **Use `For` for lists**: Keyed reconciliation is faster than mapping
|
|
691
|
+
2. **Memoize expensive components**: Use `memo()` wrapper
|
|
692
|
+
3. **Batch updates**: Use `jsxBatch()` for multiple state changes
|
|
693
|
+
4. **Avoid inline object creation**: Extract to variables
|
|
694
|
+
5. **Use `Index` for stable lists**: When order doesn't change
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
// ❌ Bad - creates new object every render
|
|
698
|
+
<div style={{ color: 'red' }}>Text</div>
|
|
699
|
+
|
|
700
|
+
// ✅ Good - object is stable
|
|
701
|
+
const style = { color: 'red' };
|
|
702
|
+
<div style={style}>Text</div>
|
|
703
|
+
|
|
704
|
+
// ❌ Bad - new function every render
|
|
705
|
+
<button onClick={() => handleClick(id)}>Click</button>
|
|
706
|
+
|
|
707
|
+
// ✅ Good - stable function
|
|
708
|
+
const handleClick = () => handleClick(id);
|
|
709
|
+
<button onClick={handleClick}>Click</button>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
## TypeScript
|
|
713
|
+
|
|
714
|
+
### Component Types
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
import type { JSXElement, JSXComponent } from '@fluixi/jsx';
|
|
718
|
+
|
|
719
|
+
// Function component type
|
|
720
|
+
const Component: JSXComponent<{ name: string }> = (props) => {
|
|
721
|
+
return <div>{props.name}</div>;
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// Element type
|
|
725
|
+
function render(): JSXElement {
|
|
726
|
+
return <div>Content</div>;
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Props Interface
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
import type { JSXProps } from '@fluixi/jsx';
|
|
734
|
+
|
|
735
|
+
interface MyProps extends JSXProps {
|
|
736
|
+
title: string;
|
|
737
|
+
count: number;
|
|
738
|
+
onUpdate?: (value: number) => void;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function MyComponent(props: MyProps) {
|
|
742
|
+
return <div>{props.title}: {props.count}</div>;
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Intrinsic Elements
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// All HTML elements are typed
|
|
750
|
+
<div>Text</div>
|
|
751
|
+
<button onClick={() => {}}>Button</button>
|
|
752
|
+
<input type="text" value="text" />
|
|
753
|
+
|
|
754
|
+
// With proper event types
|
|
755
|
+
<button onClick={(e: MouseEvent) => console.log(e)}>
|
|
756
|
+
Click
|
|
757
|
+
</button>
|
|
758
|
+
|
|
759
|
+
<input onInput={(e: InputEvent) => {
|
|
760
|
+
const target = e.target as HTMLInputElement;
|
|
761
|
+
console.log(target.value);
|
|
762
|
+
}} />
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
## Browser Support
|
|
766
|
+
|
|
767
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
768
|
+
- ES2020+ required
|
|
769
|
+
- No IE11 support
|
|
770
|
+
|
|
771
|
+
## Debugging
|
|
772
|
+
|
|
773
|
+
### Development Mode
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
import { createReactiveJSX } from '@fluixi/jsx';
|
|
777
|
+
|
|
778
|
+
const jsx = createReactiveJSX({
|
|
779
|
+
signalSystem: mySignalSystem,
|
|
780
|
+
development: true, // Enables warnings and checks
|
|
781
|
+
});
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Component Names
|
|
785
|
+
|
|
786
|
+
Use `defineComponent` for better debugging:
|
|
787
|
+
|
|
788
|
+
```tsx
|
|
789
|
+
import { defineComponent } from '@fluixi/jsx';
|
|
790
|
+
|
|
791
|
+
const MyComponent = defineComponent('MyComponent', (props) => {
|
|
792
|
+
return <div>{props.children}</div>;
|
|
793
|
+
});
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
## Migration Guide
|
|
797
|
+
|
|
798
|
+
### From React
|
|
799
|
+
|
|
800
|
+
```tsx
|
|
801
|
+
// React
|
|
802
|
+
import React, { useState } from 'react';
|
|
803
|
+
|
|
804
|
+
function Counter() {
|
|
805
|
+
const [count, setCount] = useState(0);
|
|
806
|
+
return <div onClick={() => setCount(count + 1)}>{count}</div>;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// This package
|
|
810
|
+
import { createSignal } from '@fluixi/reactive/signal';
|
|
811
|
+
|
|
812
|
+
function Counter() {
|
|
813
|
+
const [count, setCount] = createSignal(0);
|
|
814
|
+
return <div onClick={() => setCount(count() + 1)}>{count()}</div>;
|
|
815
|
+
}
|
|
816
|
+
// Note: Signals are functions!
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### From Solid
|
|
820
|
+
|
|
821
|
+
```tsx
|
|
822
|
+
// Very similar! Main difference is import paths
|
|
823
|
+
import { createSignal, Show, For } from 'solid-js';
|
|
824
|
+
// becomes
|
|
825
|
+
import { createSignal } from '@fluixi/reactive/signal';
|
|
826
|
+
import { Show, For } from '@fluixi/jsx';
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
## Examples
|
|
830
|
+
|
|
831
|
+
Check the examples directory for complete applications:
|
|
832
|
+
|
|
833
|
+
- Counter app
|
|
834
|
+
- Todo list
|
|
835
|
+
- Data fetching
|
|
836
|
+
- Forms
|
|
837
|
+
- Routing
|
|
838
|
+
|
|
839
|
+
## Contributing
|
|
840
|
+
|
|
841
|
+
Contributions welcome! Please see the main repository for guidelines.
|
|
842
|
+
|
|
843
|
+
## License
|
|
844
|
+
|
|
845
|
+
MIT
|
|
846
|
+
|
|
847
|
+
## Related Packages
|
|
848
|
+
|
|
849
|
+
- `@fluixi/dom` - Core reactive DOM runtime
|
|
850
|
+
- `@fluixi/reactive` - Signal and store implementations
|
|
851
|
+
- `lit` - Template literals for HTML
|
|
852
|
+
|
|
853
|
+
## Resources
|
|
854
|
+
|
|
855
|
+
- [SolidJS Documentation](https://www.solidjs.com/)
|
|
856
|
+
- [Lit Documentation](https://lit.dev/)
|
|
857
|
+
- [Fine-Grained Reactivity](https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf)
|