@fluixi/core 1.0.0-alpha.53 β 1.0.0-alpha.55
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 +137 -479
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -8,33 +8,31 @@
|
|
|
8
8
|
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|

|
|
11
|
-

|
|
11
|
+
[](https://www.npmjs.com/package/@fluixi/core)
|
|
13
12
|
|
|
14
13
|
---
|
|
15
14
|
|
|
16
|
-
A modern, reactive framework
|
|
15
|
+
A modern, reactive UI framework with JSX, fine-grained reactivity, routing and SSR. Components compile to direct DOM operations β no virtual DOM, surgical updates.
|
|
17
16
|
|
|
18
17
|
## Features
|
|
19
18
|
|
|
20
|
-
- π **Fine-grained Reactivity** -
|
|
21
|
-
-
|
|
22
|
-
- π **Control Flow Components** - Show, For, Switch, Portal, and Dynamic
|
|
19
|
+
- π **Fine-grained Reactivity** - Signals and memos drive surgical DOM updates, no virtual DOM
|
|
20
|
+
- π§© **JSX** - Compiled to direct DOM instructions via `@fluixi/vite-plugin`
|
|
21
|
+
- π **Control Flow Components** - Show, For, Switch, Portal, and Dynamic
|
|
23
22
|
- π£οΈ **Built-in Router** - Client-side routing with nested routes and lazy loading
|
|
24
|
-
- π‘ **Resource Management** - Async data fetching with automatic loading states
|
|
23
|
+
- π‘ **Resource Management** - Async data fetching with automatic loading states + Suspense
|
|
25
24
|
- π **Context API** - Share state across component trees without prop drilling
|
|
25
|
+
- π₯οΈ **SSR + Hydration** - Server rendering with flash-free hydration (via `@fluixi/start`)
|
|
26
26
|
- ποΈ **TypeScript First** - Full type safety and excellent IDE support
|
|
27
|
-
- π οΈ **CLI Tools** - Project and component generators for rapid development
|
|
28
|
-
- π¦ **Zero Runtime Overhead** - Compile-time optimizations for minimal bundle size
|
|
29
27
|
|
|
30
28
|
## Installation
|
|
31
29
|
|
|
32
30
|
```bash
|
|
33
|
-
npm install @fluixi/core @fluixi/reactive
|
|
31
|
+
npm install @fluixi/core @fluixi/reactive
|
|
34
32
|
# or
|
|
35
|
-
|
|
33
|
+
pnpm add @fluixi/core @fluixi/reactive
|
|
36
34
|
# or
|
|
37
|
-
|
|
35
|
+
yarn add @fluixi/core @fluixi/reactive
|
|
38
36
|
```
|
|
39
37
|
|
|
40
38
|
## Quick Start
|
|
@@ -42,570 +40,230 @@ pnpm add @fluixi/core @fluixi/reactive lit
|
|
|
42
40
|
### Creating a New Project
|
|
43
41
|
|
|
44
42
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Or install globally
|
|
49
|
-
npm install -g @fluixi/core
|
|
50
|
-
fluixi new my-app
|
|
43
|
+
npm create fluixi my-app
|
|
44
|
+
# or: pnpm create fluixi my-app
|
|
51
45
|
```
|
|
52
46
|
|
|
53
47
|
### Basic Example
|
|
54
48
|
|
|
55
|
-
```
|
|
56
|
-
import { createSignal, render
|
|
49
|
+
```tsx
|
|
50
|
+
import { createSignal, render } from '@fluixi/core';
|
|
57
51
|
|
|
58
52
|
const Counter = () => {
|
|
59
53
|
const [count, setCount] = createSignal(0);
|
|
60
54
|
|
|
61
|
-
return
|
|
55
|
+
return (
|
|
62
56
|
<div>
|
|
63
|
-
<h1>Count:
|
|
64
|
-
<button
|
|
65
|
-
<button
|
|
66
|
-
<button
|
|
57
|
+
<h1>Count: {count()}</h1>
|
|
58
|
+
<button onClick={() => setCount(count() + 1)}>Increment</button>
|
|
59
|
+
<button onClick={() => setCount(count() - 1)}>Decrement</button>
|
|
60
|
+
<button onClick={() => setCount(0)}>Reset</button>
|
|
67
61
|
</div>
|
|
68
|
-
|
|
62
|
+
);
|
|
69
63
|
};
|
|
70
64
|
|
|
71
|
-
render(Counter
|
|
65
|
+
render(() => <Counter />, document.getElementById('app')!);
|
|
72
66
|
```
|
|
73
67
|
|
|
68
|
+
> JSX is compiled by `@fluixi/vite-plugin`. Scaffold a ready-to-run app with `npm create fluixi`.
|
|
69
|
+
|
|
74
70
|
## Core Concepts
|
|
75
71
|
|
|
76
72
|
### Signals
|
|
77
73
|
|
|
78
|
-
Signals are
|
|
74
|
+
Signals are reactive state. Reading one inside an effect or JSX subscribes to it; setting it updates only what depends on it.
|
|
79
75
|
|
|
80
|
-
```
|
|
76
|
+
```tsx
|
|
81
77
|
import { createSignal, createEffect } from '@fluixi/core';
|
|
82
78
|
|
|
83
79
|
const [count, setCount] = createSignal(0);
|
|
84
80
|
const [name, setName] = createSignal('Alice');
|
|
85
81
|
|
|
86
|
-
// Effects run automatically when dependencies change
|
|
87
82
|
createEffect(() => {
|
|
88
|
-
console.log(`${name()}
|
|
83
|
+
console.log(`${name()} counted to ${count()}`);
|
|
89
84
|
});
|
|
90
85
|
|
|
91
|
-
setCount(5);
|
|
92
|
-
setName('Bob');
|
|
86
|
+
setCount(5); // "Alice counted to 5"
|
|
87
|
+
setName('Bob'); // "Bob counted to 5"
|
|
93
88
|
```
|
|
94
89
|
|
|
95
90
|
### Components
|
|
96
91
|
|
|
97
|
-
Components are
|
|
92
|
+
Components are plain functions that return JSX. Props are reactive β read them where you use them.
|
|
98
93
|
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
interface GreetingProps extends ComponentProps {
|
|
103
|
-
initialName?: string;
|
|
94
|
+
```tsx
|
|
95
|
+
function Greeting(props: { name: string }) {
|
|
96
|
+
return <h1>Hello, {props.name}!</h1>;
|
|
104
97
|
}
|
|
105
98
|
|
|
106
|
-
|
|
107
|
-
const [name, setName] = createSignal(props.initialName || 'World');
|
|
108
|
-
|
|
109
|
-
return html`
|
|
110
|
-
<div>
|
|
111
|
-
<h1>Hello, ${name()}!</h1>
|
|
112
|
-
<input
|
|
113
|
-
type="text"
|
|
114
|
-
.value=${name()}
|
|
115
|
-
@input=${(e: Event) => setName((e.target as HTMLInputElement).value)}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
`;
|
|
119
|
-
};
|
|
99
|
+
// <Greeting name="World" />
|
|
120
100
|
```
|
|
121
101
|
|
|
122
102
|
### Control Flow
|
|
123
103
|
|
|
124
|
-
|
|
104
|
+
Use control-flow components instead of ternaries and `.map()` so updates stay fine-grained.
|
|
125
105
|
|
|
126
|
-
|
|
106
|
+
```tsx
|
|
107
|
+
import { Show, For, Switch, Match } from '@fluixi/core';
|
|
127
108
|
|
|
128
|
-
|
|
129
|
-
|
|
109
|
+
// Show β conditional rendering (child callback receives the narrowed value)
|
|
110
|
+
<Show when={() => user()} fallback={<p>Please log in</p>}>
|
|
111
|
+
{(u) => <p>Welcome, {u.name}!</p>}
|
|
112
|
+
</Show>
|
|
130
113
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
114
|
+
// For β keyed list rendering (index is an accessor)
|
|
115
|
+
<For each={todos()}>
|
|
116
|
+
{(todo, i) => <li>{i() + 1}. {todo.text}</li>}
|
|
117
|
+
</For>
|
|
134
118
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
children: (u) => html`<p>Welcome, ${u.name}!</p>`
|
|
142
|
-
}),
|
|
143
|
-
children: html`<p>Loading...</p>`
|
|
144
|
-
})}
|
|
145
|
-
`;
|
|
146
|
-
};
|
|
119
|
+
// Switch / Match β multi-branch
|
|
120
|
+
<Switch fallback={<p>Idle</p>}>
|
|
121
|
+
<Match when={() => status() === 'loading'}><p>Loadingβ¦</p></Match>
|
|
122
|
+
<Match when={() => status() === 'success'}><p>Done!</p></Match>
|
|
123
|
+
<Match when={() => status() === 'error'}><p>Error</p></Match>
|
|
124
|
+
</Switch>
|
|
147
125
|
```
|
|
148
126
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import { For, createSignal, html } from '@fluixi/core';
|
|
153
|
-
|
|
154
|
-
const TodoList = () => {
|
|
155
|
-
const [todos, setTodos] = createSignal([
|
|
156
|
-
{ id: 1, text: 'Learn Fluixi', done: false },
|
|
157
|
-
{ id: 2, text: 'Build an app', done: false },
|
|
158
|
-
]);
|
|
159
|
-
|
|
160
|
-
return html`
|
|
161
|
-
<ul>
|
|
162
|
-
${For({
|
|
163
|
-
each: todos,
|
|
164
|
-
children: (todo, index) => html`
|
|
165
|
-
<li>
|
|
166
|
-
<input
|
|
167
|
-
type="checkbox"
|
|
168
|
-
.checked=${todo.done}
|
|
169
|
-
@change=${(e) => {
|
|
170
|
-
const newTodos = [...todos()];
|
|
171
|
-
newTodos[index].done = e.target.checked;
|
|
172
|
-
setTodos(newTodos);
|
|
173
|
-
}}
|
|
174
|
-
/>
|
|
175
|
-
${todo.text}
|
|
176
|
-
</li>
|
|
177
|
-
`
|
|
178
|
-
})}
|
|
179
|
-
</ul>
|
|
180
|
-
`;
|
|
181
|
-
};
|
|
182
|
-
```
|
|
127
|
+
`Portal`, `Dynamic`, `Index`, and `ErrorBoundary` are also exported.
|
|
183
128
|
|
|
184
|
-
|
|
129
|
+
### Resources & Suspense
|
|
185
130
|
|
|
186
|
-
|
|
187
|
-
import { Switch, Match, createSignal, html } from '@fluixi/core';
|
|
131
|
+
`createResource` fetches async data and tracks loading/error. `<Suspense>` shows a fallback while it's pending (works on the client and during SSR).
|
|
188
132
|
|
|
189
|
-
|
|
190
|
-
|
|
133
|
+
```tsx
|
|
134
|
+
import { createSignal, createResource, Suspense } from '@fluixi/core';
|
|
191
135
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Match({ when: () => status() === 'error', children: html`<p>Error!</p>` })
|
|
198
|
-
]
|
|
199
|
-
})}
|
|
200
|
-
`;
|
|
201
|
-
};
|
|
202
|
-
```
|
|
136
|
+
function UserCard() {
|
|
137
|
+
const [id] = createSignal(1);
|
|
138
|
+
const [user] = createResource(id, (id) =>
|
|
139
|
+
fetch(`/api/users/${id}`).then((r) => r.json())
|
|
140
|
+
);
|
|
203
141
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const Modal = () => {
|
|
210
|
-
const [isOpen, setIsOpen] = createSignal(false);
|
|
211
|
-
|
|
212
|
-
return html`
|
|
213
|
-
<button @click=${() => setIsOpen(true)}>Open Modal</button>
|
|
214
|
-
|
|
215
|
-
${Show({
|
|
216
|
-
when: isOpen,
|
|
217
|
-
children: Portal({
|
|
218
|
-
children: html`
|
|
219
|
-
<div class="modal-overlay">
|
|
220
|
-
<div class="modal-content">
|
|
221
|
-
<h2>Modal Title</h2>
|
|
222
|
-
<p>Modal content goes here</p>
|
|
223
|
-
<button @click=${() => setIsOpen(false)}>Close</button>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
`
|
|
227
|
-
})
|
|
228
|
-
})}
|
|
229
|
-
`;
|
|
230
|
-
};
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Resources
|
|
234
|
-
|
|
235
|
-
Resources handle async data fetching with built-in loading and error states.
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
import { createResource, Show, html } from '@fluixi/core';
|
|
239
|
-
|
|
240
|
-
interface User {
|
|
241
|
-
id: number;
|
|
242
|
-
name: string;
|
|
243
|
-
email: string;
|
|
142
|
+
return (
|
|
143
|
+
<Suspense fallback={<p>Loadingβ¦</p>}>
|
|
144
|
+
<p>{user()?.name}</p>
|
|
145
|
+
</Suspense>
|
|
146
|
+
);
|
|
244
147
|
}
|
|
245
|
-
|
|
246
|
-
const fetchUser = async (id: number): Promise<User> => {
|
|
247
|
-
const response = await fetch(`/api/users/${id}`);
|
|
248
|
-
return response.json();
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const UserDetail = () => {
|
|
252
|
-
const [userId, setUserId] = createSignal(1);
|
|
253
|
-
const [user, { refetch }] = createResource(() => userId(), fetchUser);
|
|
254
|
-
|
|
255
|
-
return html`
|
|
256
|
-
${Show({
|
|
257
|
-
when: user.loading,
|
|
258
|
-
fallback: Show({
|
|
259
|
-
when: user.error,
|
|
260
|
-
fallback: Show({
|
|
261
|
-
when: user,
|
|
262
|
-
children: (u) => html`
|
|
263
|
-
<div>
|
|
264
|
-
<h2>${u.name}</h2>
|
|
265
|
-
<p>${u.email}</p>
|
|
266
|
-
<button @click=${refetch}>Refresh</button>
|
|
267
|
-
</div>
|
|
268
|
-
`
|
|
269
|
-
}),
|
|
270
|
-
children: html`<p>Error: ${user.error?.message}</p>`
|
|
271
|
-
}),
|
|
272
|
-
children: html`<p>Loading user...</p>`
|
|
273
|
-
})}
|
|
274
|
-
`;
|
|
275
|
-
};
|
|
276
148
|
```
|
|
277
149
|
|
|
278
|
-
### Context
|
|
150
|
+
### Context
|
|
279
151
|
|
|
280
|
-
Share state
|
|
152
|
+
Share state down the tree without prop drilling.
|
|
281
153
|
|
|
282
|
-
```
|
|
283
|
-
import { createContext, useContext
|
|
154
|
+
```tsx
|
|
155
|
+
import { createContext, useContext } from '@fluixi/core';
|
|
284
156
|
|
|
285
|
-
|
|
286
|
-
primaryColor: string;
|
|
287
|
-
secondaryColor: string;
|
|
288
|
-
}
|
|
157
|
+
const ThemeContext = createContext<'light' | 'dark'>('light');
|
|
289
158
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
159
|
+
function App() {
|
|
160
|
+
return (
|
|
161
|
+
<ThemeContext.Provider value="dark">
|
|
162
|
+
<Toolbar />
|
|
163
|
+
</ThemeContext.Provider>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
294
166
|
|
|
295
|
-
|
|
167
|
+
function Toolbar() {
|
|
296
168
|
const theme = useContext(ThemeContext);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
<button style="background: ${theme.primaryColor}">
|
|
300
|
-
Themed Button
|
|
301
|
-
</button>
|
|
302
|
-
`;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const App = () => {
|
|
306
|
-
const theme = { primaryColor: '#28a745', secondaryColor: '#dc3545' };
|
|
307
|
-
|
|
308
|
-
return Provider({
|
|
309
|
-
context: ThemeContext,
|
|
310
|
-
value: theme,
|
|
311
|
-
children: html`
|
|
312
|
-
<div>
|
|
313
|
-
<h1>My App</h1>
|
|
314
|
-
${ThemedButton()}
|
|
315
|
-
</div>
|
|
316
|
-
`
|
|
317
|
-
});
|
|
318
|
-
};
|
|
169
|
+
return <div class={theme}>β¦</div>;
|
|
170
|
+
}
|
|
319
171
|
```
|
|
320
172
|
|
|
321
173
|
### Router
|
|
322
174
|
|
|
323
|
-
|
|
175
|
+
The router takes a `routes` config; nested routes render through `<Outlet />`, and `lazy()` code-splits.
|
|
324
176
|
|
|
325
|
-
```
|
|
326
|
-
import {
|
|
327
|
-
|
|
328
|
-
const Home = () => html`<h1>Home Page</h1>`;
|
|
329
|
-
|
|
330
|
-
const UserDetail = () => {
|
|
331
|
-
const params = useParams();
|
|
332
|
-
return html`<h1>User ${params.id}</h1>`;
|
|
333
|
-
};
|
|
177
|
+
```tsx
|
|
178
|
+
import { render } from '@fluixi/core';
|
|
179
|
+
import { Router, Outlet, lazy } from '@fluixi/core/router-next';
|
|
334
180
|
|
|
335
181
|
const routes = [
|
|
336
|
-
{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const router = createRouter(routes);
|
|
343
|
-
|
|
344
|
-
return Router({
|
|
345
|
-
router,
|
|
346
|
-
children: html`
|
|
347
|
-
<div>
|
|
348
|
-
<nav>
|
|
349
|
-
${Link({ href: '/', children: 'Home' })}
|
|
350
|
-
${Link({ href: '/about', children: 'About' })}
|
|
351
|
-
</nav>
|
|
352
|
-
<main id="content"></main>
|
|
182
|
+
{
|
|
183
|
+
path: '/',
|
|
184
|
+
component: () => (
|
|
185
|
+
<div class="app">
|
|
186
|
+
<nav>β¦</nav>
|
|
187
|
+
<Outlet />
|
|
353
188
|
</div>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
### Create a New Project
|
|
362
|
-
|
|
363
|
-
```bash
|
|
364
|
-
# Interactive mode
|
|
365
|
-
fluixi new
|
|
366
|
-
|
|
367
|
-
# With options
|
|
368
|
-
fluixi new my-app --template full
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Generate a Component
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
# Interactive mode
|
|
375
|
-
fluixi generate component
|
|
189
|
+
),
|
|
190
|
+
children: [
|
|
191
|
+
{ path: '', component: () => <h1>Home</h1> },
|
|
192
|
+
{ path: 'about', component: lazy(() => import('./About')) },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
];
|
|
376
196
|
|
|
377
|
-
|
|
378
|
-
fluixi g c MyComponent --type with-state --path components/MyComponent/MyComponent.ts
|
|
197
|
+
render(() => <Router routes={routes} />, document.getElementById('app')!);
|
|
379
198
|
```
|
|
380
199
|
|
|
381
|
-
Component types:
|
|
382
|
-
- `basic` - Simple component
|
|
383
|
-
- `with-state` - Component with reactive state
|
|
384
|
-
- `with-resource` - Component with async data fetching
|
|
385
|
-
- `with-router` - Component with router integration
|
|
386
|
-
- `full` - Full-featured component with all features
|
|
387
|
-
|
|
388
200
|
## Lifecycle Hooks
|
|
389
201
|
|
|
390
|
-
```
|
|
391
|
-
import { onMount,
|
|
202
|
+
```tsx
|
|
203
|
+
import { onMount, onCleanup } from '@fluixi/core';
|
|
392
204
|
|
|
393
|
-
|
|
394
|
-
const [
|
|
205
|
+
function Clock() {
|
|
206
|
+
const [now, setNow] = createSignal(Date.now());
|
|
395
207
|
|
|
396
208
|
onMount(() => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// Cleanup function
|
|
401
|
-
return () => {
|
|
402
|
-
console.log('Component unmounting');
|
|
403
|
-
clearInterval(interval);
|
|
404
|
-
};
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
onUnmount(() => {
|
|
408
|
-
console.log('Additional cleanup');
|
|
209
|
+
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
210
|
+
onCleanup(() => clearInterval(id));
|
|
409
211
|
});
|
|
410
212
|
|
|
411
|
-
return
|
|
412
|
-
}
|
|
213
|
+
return <time>{new Date(now()).toLocaleTimeString()}</time>;
|
|
214
|
+
}
|
|
413
215
|
```
|
|
414
216
|
|
|
415
|
-
##
|
|
416
|
-
|
|
417
|
-
### Derived State (Memos)
|
|
217
|
+
## Derived State & Batching
|
|
418
218
|
|
|
419
|
-
```
|
|
420
|
-
import { createSignal, createMemo,
|
|
219
|
+
```tsx
|
|
220
|
+
import { createSignal, createMemo, batch } from '@fluixi/core';
|
|
421
221
|
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
const [b, setB] = createSignal(2);
|
|
222
|
+
const [first, setFirst] = createSignal('Ada');
|
|
223
|
+
const [last, setLast] = createSignal('Lovelace');
|
|
425
224
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
console.log('Calculating sum...');
|
|
429
|
-
return a() + b();
|
|
430
|
-
});
|
|
225
|
+
// Memo β cached, recomputes only when a dependency changes
|
|
226
|
+
const fullName = createMemo(() => `${first()} ${last()}`);
|
|
431
227
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
</div>
|
|
438
|
-
`;
|
|
439
|
-
};
|
|
228
|
+
// Batch β coalesce multiple writes into one update
|
|
229
|
+
batch(() => {
|
|
230
|
+
setFirst('Grace');
|
|
231
|
+
setLast('Hopper');
|
|
232
|
+
});
|
|
440
233
|
```
|
|
441
234
|
|
|
442
|
-
|
|
235
|
+
## SSR
|
|
443
236
|
|
|
444
|
-
|
|
445
|
-
import { createSignal, batch, html } from '@fluixi/core';
|
|
237
|
+
Server rendering, hydration, file-based routing and the dev/build tooling live in [`@fluixi/start`](https://www.npmjs.com/package/@fluixi/start). Scaffold an SSR app with `npm create fluixi` and pick the SSR template.
|
|
446
238
|
|
|
447
|
-
|
|
448
|
-
const [firstName, setFirstName] = createSignal('John');
|
|
449
|
-
const [lastName, setLastName] = createSignal('Doe');
|
|
239
|
+
## TypeScript
|
|
450
240
|
|
|
451
|
-
|
|
452
|
-
batch(() => {
|
|
453
|
-
setFirstName('Jane');
|
|
454
|
-
setLastName('Smith');
|
|
455
|
-
}); // UI updates only once
|
|
456
|
-
};
|
|
241
|
+
Fluixi is written in TypeScript and ships full types. Configure JSX in your `tsconfig.json`:
|
|
457
242
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
`;
|
|
464
|
-
};
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### Error Boundaries
|
|
468
|
-
|
|
469
|
-
```typescript
|
|
470
|
-
import { createSignal, Show, html } from '@fluixi/core';
|
|
471
|
-
|
|
472
|
-
const ErrorBoundary = (props: { children: any }) => {
|
|
473
|
-
const [error, setError] = createSignal<Error | null>(null);
|
|
474
|
-
|
|
475
|
-
try {
|
|
476
|
-
return html`
|
|
477
|
-
${Show({
|
|
478
|
-
when: () => !error(),
|
|
479
|
-
fallback: html`<div>Error: ${error()?.message}</div>`,
|
|
480
|
-
children: props.children
|
|
481
|
-
})}
|
|
482
|
-
`;
|
|
483
|
-
} catch (e) {
|
|
484
|
-
setError(e as Error);
|
|
485
|
-
return html`<div>Error: ${(e as Error).message}</div>`;
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"compilerOptions": {
|
|
246
|
+
"jsx": "preserve",
|
|
247
|
+
"jsxImportSource": "@fluixi/jsx"
|
|
486
248
|
}
|
|
487
|
-
};
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
## API Reference
|
|
491
|
-
|
|
492
|
-
### Core
|
|
493
|
-
|
|
494
|
-
- `createSignal(initialValue, options?)` - Create a reactive signal
|
|
495
|
-
- `createEffect(fn)` - Run side effects when dependencies change
|
|
496
|
-
- `createMemo(fn)` - Create derived reactive state
|
|
497
|
-
- `createComponent(fn)` - Create a component with lifecycle management
|
|
498
|
-
- `render(component, container)` - Render a component into the DOM
|
|
499
|
-
- `onMount(fn)` - Run code when component mounts
|
|
500
|
-
- `onUnmount(fn)` - Run code when component unmounts
|
|
501
|
-
- `batch(fn)` - Batch multiple updates together
|
|
502
|
-
- `untrack(fn)` - Run code without tracking dependencies
|
|
503
|
-
|
|
504
|
-
### Control Flow
|
|
505
|
-
|
|
506
|
-
- `Show({ when, fallback?, children })` - Conditional rendering
|
|
507
|
-
- `For({ each, fallback?, children })` - List rendering
|
|
508
|
-
- `Index({ each, fallback?, children })` - Non-keyed list rendering
|
|
509
|
-
- `Switch({ fallback?, children })` - Multiple condition branches
|
|
510
|
-
- `Match({ when, children })` - Condition branch in Switch
|
|
511
|
-
- `Portal({ mount?, children })` - Render outside component tree
|
|
512
|
-
- `Dynamic({ component, props?, children? })` - Render dynamic components
|
|
513
|
-
|
|
514
|
-
### Context
|
|
515
|
-
|
|
516
|
-
- `createContext(defaultValue, name?)` - Create a context
|
|
517
|
-
- `useContext(context)` - Access context value
|
|
518
|
-
- `Provider({ context, value, children })` - Provide context value
|
|
519
|
-
|
|
520
|
-
### Resources
|
|
521
|
-
|
|
522
|
-
- `createResource(source, fetcher, options?)` - Create async resource
|
|
523
|
-
- `createResources([...])` - Create multiple resources
|
|
524
|
-
- `createLazyResource(source, fetcher, options?)` - Create lazy resource
|
|
525
|
-
|
|
526
|
-
### Router
|
|
527
|
-
|
|
528
|
-
- `createRouter(routes, options?)` - Create router instance
|
|
529
|
-
- `Router({ router, children })` - Router provider component
|
|
530
|
-
- `Link({ href, children })` - Navigation link
|
|
531
|
-
- `useRouter()` - Access router instance
|
|
532
|
-
- `useLocation()` - Access current location
|
|
533
|
-
- `useNavigate()` - Get navigation function
|
|
534
|
-
- `useParams()` - Get route parameters
|
|
535
|
-
- `useQuery()` - Get query parameters
|
|
536
|
-
|
|
537
|
-
## TypeScript Support
|
|
538
|
-
|
|
539
|
-
Fluixi is written in TypeScript and provides full type safety:
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
import { Component, ComponentProps, createSignal } from '@fluixi/core';
|
|
543
|
-
|
|
544
|
-
interface UserProps extends ComponentProps {
|
|
545
|
-
userId: number;
|
|
546
|
-
onUserLoad?: (user: User) => void;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
interface User {
|
|
550
|
-
id: number;
|
|
551
|
-
name: string;
|
|
552
|
-
email: string;
|
|
553
249
|
}
|
|
554
|
-
|
|
555
|
-
const UserComponent: Component<UserProps> = (props) => {
|
|
556
|
-
const [user, setUser] = createSignal<User | null>(null);
|
|
557
|
-
|
|
558
|
-
// TypeScript knows the types of all signals and props
|
|
559
|
-
return html`<div>${user()?.name}</div>`;
|
|
560
|
-
};
|
|
561
250
|
```
|
|
562
251
|
|
|
563
|
-
##
|
|
564
|
-
|
|
565
|
-
### vs React
|
|
566
|
-
|
|
567
|
-
- β
No virtual DOM - direct DOM updates for better performance
|
|
568
|
-
- β
True fine-grained reactivity without re-renders
|
|
569
|
-
- β
Simpler mental model - no hooks rules or dependency arrays
|
|
570
|
-
- β
Smaller bundle size
|
|
571
|
-
- β οΈ Smaller ecosystem (but growing!)
|
|
572
|
-
|
|
573
|
-
### vs SolidJS
|
|
574
|
-
|
|
575
|
-
- β
Similar reactivity model and API
|
|
576
|
-
- β
Uses lit-html for familiar template syntax
|
|
577
|
-
- β
Integrated with @fluixi/reactive library
|
|
578
|
-
- β οΈ Different template syntax (lit-html vs JSX)
|
|
579
|
-
|
|
580
|
-
### vs Vue
|
|
581
|
-
|
|
582
|
-
- β
More explicit reactivity with signals
|
|
583
|
-
- β
Better TypeScript inference
|
|
584
|
-
- β
Lighter weight
|
|
585
|
-
- β οΈ No single-file components
|
|
252
|
+
## Ecosystem
|
|
586
253
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
254
|
+
| Package | Purpose |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| `@fluixi/core` | Framework faΓ§ade β components, control flow, router, render |
|
|
257
|
+
| `@fluixi/reactive` | Signals, memos, effects, store |
|
|
258
|
+
| `@fluixi/dom` | The DOM rendering runtime |
|
|
259
|
+
| `@fluixi/start` | SSR, hydration, file routing, dev/build |
|
|
260
|
+
| `@fluixi/vite-plugin` | JSX compile (Vite) |
|
|
261
|
+
| `create-fluixi` | App scaffolder (`npm create fluixi`) |
|
|
590
262
|
|
|
591
263
|
## License
|
|
592
264
|
|
|
593
|
-
MIT
|
|
594
|
-
|
|
595
|
-
## Credits
|
|
596
|
-
|
|
597
|
-
Fluixi is inspired by:
|
|
598
|
-
- [SolidJS](https://www.solidjs.com/) - For the reactivity model and API design
|
|
599
|
-
- [Lit](https://lit.dev/) - For the template system
|
|
600
|
-
- The [TC39 Signals Proposal](https://github.com/tc39/proposal-signals)
|
|
265
|
+
MIT
|
|
601
266
|
|
|
602
267
|
## Links
|
|
603
268
|
|
|
604
|
-
- [Documentation](https://github.com/your-org/fluixi/docs)
|
|
605
|
-
- [Examples](https://github.com/your-org/fluixi/examples)
|
|
606
|
-
- [GitHub](https://github.com/your-org/fluixi)
|
|
607
269
|
- [npm](https://www.npmjs.com/package/@fluixi/core)
|
|
608
|
-
|
|
609
|
-
---
|
|
610
|
-
|
|
611
|
-
Made with β by the Adafri team
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluixi/core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.55",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"description": "Solid-Compatible Fine-Grained UI Framework",
|
|
5
6
|
"private": false,
|
|
6
7
|
"publishConfig": {
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"files": [
|
|
19
20
|
"dist",
|
|
20
21
|
"types",
|
|
21
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
22
24
|
],
|
|
23
25
|
"exports": {
|
|
24
26
|
"./types": {
|
|
@@ -84,16 +86,16 @@
|
|
|
84
86
|
"lit": "^3.0.0",
|
|
85
87
|
"lit-html": "^3.0.0",
|
|
86
88
|
"vite-plugin-node-polyfills": "^0.25.0",
|
|
87
|
-
"@fluixi/compiler": "1.0.0-alpha.
|
|
88
|
-
"@fluixi/dom": "1.0.0-alpha.
|
|
89
|
-
"@fluixi/jsx": "1.0.0-alpha.
|
|
90
|
-
"@fluixi/lit": "1.0.0-alpha.
|
|
91
|
-
"@fluixi/vite-plugin": "1.0.0-alpha.
|
|
92
|
-
"@fluixi/reactive": "1.0.0-alpha.
|
|
93
|
-
"@fluixi/router": "0.1.2-alpha.
|
|
94
|
-
"@fluixi/server": "1.0.0-alpha.
|
|
95
|
-
"@fluixi/utils": "1.0.0-alpha.
|
|
96
|
-
"@fluixi/head": "0.1.0-alpha.
|
|
89
|
+
"@fluixi/compiler": "1.0.0-alpha.55",
|
|
90
|
+
"@fluixi/dom": "1.0.0-alpha.55",
|
|
91
|
+
"@fluixi/jsx": "1.0.0-alpha.55",
|
|
92
|
+
"@fluixi/lit": "1.0.0-alpha.55",
|
|
93
|
+
"@fluixi/vite-plugin": "1.0.0-alpha.55",
|
|
94
|
+
"@fluixi/reactive": "1.0.0-alpha.55",
|
|
95
|
+
"@fluixi/router": "0.1.2-alpha.3",
|
|
96
|
+
"@fluixi/server": "1.0.0-alpha.55",
|
|
97
|
+
"@fluixi/utils": "1.0.0-alpha.55",
|
|
98
|
+
"@fluixi/head": "0.1.0-alpha.22"
|
|
97
99
|
},
|
|
98
100
|
"devDependencies": {
|
|
99
101
|
"@bambiste/mix-builder": "next",
|