fusee-framework 1.7.0 → 1.9.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.
- package/README.md +206 -139
- package/app/benchmark/benchmark.js +2 -2
- package/app/benchmark/vite.config.js +23 -0
- package/app/main.js +24 -3
- package/app/main.ts +7 -1
- package/app/pages/_layout.js +27 -0
- package/app/pages/async-work.js +65 -0
- package/app/pages/index.js +25 -5
- package/app/pages/index.ts +9 -0
- package/app/pages/router-example.js +24 -1
- package/app/pages/scroll-demo.ts +102 -0
- package/app/providers/ChildComponent.js +8 -2
- package/app/providers/ChildComponent.ts +47 -47
- package/app/providers/GrandchildComponent.js +19 -0
- package/app/providers/ParentComponent.js +5 -1
- package/app/routes/RouterExample.js +23 -1
- package/app/routes/TestResource.js +47 -0
- package/app/services.js +15 -0
- package/auto-imports.d.ts +12 -0
- package/framework/auto-import-preset.js +15 -9
- package/framework/core/async-context.js +27 -0
- package/framework/core/compiler/ast.js +167 -0
- package/framework/core/compiler/errors-list.js +47 -0
- package/framework/core/compiler/errors.js +107 -0
- package/framework/core/compiler/generator.js +363 -0
- package/framework/core/compiler/lexer.js +347 -0
- package/framework/core/compiler/main-compiler.js +115 -0
- package/framework/core/compiler/parser.js +414 -0
- package/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/framework/core/compiler/transformer.js +398 -0
- package/framework/core/component.js +61 -23
- package/framework/core/di.js +187 -0
- package/framework/core/directives.js +40 -96
- package/framework/core/evaluator.js +1 -1
- package/framework/core/h.js +430 -0
- package/framework/core/hydration.js +83 -0
- package/framework/core/reconcile.js +151 -0
- package/framework/core/signal.js +269 -0
- package/framework/core/store.js +322 -81
- package/framework/index.js +5 -3
- package/framework/router/file-router.js +1 -1
- package/framework/router/router.js +113 -21
- package/framework/server/actions.js +173 -0
- package/framework/server/actions.server.js +88 -0
- package/framework/server/index.js +18 -0
- package/framework/types/actions.d.ts +50 -0
- package/framework/types/actions.server.d.ts +36 -0
- package/framework/types/compiler/ast.d.ts +166 -0
- package/framework/types/compiler/errors-codes.d.ts +23 -0
- package/framework/types/compiler/errors.d.ts +43 -0
- package/framework/types/compiler/generator.d.ts +8 -0
- package/framework/types/compiler/h.d.ts +46 -0
- package/framework/types/compiler/lexer.d.ts +27 -0
- package/framework/types/compiler/main-compiler.d.ts +60 -0
- package/framework/types/compiler/parser.d.ts +4 -0
- package/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/framework/types/compiler/transformer.d.ts +28 -0
- package/framework/types/component.d.ts +89 -88
- package/framework/types/di.d.ts +43 -0
- package/framework/types/hydration.d.ts +30 -0
- package/framework/types/index.d.ts +38 -22
- package/framework/types/reconcile.d.ts +7 -0
- package/framework/types/router.d.ts +11 -1
- package/framework/types/server.d.ts +11 -0
- package/framework/types/signal.d.ts +142 -79
- package/framework/types/store.d.ts +66 -8
- package/ghid_go_fusee_integrat_go1_26.txt +276 -0
- package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
- package/package.json +44 -36
- package/vite.config.js +65 -47
package/README.md
CHANGED
|
@@ -1,139 +1,206 @@
|
|
|
1
|
-
# Fusée Framework
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1
|
+
# Fusée Framework
|
|
2
|
+
|
|
3
|
+
**v1.9.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
|
|
4
|
+
|
|
5
|
+
Fusée is a custom, high-performance fine-grained reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, Dependency Injection for components and a comprehensive CLI for instant application scaffolding.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
The fastest way to get started with Fusée is via the **Go-Powered CLI**:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Install Fusée CLI globally
|
|
15
|
+
npm install -g fusee-framework
|
|
16
|
+
|
|
17
|
+
# 2. Scaffold a new high-performance project
|
|
18
|
+
create-fusee-app my-awesome-app
|
|
19
|
+
# or simply
|
|
20
|
+
fusee init my-awesome-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Follow the interactive prompt to choose your template (**JavaScript** or **TypeScript**) and launch your app instantly!
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## File-Based Routing (Nuxt-style)
|
|
28
|
+
|
|
29
|
+
Fusée now supports automated, file-based routing!
|
|
30
|
+
|
|
31
|
+
- **Pages**: Just drop a file in `app/pages/` and it becomes a route.
|
|
32
|
+
- **Layouts**: Use `app/pages/_layout.js` to wrap your pages in consistent UI structures.
|
|
33
|
+
- **Dynamic Routes**: Support for `[id].js` style dynamic parameters.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Generate a new page instantly
|
|
37
|
+
fusee generate page contact
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Build-Time HTML Template Compiler (New in v1.9)
|
|
43
|
+
|
|
44
|
+
Fusée now features a fully integrated **Build-Time Template Compiler** powered by the `fuseeCompilerPlugin` for Vite. Instead of parsing and compiling HTML strings at runtime, Fusée compiles external HTML templates into highly optimized virtual DOM creation calls (`h`, `hText`, `hIf`, `hFor`) during the build/dev stage.
|
|
45
|
+
|
|
46
|
+
### Why use the Build-Time Compiler?
|
|
47
|
+
|
|
48
|
+
- **Zero Runtime Overhead**: No template parsing in the browser, leading to smaller bundles and faster page load speeds.
|
|
49
|
+
- **Separation of Concerns**: Write clean HTML in `.template.html` files with full editor autocomplete, syntax highlighting, and formatting support.
|
|
50
|
+
- **Compile-Time Warnings**: Errors in template syntax are caught immediately in your terminal or browser console during development.
|
|
51
|
+
|
|
52
|
+
### How to use it:
|
|
53
|
+
|
|
54
|
+
1. **Create an HTML template file** (e.g., `Welcome.template.html`):
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<div class="card">
|
|
58
|
+
<h2>Hello, {{ name }}!</h2>
|
|
59
|
+
<input f-model="name" placeholder="Type a name..." />
|
|
60
|
+
|
|
61
|
+
<button @click="increment">Clicked {{ count }} times</button>
|
|
62
|
+
|
|
63
|
+
<p f-if="count() > 0">Double: <strong>{{ double }}</strong></p>
|
|
64
|
+
</div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. **Import and bind the `render` function** in your component file (e.g., `Welcome.js`):
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
import { render } from "./Welcome.template.html";
|
|
71
|
+
|
|
72
|
+
export const Welcome = defineComponent({
|
|
73
|
+
render,
|
|
74
|
+
setup() {
|
|
75
|
+
const name = signal("World");
|
|
76
|
+
const count = signal(0);
|
|
77
|
+
const double = computed(() => count() * 2);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
count,
|
|
82
|
+
double,
|
|
83
|
+
increment: () => count(count() + 1),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Vite intercepts the `.template.html` import, runs the compiler, and inlines the generated JavaScript render code seamlessly!
|
|
90
|
+
|
|
91
|
+
Benchmark results for specific stress tests (from https://github.com/codegenixdev/js-frontend-frameworks-benchmark):
|
|
92
|
+
|
|
93
|
+
| Action | Avg Duration (ms) |
|
|
94
|
+
| :--------------------------------- | :---------------- |
|
|
95
|
+
| Create 50,000 Rows | 12.60 |
|
|
96
|
+
| Update Every 10th Row (Salary +50) | 15.90 |
|
|
97
|
+
| Swap 2nd and 9th-to-last Rows | 4.60 |
|
|
98
|
+
| Clear All Rows | 0.70 |
|
|
99
|
+
| **Total Average** | **33.80** |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Key Features
|
|
104
|
+
|
|
105
|
+
- **New Go-Powered CLI**: Blazing fast project scaffolding and resource generation.
|
|
106
|
+
- **Signals/Resources-First Reactivity**: Modern atomic updates that ensure only the modified parts of the DOM are touched.
|
|
107
|
+
- **Nuxt-style File Routing**: Automated route discovery with layout support.
|
|
108
|
+
- **Recursive Hybrid Compiler**: A robust architectural approach to node traversal.
|
|
109
|
+
- **Optimized Directives**: Native support for `f-if`, `f-for`, `f-model`, `f-text`, `f-cloak` and more.
|
|
110
|
+
- **Performance Shield (`f-once`)**: Isolate and stabilize static subtrees.
|
|
111
|
+
- **Vite Integration**: Full support for the fastest development workflow and HMR.
|
|
112
|
+
- **Dependency Injection**: Nested Provide/Inject and Shadowing.
|
|
113
|
+
- **Memory Safety**: Explicit checks for memory leaks and automatic effect disposal.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Project Structure
|
|
118
|
+
|
|
119
|
+
A typical Fusée project looks like this:
|
|
120
|
+
|
|
121
|
+
- **`framework/`**: The core reactive engine.
|
|
122
|
+
- **`app/`**: Your application logic (routes, components).
|
|
123
|
+
- **`index.html`**: The entry point of your reactive world.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Example Component
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
export const Welcome = defineComponent({
|
|
131
|
+
setup() {
|
|
132
|
+
const name = signal("World");
|
|
133
|
+
const count = signal(0);
|
|
134
|
+
const double = computed(() => count() * 2);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
name,
|
|
138
|
+
count,
|
|
139
|
+
double,
|
|
140
|
+
inc: () => count(count() + 1),
|
|
141
|
+
template: `
|
|
142
|
+
<div class="card">
|
|
143
|
+
<h2>Hello, {{ name }}!</h2>
|
|
144
|
+
<input f-model="name" placeholder="Name" />
|
|
145
|
+
|
|
146
|
+
<button @click.debounce.300ms="inc">
|
|
147
|
+
Clicked {{ count }} times
|
|
148
|
+
</button>
|
|
149
|
+
|
|
150
|
+
<p f-if="count() > 0">
|
|
151
|
+
Double: <strong>{{ double }}</strong>
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
`,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Quality Assurance & Testing
|
|
163
|
+
|
|
164
|
+
Fusée is built with a test-driven mindset to ensure the reliability of its reactivity engine and component lifecycle.
|
|
165
|
+
|
|
166
|
+
- **Total Tests:** `550`
|
|
167
|
+
- **Status:** `All Passed`
|
|
168
|
+
- **Framework:** [Vitest](https://vitest.dev/)
|
|
169
|
+
- **Environment:** JSDOM (Browser simulation)
|
|
170
|
+
|
|
171
|
+
### Running Tests
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Run all tests (easiest)
|
|
175
|
+
npm test
|
|
176
|
+
|
|
177
|
+
# Run all tests with verbose output
|
|
178
|
+
npx vitest --reporter=verbose
|
|
179
|
+
|
|
180
|
+
# Run type tests only
|
|
181
|
+
npx vitest run --typecheck
|
|
182
|
+
|
|
183
|
+
# Run tests in watch mode
|
|
184
|
+
npx vitest
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Test Coverage includes:
|
|
188
|
+
|
|
189
|
+
- **Reactivity Engine:** 100% (Signals, Batching, Computed, Watchers).
|
|
190
|
+
- **Component System:** Props, Slots, Async Components, and Lifecycle Hooks.
|
|
191
|
+
- **Dependency Injection:** Nested Provide/Inject and Shadowing.
|
|
192
|
+
- **Directives:** `f-if`, `f-for`, `f-model`, and Event Modifiers.
|
|
193
|
+
- **Events:** Event delegation and native event handling.
|
|
194
|
+
- **Memory Safety:** Explicit checks for memory leaks and automatic effect disposal.
|
|
195
|
+
- **Type Safety:** Comprehensive TypeScript definitions and type tests.
|
|
196
|
+
- **Integration Tests:** Advanced module combinations and edge cases.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Future Roadmap
|
|
201
|
+
|
|
202
|
+
- [ ] Advanced state management and SSR support
|
|
203
|
+
- [x] Optimising the existing HTML compiler and remove useless DOM trasversals in lists
|
|
204
|
+
- [ ] Integrated backend development support
|
|
205
|
+
|
|
206
|
+
Built by me, Sebi Somu, a forward-thinking JavaScript Architect.
|
|
@@ -96,7 +96,7 @@ function renderTable(rows, container) {
|
|
|
96
96
|
</tr>
|
|
97
97
|
</thead>
|
|
98
98
|
<tbody>
|
|
99
|
-
<tr f-for="row in rows" :class="row.selected() ? 'selected' : ''">
|
|
99
|
+
<tr f-for="row in rows" :key="row.id" :class="row.selected() ? 'selected' : ''">
|
|
100
100
|
<td>{{ row.id }}</td>
|
|
101
101
|
<td>{{ row.label() }}</td>
|
|
102
102
|
<td>
|
|
@@ -166,7 +166,7 @@ const benchmarks = {
|
|
|
166
166
|
<table>
|
|
167
167
|
<thead><tr><th>ID</th><th>Label</th></tr></thead>
|
|
168
168
|
<tbody>
|
|
169
|
-
<tr f-for="row in rows" :class="row.selected() ? 'selected' : ''">
|
|
169
|
+
<tr f-for="row in rows" :key="row.id" :class="row.selected() ? 'selected' : ''">
|
|
170
170
|
<td>{{ row.id }}</td>
|
|
171
171
|
<td>{{ row.label() }}</td>
|
|
172
172
|
</tr>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
root: './app/benchmark',
|
|
6
|
+
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'fusee-framework': path.resolve(__dirname, '../../framework'),
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
server: {
|
|
14
|
+
port: 5173,
|
|
15
|
+
open: true,
|
|
16
|
+
hmr: true,
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
build: {
|
|
20
|
+
outDir: '../../dist-benchmark',
|
|
21
|
+
emptyOutDir: true,
|
|
22
|
+
}
|
|
23
|
+
})
|
package/app/main.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { createRouter, mountOutlet,
|
|
1
|
+
import { createRouter, mountOutlet, generateRoutes, directive, provideGlobal } from '../framework/index.js'
|
|
2
|
+
import { APP_CONFIG, LoggerService } from './services.js'
|
|
3
|
+
|
|
2
4
|
import { Loading } from './components/Loading.js'
|
|
3
5
|
|
|
4
6
|
directive('focus', {
|
|
@@ -17,13 +19,32 @@ directive('highlight', {
|
|
|
17
19
|
}
|
|
18
20
|
})
|
|
19
21
|
|
|
20
|
-
const pages = import.meta.glob('./pages/**/*.
|
|
22
|
+
const pages = import.meta.glob('./pages/**/*.js')
|
|
23
|
+
|
|
21
24
|
const routes = generateRoutes(pages, {
|
|
22
25
|
loadingComponent: Loading
|
|
23
26
|
})
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
provideGlobal(LoggerService);
|
|
29
|
+
|
|
30
|
+
provideGlobal({
|
|
31
|
+
provide: APP_CONFIG,
|
|
32
|
+
useValue: {
|
|
33
|
+
title: 'Fusee v2 App',
|
|
34
|
+
version: '2.0.0-beta'
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Scroll behavior configuration
|
|
39
|
+
createRouter(routes, {
|
|
40
|
+
scrollBehavior: {
|
|
41
|
+
scrollToTop: true,
|
|
42
|
+
scrollToAnchor: true,
|
|
43
|
+
saveScrollPosition: true
|
|
44
|
+
}
|
|
45
|
+
})
|
|
26
46
|
|
|
27
47
|
const outlet = document.getElementById('app')
|
|
28
48
|
if (!outlet) throw new Error('[app] #app element not found in DOM')
|
|
49
|
+
|
|
29
50
|
mountOutlet(outlet)
|
package/app/main.ts
CHANGED
package/app/pages/_layout.js
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
1
|
import { defineComponent } from '../../framework/index.js'
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
|
|
3
5
|
export const RootLayout = defineComponent({
|
|
6
|
+
|
|
4
7
|
setup() {
|
|
8
|
+
|
|
5
9
|
return {
|
|
10
|
+
|
|
6
11
|
template: `
|
|
12
|
+
|
|
7
13
|
<div class="root-layout">
|
|
14
|
+
|
|
8
15
|
<nav>
|
|
16
|
+
|
|
9
17
|
<a href="/" f-link>Home</a>
|
|
18
|
+
|
|
10
19
|
<a href="/about" f-link>About</a>
|
|
20
|
+
|
|
11
21
|
<a href="/test-for" f-link>Loops Test</a>
|
|
22
|
+
|
|
12
23
|
<a href="/dynamic" f-link style="color: #ec4899;">Dynamic Components</a>
|
|
24
|
+
|
|
13
25
|
<a href="/composables" f-link style="color: #34d399;">Composables</a>
|
|
26
|
+
|
|
14
27
|
<a href="/users" f-link style="color: #f59e0b;">👥 Users (Nested)</a>
|
|
28
|
+
|
|
15
29
|
<a href="/router-example" f-link style="color: #8b8bff;">🔗 Router Example</a>
|
|
30
|
+
<a href="/async-work" f-link style="color: #34d399;">⚡ Async Work</a>
|
|
31
|
+
|
|
16
32
|
</nav>
|
|
33
|
+
|
|
17
34
|
|
|
35
|
+
|
|
18
36
|
<main class="content-wrap">
|
|
37
|
+
|
|
19
38
|
<!-- This is where all pages will be rendered -->
|
|
39
|
+
|
|
20
40
|
<div data-router-view></div>
|
|
41
|
+
|
|
21
42
|
</main>
|
|
43
|
+
|
|
22
44
|
</div>
|
|
45
|
+
|
|
23
46
|
`
|
|
47
|
+
|
|
24
48
|
}
|
|
49
|
+
|
|
25
50
|
}
|
|
51
|
+
|
|
26
52
|
})
|
|
53
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineComponent, signal, resource } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
export default defineComponent({
|
|
4
|
+
setup() {
|
|
5
|
+
const userId = signal(1)
|
|
6
|
+
|
|
7
|
+
const [user, { refetch }] = resource(userId, async (id) => {
|
|
8
|
+
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
|
|
9
|
+
if (!response.ok) throw new Error('User not found')
|
|
10
|
+
return response.json()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
userId,
|
|
15
|
+
user,
|
|
16
|
+
refetch,
|
|
17
|
+
next: () => userId(userId() + 1),
|
|
18
|
+
prev: () => userId() > 1 && userId(userId() - 1),
|
|
19
|
+
template: `
|
|
20
|
+
<div class="page">
|
|
21
|
+
<h1>⚡ Async Work with Resources</h1>
|
|
22
|
+
<p>This page demonstrates the new <code>resource</code> primitive, inspired by SolidJS. It handles async data fetching with reactive loading and error states.</p>
|
|
23
|
+
|
|
24
|
+
<div class="demo-section">
|
|
25
|
+
<div class="flex gap-2 mb-6">
|
|
26
|
+
<button @click="prev" class="cmp-btn minus" :disabled="userId() <= 1">← Previous</button>
|
|
27
|
+
<button @click="next" class="cmp-btn plus">Next User →</button>
|
|
28
|
+
<button @click="refetch" class="cmp-btn reset">🔄 Refetch</button>
|
|
29
|
+
<span class="ml-4 flex items-center">Current ID: <strong>{{ userId }}</strong></span>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div f-if="user.loading()">
|
|
33
|
+
<div class="action-btn btn-loading">Fetching data from API...</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div f-if="user.error()">
|
|
37
|
+
<div class="action-btn btn-error">
|
|
38
|
+
<strong>Error:</strong> {{ user.error().message }}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div f-if="user() && !user.loading()">
|
|
43
|
+
<div class="theme-box theme-dark">
|
|
44
|
+
<h2 class="text-xl font-bold mb-2">{{ user().name }}</h2>
|
|
45
|
+
<p><strong>Username:</strong> @{{ user().username }}</p>
|
|
46
|
+
<p><strong>Email:</strong> {{ user().email }}</p>
|
|
47
|
+
<p><strong>Website:</strong> <a :href="'https://' + user().website" target="_blank" style="color: #8b8bff">{{ user().website }}</a></p>
|
|
48
|
+
<p><strong>Company:</strong> {{ user().company.name }}</p>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="info-section">
|
|
54
|
+
<h2>How it works:</h2>
|
|
55
|
+
<ul>
|
|
56
|
+
<li><strong>Reactive Source:</strong> The fetcher automatically reruns when <code>userId</code> changes.</li>
|
|
57
|
+
<li><strong>State Tracking:</strong> <code>user.loading()</code> and <code>user.error()</code> are signals updated by the framework.</li>
|
|
58
|
+
<li><strong>Batching:</strong> Loading and data states are updated atomically to prevent UI flickers.</li>
|
|
59
|
+
</ul>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
package/app/pages/index.js
CHANGED
|
@@ -52,10 +52,21 @@ export const Home = defineComponent({
|
|
|
52
52
|
<p>This is an SPA built with my own JavaScript custom framework!</p>
|
|
53
53
|
<p>Made with passion by Sebastian Șomu</p>
|
|
54
54
|
|
|
55
|
+
<!-- Navigation -->
|
|
56
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 1.5rem 0; padding: 1rem; background: #2a2a2a; border-radius: 8px;">
|
|
57
|
+
<a href="#classlist" f-link style="color: #f59e0b; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🎨 f-classList</a>
|
|
58
|
+
<a href="#userform" f-link style="color: #10b981; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">📝 User Form</a>
|
|
59
|
+
<a href="#counter" f-link style="color: #3b82f6; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🔢 Counter</a>
|
|
60
|
+
<a href="#emit" f-link style="color: #4da6ff; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🧪 Emit Events</a>
|
|
61
|
+
<a href="#slots" f-link style="color: #8b5cf6; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🎴 Slots</a>
|
|
62
|
+
<a href="#interpolation" f-link style="color: #e83e8c; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🔤 Interpolation</a>
|
|
63
|
+
<a href="#provide" f-link style="color: #4CAF50; text-decoration: none; padding: 0.5rem 1rem; background: #3a3a3a; border-radius: 4px;">🔗 Provide/Inject</a>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
55
66
|
<button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
|
|
56
67
|
|
|
57
68
|
<!-- 🎨 f-classList Demo -->
|
|
58
|
-
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #f59e0b;">🎨 f-classList Demo</h3>
|
|
69
|
+
<h3 id="classlist" style="margin-top: 40px; margin-bottom: 20px; color: #f59e0b;">🎨 f-classList Demo</h3>
|
|
59
70
|
<div class="demo-box" f-classList="basicClasses">
|
|
60
71
|
Demo Box (hover me!)
|
|
61
72
|
</div>
|
|
@@ -71,16 +82,18 @@ export const Home = defineComponent({
|
|
|
71
82
|
</button>
|
|
72
83
|
</div>
|
|
73
84
|
|
|
85
|
+
<h3 id="userform" style="margin-top: 40px; margin-bottom: 20px; color: #10b981;">📝 User Form Demo</h3>
|
|
74
86
|
{{ UserForm }}
|
|
75
87
|
|
|
88
|
+
<h3 id="counter" style="margin-top: 40px; margin-bottom: 20px; color: #3b82f6;">🔢 Counter Demo</h3>
|
|
76
89
|
{{ CounterSection @counter-change="handleCounterChange" @counter-reset="handleCounterReset" @custom-event="handleCustomEvent" :pageTitle="title" }}
|
|
77
90
|
|
|
78
91
|
<!-- 🧪 EMIT TEST - NEW SEPARATE COMPONENT -->
|
|
79
|
-
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #4da6ff;">🧪 EMIT EVENTS TEST LAB</h3>
|
|
92
|
+
<h3 id="emit" style="margin-top: 40px; margin-bottom: 20px; color: #4da6ff;">🧪 EMIT EVENTS TEST LAB</h3>
|
|
80
93
|
{{ EmitTest @simple-click="handleSimpleClick" @click-with-data="handleClickWithData" @object-event="handleObjectEvent" }}
|
|
81
94
|
|
|
82
95
|
<!-- 🧪 Slots Test -->
|
|
83
|
-
<h3 style="margin-top: 40px; margin-bottom: 20px;">🎴 Slots Test</h3>
|
|
96
|
+
<h3 id="slots" style="margin-top: 40px; margin-bottom: 20px;">🎴 Slots Test</h3>
|
|
84
97
|
|
|
85
98
|
{{ Card title="Custom Card" }}
|
|
86
99
|
<template slot="header">
|
|
@@ -100,7 +113,7 @@ export const Home = defineComponent({
|
|
|
100
113
|
{{ /Card }}
|
|
101
114
|
|
|
102
115
|
<!-- 🧪 INTERPOLATION TEST -->
|
|
103
|
-
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #e83e8c;">🔤 INTERPOLATION TEST</h3>
|
|
116
|
+
<h3 id="interpolation" style="margin-top: 40px; margin-bottom: 20px; color: #e83e8c;">🔤 INTERPOLATION TEST</h3>
|
|
104
117
|
<div style="background: #2a2a2a; padding: 15px; border-radius: 8px; border: 1px solid #444;">
|
|
105
118
|
<p><strong>Escaped Braces:</strong> \\{{ this is not evaluated }}</p>
|
|
106
119
|
<p><strong>String Literal with Braces:</strong> {{ "Look, {{ braces }} inside a string!" }}</p>
|
|
@@ -108,8 +121,15 @@ export const Home = defineComponent({
|
|
|
108
121
|
</div>
|
|
109
122
|
|
|
110
123
|
<!-- 🧪 PROVIDE / INJECT TEST -->
|
|
111
|
-
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #4CAF50;">🔗 PROVIDE / INJECT TEST</h3>
|
|
124
|
+
<h3 id="provide" style="margin-top: 40px; margin-bottom: 20px; color: #4CAF50;">🔗 PROVIDE / INJECT TEST</h3>
|
|
112
125
|
{{ ParentComponent }}
|
|
126
|
+
|
|
127
|
+
<!-- Back to top -->
|
|
128
|
+
<div style="text-align: center; margin-top: 3rem; padding: 2rem;">
|
|
129
|
+
<a href="/" f-link style="color: white; text-decoration: none; padding: 0.75rem 1.5rem; background: #3b82f6; border-radius: 6px; font-weight: bold;">
|
|
130
|
+
⬆️ Back to Top
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
113
133
|
</div>
|
|
114
134
|
`
|
|
115
135
|
}
|
package/app/pages/index.ts
CHANGED
|
@@ -50,6 +50,15 @@ export const Home = defineComponent({
|
|
|
50
50
|
<p>This is an SPA built with my own (TypeScript!) custom framework!</p>
|
|
51
51
|
<p>Made with passion by Sebastian Șomu</p>
|
|
52
52
|
|
|
53
|
+
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap; margin: 1rem 0;">
|
|
54
|
+
<a href="/scroll-demo" f-link class="cmp-btn" style="background: linear-gradient(135deg, #8b8bff, #6b6bff);">
|
|
55
|
+
🔽 Scroll Behavior Demo
|
|
56
|
+
</a>
|
|
57
|
+
<a href="/users" f-link class="cmp-btn">
|
|
58
|
+
👥 Router Example
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
53
62
|
<button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
|
|
54
63
|
|
|
55
64
|
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #f59e0b;">🎨 f-classList Demo</h3>
|