fusee-framework 1.6.0 → 1.7.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 +29 -13
- package/app/benchmark/benchmark.js +633 -0
- package/app/benchmark/index.html +102 -0
- package/app/benchmark/styles.css +249 -0
- package/app/components/Counter.js +17 -7
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +29 -62
- package/app/main.ts +72 -63
- package/app/pages/404.js +16 -0
- package/app/pages/[...all].js +35 -0
- package/app/pages/[...all].ts +35 -0
- package/app/pages/_layout.js +26 -0
- package/app/pages/_layout.ts +26 -0
- package/app/pages/about.js +23 -0
- package/app/pages/about.ts +29 -0
- package/app/pages/composables.js +158 -0
- package/app/pages/composables.ts +158 -0
- package/app/pages/dynamic.js +40 -0
- package/app/pages/dynamic.ts +40 -0
- package/app/pages/index.js +117 -0
- package/app/pages/index.ts +78 -0
- package/app/pages/login.ts +21 -0
- package/app/pages/objects/[id].js +56 -0
- package/app/pages/objects/[id].ts +56 -0
- package/app/pages/posts/[category]/[slug].js +56 -0
- package/app/pages/posts/[category]/[slug].ts +56 -0
- package/app/pages/router-example.js +56 -0
- package/app/pages/router-example.ts +56 -0
- package/app/pages/test-for.js +65 -0
- package/app/pages/test-for.ts +80 -0
- package/app/pages/users/[id].js +24 -0
- package/app/pages/users/[id].ts +46 -0
- package/app/pages/users/index.js +22 -0
- package/app/pages/users/index.ts +29 -0
- package/app/pages/users.js +43 -0
- package/app/pages/users.ts +78 -0
- package/app/routes/About.js +1 -1
- package/app/routes/About.ts +1 -1
- package/app/routes/AuthRoutes.ts +41 -0
- package/app/routes/Home.js +9 -0
- package/app/routes/NotFound.js +16 -0
- package/app/routes/NotFound.ts +16 -0
- package/app/routes/RouterExample.js +56 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/auto-imports.d.ts +1 -0
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/bin/create-fusee.exe +0 -0
- package/bin/index.js +38 -0
- package/create-fusee-go/cmd/generate.go +72 -0
- package/create-fusee-go/cmd/init.go +38 -0
- package/create-fusee-go/cmd/root.go +121 -0
- package/create-fusee-go/go.mod +10 -0
- package/create-fusee-go/go.sum +10 -0
- package/create-fusee-go/internal/assets/assets.go +78 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
- package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
- package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
- package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
- package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
- package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
- package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
- package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
- package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
- package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
- package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
- package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
- package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
- package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
- package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
- package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
- package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
- package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
- package/create-fusee-go/main.go +7 -0
- package/framework/auto-import-preset.js +1 -1
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +265 -260
- package/framework/core/directives.js +875 -786
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +33 -14
- package/framework/core/signal.js +412 -403
- package/framework/index.js +10 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +462 -80
- package/framework/types/event-delegation.d.ts +0 -58
- package/framework/types/router.d.ts +32 -15
- package/framework/types/signal.d.ts +1 -0
- package/index.html +638 -626
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Fusée Framework 🚀
|
|
2
|
-
**v1.
|
|
2
|
+
**v1.7.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
|
|
3
3
|
|
|
4
4
|
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.
|
|
5
5
|
|
|
@@ -7,27 +7,44 @@ Fusée is a custom, high-performance fine-grained reactive JavaScript framework
|
|
|
7
7
|
|
|
8
8
|
## ⚡ Quick Start
|
|
9
9
|
|
|
10
|
-
The fastest way to get started with Fusée is via the CLI
|
|
10
|
+
The fastest way to get started with Fusée is via the **Go-Powered CLI**:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
# 1. Install Fusée globally
|
|
13
|
+
# 1. Install Fusée CLI globally
|
|
14
14
|
npm install -g fusee-framework
|
|
15
15
|
|
|
16
|
-
# 2. Scaffold
|
|
17
|
-
create-fusee-app
|
|
16
|
+
# 2. Scaffold a new high-performance project
|
|
17
|
+
create-fusee-app my-awesome-app
|
|
18
|
+
# or simply
|
|
19
|
+
fusee init my-awesome-app
|
|
20
|
+
```
|
|
21
|
+
Follow the interactive prompt to choose your template (**JavaScript** or **TypeScript**) and launch your app instantly!
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🏗️ File-Based Routing (Nuxt-style)
|
|
26
|
+
Fusée now supports automated, file-based routing. No more manual route definitions!
|
|
27
|
+
|
|
28
|
+
- **Pages**: Just drop a file in `app/pages/` and it becomes a route.
|
|
29
|
+
- **Layouts**: Use `app/pages/_layout.js` to wrap your pages in consistent UI structures.
|
|
30
|
+
- **Dynamic Routes**: Support for `[id].js` style dynamic parameters.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Generate a new page instantly
|
|
34
|
+
fusee generate page contact
|
|
18
35
|
```
|
|
19
|
-
Follow the interactive prompt to choose your template (**JavaScript** or **TypeScript**) and launch your app with `npm run dev`!
|
|
20
36
|
|
|
21
37
|
---
|
|
22
38
|
|
|
23
39
|
## 🔥 Key Features
|
|
24
40
|
|
|
41
|
+
- **New Go-Powered CLI**: Blazing fast project scaffolding and resource generation.
|
|
25
42
|
- **Signals-First Reactivity**: Modern atomic updates that ensure only the modified parts of the DOM are touched.
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
43
|
+
- **Nuxt-style File Routing**: Automated route discovery with layout support.
|
|
44
|
+
- **Recursive Hybrid Compiler**: A robust architectural approach to node traversal.
|
|
28
45
|
- **Optimized Directives**: Native support for `f-if`, `f-for`, `f-model`, `f-text`, `f-cloak` and more.
|
|
29
|
-
- **Performance Shield (`f-once`)**: Isolate and stabilize static subtrees
|
|
30
|
-
- **Vite Integration**: Full support for the fastest development workflow and
|
|
46
|
+
- **Performance Shield (`f-once`)**: Isolate and stabilize static subtrees.
|
|
47
|
+
- **Vite Integration**: Full support for the fastest development workflow and HMR.
|
|
31
48
|
|
|
32
49
|
---
|
|
33
50
|
|
|
@@ -80,7 +97,7 @@ export const Welcome = defineComponent({
|
|
|
80
97
|
|
|
81
98
|
Fusée is built with a test-driven mindset to ensure the reliability of its reactivity engine and component lifecycle.
|
|
82
99
|
|
|
83
|
-
- **Total Tests:** `
|
|
100
|
+
- **Total Tests:** `468`
|
|
84
101
|
- **Status:** `All Passed`
|
|
85
102
|
- **Framework:** [Vitest](https://vitest.dev/)
|
|
86
103
|
- **Environment:** JSDOM (Browser simulation)
|
|
@@ -115,8 +132,7 @@ npx vitest
|
|
|
115
132
|
|
|
116
133
|
## 🚀 Future Roadmap
|
|
117
134
|
|
|
118
|
-
- [ ] Advanced
|
|
119
|
-
- [ ] SSR support
|
|
135
|
+
- [ ] Advanced state management and SSR support
|
|
120
136
|
- [ ] Lightweight compiler which transforms declarative HTML into optimized runtime instructions
|
|
121
137
|
- [ ] Integrated backend development support
|
|
122
138
|
|
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { signal, batch } from '../../framework/core/signal.js';
|
|
2
|
+
import { mountTemplate } from '../../framework/core/compiler.js';
|
|
3
|
+
|
|
4
|
+
// ─── Configuration ────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const WARMUP_RUNS = 5;
|
|
7
|
+
const MEASURE_RUNS = 5;
|
|
8
|
+
|
|
9
|
+
const BENCHMARK_WEIGHTS = {
|
|
10
|
+
create: 0.64,
|
|
11
|
+
replace: 0.56,
|
|
12
|
+
partial: 0.56,
|
|
13
|
+
select: 0.19,
|
|
14
|
+
swap: 0.13,
|
|
15
|
+
remove: 0.53,
|
|
16
|
+
createMany: 0.56,
|
|
17
|
+
append: 0.55,
|
|
18
|
+
clear: 0.42
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const BASELINE = {
|
|
22
|
+
create: 50,
|
|
23
|
+
replace: 55,
|
|
24
|
+
partial: 20,
|
|
25
|
+
select: 10,
|
|
26
|
+
swap: 15,
|
|
27
|
+
remove: 10,
|
|
28
|
+
createMany: 500,
|
|
29
|
+
append: 60,
|
|
30
|
+
clear: 15
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ─── State ─────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
let idCounter = 1;
|
|
36
|
+
const results = {};
|
|
37
|
+
const previewContainer = document.getElementById('previewContainer');
|
|
38
|
+
let currentMode = 'fusée';
|
|
39
|
+
|
|
40
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function buildRow(id) {
|
|
43
|
+
return { id, label: signal(`Row ${id}`), selected: signal(false) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createRows(count) {
|
|
47
|
+
return Array.from({ length: count }, (_, i) => buildRow(idCounter++));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function warmup(fn, times = WARMUP_RUNS) {
|
|
51
|
+
for (let i = 0; i < times; i++) fn();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function measureBenchmark(name, fn) {
|
|
55
|
+
// Warmup
|
|
56
|
+
warmup(fn);
|
|
57
|
+
|
|
58
|
+
// Measure
|
|
59
|
+
const times = [];
|
|
60
|
+
for (let i = 0; i < MEASURE_RUNS; i++) {
|
|
61
|
+
const start = performance.now();
|
|
62
|
+
fn();
|
|
63
|
+
const end = performance.now();
|
|
64
|
+
times.push(end - start);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const mean = times.reduce((a, b) => a + b, 0) / times.length;
|
|
68
|
+
const min = Math.min(...times);
|
|
69
|
+
const max = Math.max(...times);
|
|
70
|
+
|
|
71
|
+
return { name, mean, min, max, times };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderTable(rows, container) {
|
|
75
|
+
const context = {
|
|
76
|
+
rows,
|
|
77
|
+
selectedId: signal(null),
|
|
78
|
+
selectRow(id) {
|
|
79
|
+
batch(() => {
|
|
80
|
+
const prev = rows.find(r => r.id === context.selectedId());
|
|
81
|
+
if (prev) prev.selected(false);
|
|
82
|
+
context.selectedId(id);
|
|
83
|
+
const next = rows.find(r => r.id === id);
|
|
84
|
+
if (next) next.selected(true);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const template = `
|
|
90
|
+
<table>
|
|
91
|
+
<thead>
|
|
92
|
+
<tr>
|
|
93
|
+
<th>ID</th>
|
|
94
|
+
<th>Label</th>
|
|
95
|
+
<th>Actions</th>
|
|
96
|
+
</tr>
|
|
97
|
+
</thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
<tr f-for="row in rows" :class="row.selected() ? 'selected' : ''">
|
|
100
|
+
<td>{{ row.id }}</td>
|
|
101
|
+
<td>{{ row.label() }}</td>
|
|
102
|
+
<td>
|
|
103
|
+
<button @click="selectRow(row.id)">Select</button>
|
|
104
|
+
<button @click="rows.splice(rows.indexOf(row), 1)">Remove</button>
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
</tbody>
|
|
108
|
+
</table>
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
return mountTemplate(template, container, context, {});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Benchmarks ─────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
const benchmarks = {
|
|
117
|
+
create: () => {
|
|
118
|
+
idCounter = 1;
|
|
119
|
+
previewContainer.innerHTML = '';
|
|
120
|
+
const rows = createRows(1000);
|
|
121
|
+
renderTable(rows, previewContainer);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
replace: () => {
|
|
125
|
+
idCounter = 1;
|
|
126
|
+
previewContainer.innerHTML = '';
|
|
127
|
+
const rows = createRows(1000);
|
|
128
|
+
const items = signal(rows);
|
|
129
|
+
renderTable(items, previewContainer);
|
|
130
|
+
|
|
131
|
+
idCounter = 1;
|
|
132
|
+
items(createRows(1000));
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
partial: () => {
|
|
136
|
+
idCounter = 1;
|
|
137
|
+
const rows = createRows(1000);
|
|
138
|
+
renderTable(rows, previewContainer);
|
|
139
|
+
|
|
140
|
+
rows.forEach((r, i) => r.label(`Row ${r.id}`));
|
|
141
|
+
batch(() => {
|
|
142
|
+
for (let i = 0; i < rows.length; i += 10) {
|
|
143
|
+
rows[i].label(`${rows[i].label()} !!!`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
select: () => {
|
|
149
|
+
idCounter = 1;
|
|
150
|
+
const rows = createRows(1000);
|
|
151
|
+
const context = {
|
|
152
|
+
rows,
|
|
153
|
+
selectedId: signal(null),
|
|
154
|
+
selectRow(id) {
|
|
155
|
+
batch(() => {
|
|
156
|
+
const prev = rows.find(r => r.id === context.selectedId());
|
|
157
|
+
if (prev) prev.selected(false);
|
|
158
|
+
context.selectedId(id);
|
|
159
|
+
const next = rows.find(r => r.id === id);
|
|
160
|
+
if (next) next.selected(true);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const template = `
|
|
166
|
+
<table>
|
|
167
|
+
<thead><tr><th>ID</th><th>Label</th></tr></thead>
|
|
168
|
+
<tbody>
|
|
169
|
+
<tr f-for="row in rows" :class="row.selected() ? 'selected' : ''">
|
|
170
|
+
<td>{{ row.id }}</td>
|
|
171
|
+
<td>{{ row.label() }}</td>
|
|
172
|
+
</tr>
|
|
173
|
+
</tbody>
|
|
174
|
+
</table>
|
|
175
|
+
`;
|
|
176
|
+
mountTemplate(template, previewContainer, context, {});
|
|
177
|
+
context.selectRow(rows[41].id);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
swap: () => {
|
|
181
|
+
idCounter = 1;
|
|
182
|
+
const rows = createRows(1000);
|
|
183
|
+
const items = signal(rows);
|
|
184
|
+
renderTable(items, previewContainer);
|
|
185
|
+
|
|
186
|
+
batch(() => {
|
|
187
|
+
const current = items();
|
|
188
|
+
const tmp = current[1];
|
|
189
|
+
current[1] = current[998];
|
|
190
|
+
current[998] = tmp;
|
|
191
|
+
items(current);
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
remove: () => {
|
|
196
|
+
idCounter = 1;
|
|
197
|
+
const rows = createRows(1000);
|
|
198
|
+
const items = signal(rows);
|
|
199
|
+
renderTable(items, previewContainer);
|
|
200
|
+
|
|
201
|
+
const targetId = items()[41].id;
|
|
202
|
+
batch(() => {
|
|
203
|
+
items(items().filter(r => r.id !== targetId));
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
createMany: () => {
|
|
208
|
+
idCounter = 1;
|
|
209
|
+
previewContainer.innerHTML = '';
|
|
210
|
+
const rows = createRows(10000);
|
|
211
|
+
renderTable(rows, previewContainer);
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
append: () => {
|
|
215
|
+
idCounter = 1;
|
|
216
|
+
const rows = createRows(1000);
|
|
217
|
+
const items = signal(rows);
|
|
218
|
+
renderTable(items, previewContainer);
|
|
219
|
+
|
|
220
|
+
const extra = createRows(1000);
|
|
221
|
+
items([...items(), ...extra]);
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
clear: () => {
|
|
225
|
+
idCounter = 1;
|
|
226
|
+
const rows = createRows(1000);
|
|
227
|
+
const items = signal(rows);
|
|
228
|
+
renderTable(items, previewContainer);
|
|
229
|
+
|
|
230
|
+
batch(() => {
|
|
231
|
+
items([]);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// ─── UI ─────────────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
function updateResultCard(benchmarkName, result) {
|
|
239
|
+
const card = document.querySelector(`[data-benchmark="${benchmarkName}"]`);
|
|
240
|
+
if (!card) return;
|
|
241
|
+
|
|
242
|
+
const resultDiv = card.querySelector('.result');
|
|
243
|
+
resultDiv.innerHTML = `
|
|
244
|
+
<span class="time">${result.mean.toFixed(2)}ms</span>
|
|
245
|
+
<span class="status">(min: ${result.min.toFixed(2)}ms, max: ${result.max.toFixed(2)}ms)</span>
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function calculateWGM() {
|
|
250
|
+
const keys = Object.keys(BENCHMARK_WEIGHTS);
|
|
251
|
+
const missingKeys = keys.filter(k => !(k in results));
|
|
252
|
+
|
|
253
|
+
if (missingKeys.length > 0) {
|
|
254
|
+
console.warn(`[WGM] Lipsesc rezultate pentru: ${missingKeys.join(', ')}`);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const totalWeight = keys.reduce((sum, k) => sum + BENCHMARK_WEIGHTS[k], 0);
|
|
259
|
+
const weightedSum = keys.reduce((sum, k) => {
|
|
260
|
+
const ratio = results[k].mean / BASELINE[k];
|
|
261
|
+
return sum + BENCHMARK_WEIGHTS[k] * Math.log(ratio);
|
|
262
|
+
}, 0);
|
|
263
|
+
|
|
264
|
+
return Math.exp(weightedSum / totalWeight);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function updateSummary() {
|
|
268
|
+
const wgm = calculateWGM();
|
|
269
|
+
const wgmDiv = document.getElementById('wgmResult');
|
|
270
|
+
const detailsDiv = document.getElementById('details');
|
|
271
|
+
|
|
272
|
+
if (wgm === null) {
|
|
273
|
+
wgmDiv.innerHTML = '<span class="indicator">⏳</span> Run all benchmarks to see results';
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const indicator = wgm < 1 ? '🟢' : wgm < 2 ? '🟡' : '🔴';
|
|
278
|
+
wgmDiv.innerHTML = `
|
|
279
|
+
<span class="indicator">${indicator}</span> WGM Score: <span class="score">${wgm.toFixed(3)}x</span> baseline
|
|
280
|
+
`;
|
|
281
|
+
|
|
282
|
+
let tableHtml = '<table><thead><tr><th>Benchmark</th><th>Mean (ms)</th><th>Ratio</th></tr></thead><tbody>';
|
|
283
|
+
|
|
284
|
+
for (const [key, weight] of Object.entries(BENCHMARK_WEIGHTS)) {
|
|
285
|
+
if (results[key]) {
|
|
286
|
+
const ratio = (results[key].mean / BASELINE[key]).toFixed(2);
|
|
287
|
+
tableHtml += `<tr><td>${key}</td><td>${results[key].mean.toFixed(2)}</td><td>${ratio}x</td></tr>`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
tableHtml += '</tbody></table>';
|
|
292
|
+
detailsDiv.innerHTML = tableHtml;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function runBenchmark(benchmarkName) {
|
|
296
|
+
const benchmark = benchmarks[benchmarkName];
|
|
297
|
+
if (!benchmark) return;
|
|
298
|
+
|
|
299
|
+
const card = document.querySelector(`[data-benchmark="${benchmarkName}"]`);
|
|
300
|
+
const resultDiv = card.querySelector('.result');
|
|
301
|
+
resultDiv.innerHTML = '<span class="status">Running...</span>';
|
|
302
|
+
|
|
303
|
+
// Use setTimeout to allow UI to update
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
try {
|
|
306
|
+
const result = measureBenchmark(benchmarkName, benchmark);
|
|
307
|
+
results[benchmarkName] = result;
|
|
308
|
+
updateResultCard(benchmarkName, result);
|
|
309
|
+
updateSummary();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
resultDiv.innerHTML = `<span class="status" style="color: red">Error: ${error.message}</span>`;
|
|
312
|
+
console.error(error);
|
|
313
|
+
}
|
|
314
|
+
}, 100);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function runAllBenchmarks() {
|
|
318
|
+
for (const key of Object.keys(benchmarks)) {
|
|
319
|
+
runBenchmark(key);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function clearResults() {
|
|
324
|
+
for (const key of Object.keys(results)) {
|
|
325
|
+
delete results[key];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
document.querySelectorAll('.result').forEach(el => {
|
|
329
|
+
el.innerHTML = '';
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
document.getElementById('wgmResult').innerHTML = '';
|
|
333
|
+
document.getElementById('details').innerHTML = '';
|
|
334
|
+
previewContainer.innerHTML = '';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ─── Event Listeners ───────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
document.getElementById('runAll').addEventListener('click', runAllBenchmarks);
|
|
340
|
+
document.getElementById('clear').addEventListener('click', clearResults);
|
|
341
|
+
|
|
342
|
+
document.querySelectorAll('.run-benchmark').forEach(btn => {
|
|
343
|
+
btn.addEventListener('click', (e) => {
|
|
344
|
+
const card = e.target.closest('.benchmark-card');
|
|
345
|
+
const benchmarkName = card.dataset.benchmark;
|
|
346
|
+
runBenchmark(benchmarkName);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ─── Vanilla JS Benchmarks ─────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
let vanillaIdCounter = 1;
|
|
353
|
+
const vanillaResults = {};
|
|
354
|
+
|
|
355
|
+
function vanillaBuildRow(id) {
|
|
356
|
+
return { id, label: `Row ${id}`, selected: false };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function vanillaCreateRows(count) {
|
|
360
|
+
return Array.from({ length: count }, (_, i) => vanillaBuildRow(vanillaIdCounter++));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function vanillaRenderTable(rows, container) {
|
|
364
|
+
container.innerHTML = `
|
|
365
|
+
<table>
|
|
366
|
+
<thead>
|
|
367
|
+
<tr>
|
|
368
|
+
<th>ID</th>
|
|
369
|
+
<th>Label</th>
|
|
370
|
+
<th>Actions</th>
|
|
371
|
+
</tr>
|
|
372
|
+
</thead>
|
|
373
|
+
<tbody id="vanillaTableBody"></tbody>
|
|
374
|
+
</table>
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
const tbody = container.querySelector('#vanillaTableBody');
|
|
378
|
+
rows.forEach(row => {
|
|
379
|
+
const tr = document.createElement('tr');
|
|
380
|
+
tr.dataset.id = row.id;
|
|
381
|
+
tr.innerHTML = `
|
|
382
|
+
<td>${row.id}</td>
|
|
383
|
+
<td class="label">${row.label}</td>
|
|
384
|
+
<td>
|
|
385
|
+
<button class="select-btn">Select</button>
|
|
386
|
+
<button class="remove-btn">Remove</button>
|
|
387
|
+
</td>
|
|
388
|
+
`;
|
|
389
|
+
tbody.appendChild(tr);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
container.rows = rows;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const vanillaBenchmarks = {
|
|
396
|
+
create: () => {
|
|
397
|
+
vanillaIdCounter = 1;
|
|
398
|
+
previewContainer.innerHTML = '';
|
|
399
|
+
const rows = vanillaCreateRows(1000);
|
|
400
|
+
vanillaRenderTable(rows, previewContainer);
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
replace: () => {
|
|
404
|
+
vanillaIdCounter = 1;
|
|
405
|
+
previewContainer.innerHTML = '';
|
|
406
|
+
const rows = vanillaCreateRows(1000);
|
|
407
|
+
vanillaRenderTable(rows, previewContainer);
|
|
408
|
+
|
|
409
|
+
vanillaIdCounter = 1;
|
|
410
|
+
const newRows = vanillaCreateRows(1000);
|
|
411
|
+
vanillaRenderTable(newRows, previewContainer);
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
partial: () => {
|
|
415
|
+
vanillaIdCounter = 1;
|
|
416
|
+
const rows = vanillaCreateRows(1000);
|
|
417
|
+
vanillaRenderTable(rows, previewContainer);
|
|
418
|
+
|
|
419
|
+
for (let i = 0; i < rows.length; i += 10) {
|
|
420
|
+
rows[i].label += ' !!!';
|
|
421
|
+
const tr = previewContainer.querySelector(`tr[data-id="${rows[i].id}"]`);
|
|
422
|
+
if (tr) {
|
|
423
|
+
tr.querySelector('.label').textContent = rows[i].label;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
select: () => {
|
|
429
|
+
vanillaIdCounter = 1;
|
|
430
|
+
const rows = vanillaCreateRows(1000);
|
|
431
|
+
previewContainer.innerHTML = `
|
|
432
|
+
<table>
|
|
433
|
+
<thead><tr><th>ID</th><th>Label</th></tr></thead>
|
|
434
|
+
<tbody id="vanillaTableBody"></tbody>
|
|
435
|
+
</table>
|
|
436
|
+
`;
|
|
437
|
+
const tbody = previewContainer.querySelector('#vanillaTableBody');
|
|
438
|
+
rows.forEach(row => {
|
|
439
|
+
const tr = document.createElement('tr');
|
|
440
|
+
tr.dataset.id = row.id;
|
|
441
|
+
tr.innerHTML = `<td>${row.id}</td><td class="label">${row.label}</td>`;
|
|
442
|
+
tbody.appendChild(tr);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const targetRow = rows[41];
|
|
446
|
+
const tr = previewContainer.querySelector(`tr[data-id="${targetRow.id}"]`);
|
|
447
|
+
if (tr) tr.classList.add('selected');
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
swap: () => {
|
|
451
|
+
vanillaIdCounter = 1;
|
|
452
|
+
const rows = vanillaCreateRows(1000);
|
|
453
|
+
vanillaRenderTable(rows, previewContainer);
|
|
454
|
+
|
|
455
|
+
const tmp = rows[1];
|
|
456
|
+
rows[1] = rows[998];
|
|
457
|
+
rows[998] = tmp;
|
|
458
|
+
vanillaRenderTable(rows, previewContainer);
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
remove: () => {
|
|
462
|
+
vanillaIdCounter = 1;
|
|
463
|
+
const rows = vanillaCreateRows(1000);
|
|
464
|
+
vanillaRenderTable(rows, previewContainer);
|
|
465
|
+
|
|
466
|
+
const targetId = rows[41].id;
|
|
467
|
+
const tr = previewContainer.querySelector(`tr[data-id="${targetId}"]`);
|
|
468
|
+
if (tr) tr.remove();
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
createMany: () => {
|
|
472
|
+
vanillaIdCounter = 1;
|
|
473
|
+
previewContainer.innerHTML = '';
|
|
474
|
+
const rows = vanillaCreateRows(10000);
|
|
475
|
+
vanillaRenderTable(rows, previewContainer);
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
append: () => {
|
|
479
|
+
vanillaIdCounter = 1;
|
|
480
|
+
const rows = vanillaCreateRows(1000);
|
|
481
|
+
vanillaRenderTable(rows, previewContainer);
|
|
482
|
+
|
|
483
|
+
const extra = vanillaCreateRows(1000);
|
|
484
|
+
rows.push(...extra);
|
|
485
|
+
vanillaRenderTable(rows, previewContainer);
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
clear: () => {
|
|
489
|
+
vanillaIdCounter = 1;
|
|
490
|
+
const rows = vanillaCreateRows(1000);
|
|
491
|
+
vanillaRenderTable(rows, previewContainer);
|
|
492
|
+
|
|
493
|
+
previewContainer.innerHTML = '';
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
function vanillaUpdateResultCard(benchmarkName, result) {
|
|
498
|
+
const card = document.querySelector(`[data-benchmark="${benchmarkName}"]`);
|
|
499
|
+
if (!card) return;
|
|
500
|
+
|
|
501
|
+
const resultDiv = card.querySelector('.result');
|
|
502
|
+
resultDiv.innerHTML = `
|
|
503
|
+
<span class="time" style="color: #e74c3c">${result.mean.toFixed(2)}ms (Vanilla)</span>
|
|
504
|
+
<span class="status">(min: ${result.min.toFixed(2)}ms, max: ${result.max.toFixed(2)}ms)</span>
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function vanillaCalculateWGM() {
|
|
509
|
+
const keys = Object.keys(BENCHMARK_WEIGHTS);
|
|
510
|
+
const missingKeys = keys.filter(k => !(k in vanillaResults));
|
|
511
|
+
|
|
512
|
+
if (missingKeys.length > 0) {
|
|
513
|
+
console.warn(`[WGM Vanilla] Lipsesc rezultate pentru: ${missingKeys.join(', ')}`);
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const totalWeight = keys.reduce((sum, k) => sum + BENCHMARK_WEIGHTS[k], 0);
|
|
518
|
+
const weightedSum = keys.reduce((sum, k) => {
|
|
519
|
+
const ratio = vanillaResults[k].mean / BASELINE[k];
|
|
520
|
+
return sum + BENCHMARK_WEIGHTS[k] * Math.log(ratio);
|
|
521
|
+
}, 0);
|
|
522
|
+
|
|
523
|
+
return Math.exp(weightedSum / totalWeight);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function vanillaUpdateSummary() {
|
|
527
|
+
const wgm = vanillaCalculateWGM();
|
|
528
|
+
const wgmDiv = document.getElementById('wgmResult');
|
|
529
|
+
const detailsDiv = document.getElementById('details');
|
|
530
|
+
|
|
531
|
+
if (wgm === null) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const indicator = wgm < 1 ? '🟢' : wgm < 2 ? '🟡' : '🔴';
|
|
536
|
+
wgmDiv.innerHTML = `
|
|
537
|
+
<span class="indicator">${indicator}</span> Vanilla JS: <span class="score">${wgm.toFixed(3)}x</span> baseline
|
|
538
|
+
`;
|
|
539
|
+
|
|
540
|
+
let tableHtml = '<table style="margin-top: 20px"><thead><tr><th>Benchmark</th><th>Vanilla Mean (ms)</th><th>Ratio</th></tr></thead><tbody>';
|
|
541
|
+
|
|
542
|
+
for (const [key, weight] of Object.entries(BENCHMARK_WEIGHTS)) {
|
|
543
|
+
if (vanillaResults[key]) {
|
|
544
|
+
const ratio = (vanillaResults[key].mean / BASELINE[key]).toFixed(2);
|
|
545
|
+
tableHtml += `<tr><td>${key}</td><td>${vanillaResults[key].mean.toFixed(2)}</td><td>${ratio}x</td></tr>`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
tableHtml += '</tbody></table>';
|
|
550
|
+
detailsDiv.innerHTML = tableHtml;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function vanillaRunBenchmark(benchmarkName) {
|
|
554
|
+
const benchmark = vanillaBenchmarks[benchmarkName];
|
|
555
|
+
if (!benchmark) return;
|
|
556
|
+
|
|
557
|
+
const card = document.querySelector(`[data-benchmark="${benchmarkName}"]`);
|
|
558
|
+
const resultDiv = card.querySelector('.result');
|
|
559
|
+
resultDiv.innerHTML = '<span class="status">Running Vanilla...</span>';
|
|
560
|
+
|
|
561
|
+
setTimeout(() => {
|
|
562
|
+
try {
|
|
563
|
+
const result = measureBenchmark(benchmarkName, benchmark);
|
|
564
|
+
vanillaResults[benchmarkName] = result;
|
|
565
|
+
vanillaUpdateResultCard(benchmarkName, result);
|
|
566
|
+
vanillaUpdateSummary();
|
|
567
|
+
} catch (error) {
|
|
568
|
+
resultDiv.innerHTML = `<span class="status" style="color: red">Error: ${error.message}</span>`;
|
|
569
|
+
console.error(error);
|
|
570
|
+
}
|
|
571
|
+
}, 100);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function vanillaRunAllBenchmarks() {
|
|
575
|
+
for (const key of Object.keys(vanillaBenchmarks)) {
|
|
576
|
+
vanillaRunBenchmark(key);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function vanillaClearResults() {
|
|
581
|
+
for (const key of Object.keys(vanillaResults)) {
|
|
582
|
+
delete vanillaResults[key];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
document.getElementById('wgmResult').innerHTML = '';
|
|
586
|
+
document.getElementById('details').innerHTML = '';
|
|
587
|
+
previewContainer.innerHTML = '';
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ─── Vanilla JS Event Listeners ─────────────────────────────────────────────
|
|
591
|
+
|
|
592
|
+
document.getElementById('runAll').addEventListener('click', () => {
|
|
593
|
+
if (currentMode === 'fusée') {
|
|
594
|
+
runAllBenchmarks();
|
|
595
|
+
} else {
|
|
596
|
+
vanillaRunAllBenchmarks();
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
document.getElementById('clear').addEventListener('click', () => {
|
|
601
|
+
clearResults();
|
|
602
|
+
vanillaClearResults();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
document.querySelectorAll('.run-benchmark').forEach(btn => {
|
|
606
|
+
btn.addEventListener('click', (e) => {
|
|
607
|
+
const card = e.target.closest('.benchmark-card');
|
|
608
|
+
const benchmarkName = card.dataset.benchmark;
|
|
609
|
+
if (currentMode === 'fusée') {
|
|
610
|
+
runBenchmark(benchmarkName);
|
|
611
|
+
} else {
|
|
612
|
+
vanillaRunBenchmark(benchmarkName);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// ─── Mode Switching ─────────────────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
document.getElementById('runFusée').addEventListener('click', () => {
|
|
620
|
+
currentMode = 'fusée';
|
|
621
|
+
document.getElementById('runFusée').classList.add('active');
|
|
622
|
+
document.getElementById('runVanilla').classList.remove('active');
|
|
623
|
+
clearResults();
|
|
624
|
+
vanillaClearResults();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
document.getElementById('runVanilla').addEventListener('click', () => {
|
|
628
|
+
currentMode = 'vanilla';
|
|
629
|
+
document.getElementById('runVanilla').classList.add('active');
|
|
630
|
+
document.getElementById('runFusée').classList.remove('active');
|
|
631
|
+
clearResults();
|
|
632
|
+
vanillaClearResults();
|
|
633
|
+
});
|