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.
Files changed (112) hide show
  1. package/README.md +29 -13
  2. package/app/benchmark/benchmark.js +633 -0
  3. package/app/benchmark/index.html +102 -0
  4. package/app/benchmark/styles.css +249 -0
  5. package/app/components/Counter.js +17 -7
  6. package/app/components/CounterSection.js +3 -4
  7. package/app/main.js +29 -62
  8. package/app/main.ts +72 -63
  9. package/app/pages/404.js +16 -0
  10. package/app/pages/[...all].js +35 -0
  11. package/app/pages/[...all].ts +35 -0
  12. package/app/pages/_layout.js +26 -0
  13. package/app/pages/_layout.ts +26 -0
  14. package/app/pages/about.js +23 -0
  15. package/app/pages/about.ts +29 -0
  16. package/app/pages/composables.js +158 -0
  17. package/app/pages/composables.ts +158 -0
  18. package/app/pages/dynamic.js +40 -0
  19. package/app/pages/dynamic.ts +40 -0
  20. package/app/pages/index.js +117 -0
  21. package/app/pages/index.ts +78 -0
  22. package/app/pages/login.ts +21 -0
  23. package/app/pages/objects/[id].js +56 -0
  24. package/app/pages/objects/[id].ts +56 -0
  25. package/app/pages/posts/[category]/[slug].js +56 -0
  26. package/app/pages/posts/[category]/[slug].ts +56 -0
  27. package/app/pages/router-example.js +56 -0
  28. package/app/pages/router-example.ts +56 -0
  29. package/app/pages/test-for.js +65 -0
  30. package/app/pages/test-for.ts +80 -0
  31. package/app/pages/users/[id].js +24 -0
  32. package/app/pages/users/[id].ts +46 -0
  33. package/app/pages/users/index.js +22 -0
  34. package/app/pages/users/index.ts +29 -0
  35. package/app/pages/users.js +43 -0
  36. package/app/pages/users.ts +78 -0
  37. package/app/routes/About.js +1 -1
  38. package/app/routes/About.ts +1 -1
  39. package/app/routes/AuthRoutes.ts +41 -0
  40. package/app/routes/Home.js +9 -0
  41. package/app/routes/NotFound.js +16 -0
  42. package/app/routes/NotFound.ts +16 -0
  43. package/app/routes/RouterExample.js +56 -0
  44. package/app/routes/RouterExample.ts +56 -0
  45. package/app/routes/WildcardDemo.js +35 -0
  46. package/app/routes/WildcardDemo.ts +35 -0
  47. package/auto-imports.d.ts +1 -0
  48. package/bin/create-fusee-linux +0 -0
  49. package/bin/create-fusee-mac-arm64 +0 -0
  50. package/bin/create-fusee-mac-intel +0 -0
  51. package/bin/create-fusee-win.exe +0 -0
  52. package/bin/create-fusee.exe +0 -0
  53. package/bin/index.js +38 -0
  54. package/create-fusee-go/cmd/generate.go +72 -0
  55. package/create-fusee-go/cmd/init.go +38 -0
  56. package/create-fusee-go/cmd/root.go +121 -0
  57. package/create-fusee-go/go.mod +10 -0
  58. package/create-fusee-go/go.sum +10 -0
  59. package/create-fusee-go/internal/assets/assets.go +78 -0
  60. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  61. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  62. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  63. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  64. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  65. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  66. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  67. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  68. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  69. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  70. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  71. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  72. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  73. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  74. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  75. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  76. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  77. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  78. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  79. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  80. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  83. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  84. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  85. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  86. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  87. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  88. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  89. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  90. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  91. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  92. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  93. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  94. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  95. package/create-fusee-go/main.go +7 -0
  96. package/framework/auto-import-preset.js +1 -1
  97. package/framework/core/compiler.js +252 -249
  98. package/framework/core/component.js +265 -260
  99. package/framework/core/directives.js +875 -786
  100. package/framework/core/evaluator.js +153 -70
  101. package/framework/core/event-delegation.js +33 -14
  102. package/framework/core/signal.js +412 -403
  103. package/framework/index.js +10 -11
  104. package/framework/router/file-router.js +177 -0
  105. package/framework/router/router.js +462 -80
  106. package/framework/types/event-delegation.d.ts +0 -58
  107. package/framework/types/router.d.ts +32 -15
  108. package/framework/types/signal.d.ts +1 -0
  109. package/index.html +638 -626
  110. package/package.json +5 -3
  111. package/tsconfig.json +1 -0
  112. /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.6.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
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 your first project
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
- - **Recursive Hybrid Compiler**: A robust architectural approach to node traversal, eliminating rendering bugs while maintaining "greedy" directive processing for speed.
27
- - **Strict Component System**: Use `defineComponent` for prop validation, lifecycle hooks (`onMount`, `onUnmount`), and automated cleanup.
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 to prevent unnecessary reactivity.
30
- - **Vite Integration**: Full support for the fastest development workflow and Hot Module Replacement (HMR).
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:** `385`
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 file-based routing support and state management
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
+ });