lightview 2.3.8 → 2.4.7

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 (45) hide show
  1. package/.gemini/CODE_ANALYSIS_AND_IMPROVEMENT_PLAN.md +56 -0
  2. package/AI-GUIDANCE.md +274 -0
  3. package/README.md +35 -0
  4. package/build_tmp/lightview-cdom.js +3934 -0
  5. package/build_tmp/lightview-router.js +185 -0
  6. package/build_tmp/lightview-x.js +1739 -0
  7. package/build_tmp/lightview.js +740 -0
  8. package/components/data-display/diff.js +36 -4
  9. package/docs/api/hypermedia.html +75 -5
  10. package/docs/api/index.html +3 -3
  11. package/docs/api/nav.html +0 -16
  12. package/docs/articles/html-vs-json-partials.md +102 -0
  13. package/docs/articles/lightview-vs-htmx.md +610 -0
  14. package/docs/assets/styles/site.css +16 -7
  15. package/docs/benchmarks/bau-tagged-fragment.js +41 -0
  16. package/docs/benchmarks/tagged-fragment.js +36 -0
  17. package/docs/cdom.html +127 -88
  18. package/docs/components/chart.html +157 -210
  19. package/docs/components/component-nav.html +1 -1
  20. package/docs/components/diff.html +33 -21
  21. package/docs/components/gallery.html +107 -4
  22. package/docs/components/index.css +18 -3
  23. package/docs/components/index.html +20 -9
  24. package/docs/dom-benchmark.html +771 -0
  25. package/docs/getting-started/index.html +42 -2
  26. package/docs/hypermedia/index.html +391 -0
  27. package/docs/hypermedia/nav.html +17 -0
  28. package/docs/index.html +136 -17
  29. package/index.html +59 -10
  30. package/lightview-all.js +223 -67
  31. package/lightview-cdom.js +1 -2
  32. package/lightview-x.js +144 -13
  33. package/lightview.js +85 -277
  34. package/package.json +2 -2
  35. package/src/lightview-cdom.js +1 -5
  36. package/src/lightview-x.js +158 -27
  37. package/src/lightview.js +94 -60
  38. package/docs/articles/calculator-no-javascript-hackernoon.md +0 -283
  39. package/docs/articles/calculator-no-javascript.md +0 -290
  40. package/docs/articles/part1-reference.md +0 -236
  41. package/lightview.js.bak +0 -1
  42. package/test-xpath.html +0 -63
  43. package/test_error.txt +0 -0
  44. package/test_output.txt +0 -0
  45. package/test_output_full.txt +0 -0
@@ -0,0 +1,771 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>DOM Update Performance Benchmark | Lightview</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.23/dist/full.min.css" rel="stylesheet" type="text/css" />
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Outfit:wght@400;600;700&display=swap"
14
+ rel="stylesheet">
15
+ <style>
16
+ :root {
17
+ --site-primary: #8338ec;
18
+ --site-secondary: #3a86ff;
19
+ --site-accent: #ff006e;
20
+ }
21
+
22
+ body {
23
+ font-family: 'Inter', sans-serif;
24
+ background: #f8fafc;
25
+ }
26
+
27
+ h1,
28
+ h2,
29
+ h3 {
30
+ font-family: 'Outfit', sans-serif;
31
+ }
32
+
33
+ .btn-benchmark {
34
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
35
+ }
36
+
37
+ .btn-benchmark:hover {
38
+ transform: translateY(-2px);
39
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
40
+ }
41
+
42
+ .result-card {
43
+ background: white;
44
+ border-radius: 1rem;
45
+ padding: 1.5rem;
46
+ border: 1px solid #e2e8f0;
47
+ transition: all 0.3s ease;
48
+ }
49
+
50
+ .result-card.active {
51
+ border-color: var(--site-primary);
52
+ box-shadow: 0 4px 6px -1px rgba(131, 56, 236, 0.1);
53
+ }
54
+
55
+ #benchmark-target {
56
+ max-height: 400px;
57
+ overflow: auto;
58
+ border: 1px solid #e2e8f0;
59
+ border-radius: 0.5rem;
60
+ background: white;
61
+ }
62
+
63
+ .progress-bar-custom {
64
+ height: 8px;
65
+ border-radius: 4px;
66
+ background: #e2e8f0;
67
+ overflow: hidden;
68
+ margin-top: 0.5rem;
69
+ }
70
+
71
+ .progress-fill {
72
+ height: 100%;
73
+ background: var(--site-primary);
74
+ width: 0%;
75
+ transition: width 0.5s ease;
76
+ }
77
+
78
+ /* Benchmark Target Styles */
79
+ .benchmark-container {
80
+ padding: 1rem;
81
+ }
82
+
83
+ .items-grid {
84
+ display: grid;
85
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
86
+ gap: 1rem;
87
+ margin-top: 1rem;
88
+ }
89
+
90
+ .item-card {
91
+ background: #fff;
92
+ border: 1px solid #e2e8f0;
93
+ border-radius: 0.5rem;
94
+ padding: 0.75rem;
95
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
96
+ }
97
+
98
+ .item-title {
99
+ font-weight: bold;
100
+ font-size: 0.9rem;
101
+ color: var(--site-primary);
102
+ margin-bottom: 0.25rem;
103
+ }
104
+
105
+ .item-description {
106
+ font-size: 0.8rem;
107
+ color: #64748b;
108
+ line-height: 1.2;
109
+ }
110
+
111
+ .item-footer {
112
+ margin-top: 0.5rem;
113
+ font-size: 0.7rem;
114
+ color: #94a3b8;
115
+ border-top: 1px solid #f1f5f9;
116
+ padding-top: 0.25rem;
117
+ }
118
+ </style>
119
+ </head>
120
+
121
+ <body class="p-4 md:p-8">
122
+ <div class="max-w-6xl mx-auto">
123
+ <header class="mb-12 text-center">
124
+ <h1
125
+ class="text-4xl md:text-5xl font-extrabold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-blue-600">
126
+ DOM Update Benchmark
127
+ </h1>
128
+ <p class="text-xl text-slate-600 max-w-2xl mx-auto">
129
+ Comparing different strategies for updating DOM fragments.
130
+ </p>
131
+ </header>
132
+
133
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
134
+ <!-- Controls -->
135
+ <div class="lg:col-span-1 space-y-4">
136
+ <div class="card bg-white shadow-sm border border-slate-200">
137
+ <div class="card-body">
138
+ <h2 class="card-title text-slate-800">Configuration</h2>
139
+ <div class="form-control w-full">
140
+ <label class="label">
141
+ <span class="label-text text-xs font-bold uppercase text-slate-500">Number of
142
+ Rows</span>
143
+ </label>
144
+ <input type="number" id="row-count" value="1000" class="input input-bordered w-full h-10" />
145
+ </div>
146
+ <div class="form-control w-full mt-2">
147
+ <label class="label">
148
+ <span class="label-text text-xs font-bold uppercase text-slate-500">Number of
149
+ Cycles</span>
150
+ </label>
151
+ <input type="number" id="cycle-count" value="10" class="input input-bordered w-full h-10" />
152
+ </div>
153
+ <div class="mt-6 flex flex-col gap-3">
154
+ <button onclick="runBenchmark('innerHTML')"
155
+ class="btn btn-outline btn-benchmark border-slate-300">Run innerHTML</button>
156
+ <button onclick="runBenchmark('DOMParser')"
157
+ class="btn btn-outline btn-benchmark border-slate-300">Run DOMParser</button>
158
+ <button onclick="runBenchmark('RawVDOM')"
159
+ class="btn btn-outline btn-benchmark border-slate-300">Run Raw vDOM</button>
160
+ <button onclick="runBenchmark('RawoDOM')"
161
+ class="btn btn-outline btn-benchmark border-slate-300">Run Raw oDOM</button>
162
+ <button onclick="runBenchmark('vDOM')"
163
+ class="btn btn-outline btn-benchmark border-slate-300">Run Lightview vDOM</button>
164
+ <button onclick="runBenchmark('JurisODOM')"
165
+ class="btn btn-outline btn-benchmark border-slate-300">Run JurisJS oDOM</button>
166
+ <button onclick="runBenchmark('TaggedScript')"
167
+ class="btn btn-outline btn-benchmark border-slate-300">Run Lightview Tagged
168
+ Script</button>
169
+ <button onclick="runBenchmark('BauTaggedScript')"
170
+ class="btn btn-outline btn-benchmark border-slate-300">Run Bau Tagged
171
+ Script</button>
172
+ <div class="divider"></div>
173
+ <button onclick="runAll()" class="btn btn-primary text-white">Run All Benchmarks</button>
174
+ <button onclick="clearTarget()" class="btn btn-ghost">Clear Target</button>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Results -->
181
+ <div class="lg:col-span-2 space-y-4">
182
+ <div id="results-container" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
183
+ <!-- Template for result cards -->
184
+ <div class="result-card" id="res-innerHTML">
185
+ <div class="flex justify-between items-start mb-2">
186
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">innerHTML</h3>
187
+ <div class="flex flex-col items-end gap-1">
188
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
189
+ </div>
190
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
191
+ class="gzip-size">--</span> KB gzip</div>
192
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
193
+ </div>
194
+ </div>
195
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
196
+
197
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
198
+ <div>Min <span class="min block text-slate-600">--</span></div>
199
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
200
+ <div>Max <span class="max block text-slate-600">--</span></div>
201
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
202
+ </div>
203
+
204
+ <div class="progress-bar-custom">
205
+ <div class="progress-fill" id="fill-innerHTML"></div>
206
+ </div>
207
+ </div>
208
+
209
+ <div class="result-card" id="res-DOMParser">
210
+ <div class="flex justify-between items-start mb-2">
211
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">DOMParser</h3>
212
+ <div class="flex flex-col items-end gap-1">
213
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
214
+ </div>
215
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
216
+ class="gzip-size">--</span> KB gzip</div>
217
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
218
+ </div>
219
+ </div>
220
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
221
+
222
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
223
+ <div>Min <span class="min block text-slate-600">--</span></div>
224
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
225
+ <div>Max <span class="max block text-slate-600">--</span></div>
226
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
227
+ </div>
228
+
229
+ <div class="progress-bar-custom">
230
+ <div class="progress-fill" id="fill-DOMParser"></div>
231
+ </div>
232
+ </div>
233
+
234
+ <div class="result-card" id="res-RawVDOM">
235
+ <div class="flex justify-between items-start mb-2">
236
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Raw vDOM (No Lib)
237
+ </h3>
238
+ <div class="flex flex-col items-end gap-1">
239
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
240
+ </div>
241
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
242
+ class="gzip-size">--</span> KB gzip</div>
243
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
244
+ </div>
245
+ </div>
246
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
247
+
248
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
249
+ <div>Min <span class="min block text-slate-600">--</span></div>
250
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
251
+ <div>Max <span class="max block text-slate-600">--</span></div>
252
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
253
+ </div>
254
+
255
+ <div class="progress-bar-custom">
256
+ <div class="progress-fill" id="fill-RawVDOM"></div>
257
+ </div>
258
+ </div>
259
+
260
+ <div class="result-card" id="res-RawoDOM">
261
+ <div class="flex justify-between items-start mb-2">
262
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Raw oDOM (No Lib)
263
+ </h3>
264
+ <div class="flex flex-col items-end gap-1">
265
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
266
+ </div>
267
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
268
+ class="gzip-size">--</span> KB gzip</div>
269
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
270
+ </div>
271
+ </div>
272
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
273
+
274
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
275
+ <div>Min <span class="min block text-slate-600">--</span></div>
276
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
277
+ <div>Max <span class="max block text-slate-600">--</span></div>
278
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
279
+ </div>
280
+
281
+ <div class="progress-bar-custom">
282
+ <div class="progress-fill" id="fill-RawoDOM"></div>
283
+ </div>
284
+ </div>
285
+
286
+ <div class="result-card" id="res-vDOM">
287
+ <div class="flex justify-between items-start mb-2">
288
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Lightview vDOM</h3>
289
+ <div class="flex flex-col items-end gap-1">
290
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
291
+ </div>
292
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
293
+ class="gzip-size">--</span> KB gzip</div>
294
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
295
+ </div>
296
+ </div>
297
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
298
+
299
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
300
+ <div>Min <span class="min block text-slate-600">--</span></div>
301
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
302
+ <div>Max <span class="max block text-slate-600">--</span></div>
303
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
304
+ </div>
305
+
306
+ <div class="progress-bar-custom">
307
+ <div class="progress-fill" id="fill-vDOM"></div>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="result-card" id="res-JurisODOM">
312
+ <div class="flex justify-between items-start mb-2">
313
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">JurisJS oDOM</h3>
314
+ <div class="flex flex-col items-end gap-1">
315
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
316
+ </div>
317
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
318
+ class="gzip-size">--</span> KB gzip</div>
319
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
320
+ </div>
321
+ </div>
322
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
323
+
324
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
325
+ <div>Min <span class="min block text-slate-600">--</span></div>
326
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
327
+ <div>Max <span class="max block text-slate-600">--</span></div>
328
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
329
+ </div>
330
+
331
+ <div class="progress-bar-custom">
332
+ <div class="progress-fill" id="fill-JurisODOM"></div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="result-card" id="res-TaggedScript">
337
+ <div class="flex justify-between items-start mb-2">
338
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Lightview Tagged
339
+ Script</h3>
340
+ <div class="flex flex-col items-end gap-1">
341
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
342
+ </div>
343
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
344
+ class="gzip-size">--</span> KB gzip</div>
345
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
346
+ </div>
347
+ </div>
348
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
349
+
350
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
351
+ <div>Min <span class="min block text-slate-600">--</span></div>
352
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
353
+ <div>Max <span class="max block text-slate-600">--</span></div>
354
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
355
+ </div>
356
+
357
+ <div class="progress-bar-custom">
358
+ <div class="progress-fill" id="fill-TaggedScript"></div>
359
+ </div>
360
+ </div>
361
+
362
+ <div class="result-card" id="res-BauTaggedScript">
363
+ <div class="flex justify-between items-start mb-2">
364
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Bau Tagged
365
+ Script</h3>
366
+ <div class="flex flex-col items-end gap-1">
367
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
368
+ </div>
369
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
370
+ class="gzip-size">--</span> KB gzip</div>
371
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
372
+ </div>
373
+ </div>
374
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
375
+
376
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
377
+ <div>Min <span class="min block text-slate-600">--</span></div>
378
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
379
+ <div>Max <span class="max block text-slate-600">--</span></div>
380
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
381
+ </div>
382
+
383
+ <div class="progress-bar-custom">
384
+ <div class="progress-fill" id="fill-BauTaggedScript"></div>
385
+ </div>
386
+ </div>
387
+ </div>
388
+
389
+ <div class="card bg-white shadow-sm border border-slate-200">
390
+ <div class="card-body">
391
+ <h2 class="card-title text-slate-800">Benchmark Target</h2>
392
+ <p class="text-sm text-slate-500 mb-2">Live DOM update area:</p>
393
+ <div id="benchmark-target">
394
+ <div class="p-8 text-center text-slate-400 italic">Target is empty. Run a benchmark to
395
+ populate.</div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+
403
+ <!-- Core Libraries -->
404
+ <script src="/lightview.js"></script>
405
+ <script src="/lightview-x.js"></script>
406
+ <script src="https://cdn.jsdelivr.net/npm/juris@0.9.0/juris.min.js"></script>
407
+
408
+ <script>
409
+ function generateHTML(count) {
410
+ let html = '<section class="benchmark-container"><header><h2>Benchmark Results</h2></header><div class="items-grid">';
411
+ for (let i = 0; i < count; i++) {
412
+ html += `<article class="item-card"><header><h3 class="item-title">Item ${i}</h3></header><div class="item-content"><p class="item-description">Detailed description for item ${i} in the benchmark list.</p></div><footer class="item-footer"><span>Metadata for item ${i}</span></footer></article>`;
413
+ }
414
+ html += '</div></section>';
415
+ return html;
416
+ }
417
+
418
+ function generateVDOM(count) {
419
+ const items = [];
420
+ for (let i = 0; i < count; i++) {
421
+ items.push({
422
+ tag: 'article',
423
+ attributes: { class: 'item-card' },
424
+ children: [
425
+ { tag: 'header', children: [{ tag: 'h3', attributes: { class: 'item-title' }, children: [`Item ${i}`] }] },
426
+ { tag: 'div', attributes: { class: 'item-content' }, children: [{ tag: 'p', attributes: { class: 'item-description' }, children: [`Detailed description for item ${i} in the benchmark list.`] }] },
427
+ { tag: 'footer', attributes: { class: 'item-footer' }, children: [{ tag: 'span', children: [`Metadata for item ${i}`] }] }
428
+ ]
429
+ });
430
+ }
431
+ return {
432
+ tag: 'section',
433
+ attributes: { class: 'benchmark-container' },
434
+ children: [
435
+ { tag: 'header', children: [{ tag: 'h2', children: ['Benchmark Results'] }] },
436
+ { tag: 'div', attributes: { class: 'items-grid' }, children: items }
437
+ ]
438
+ };
439
+ }
440
+
441
+ // Dedicated helper to convert vDOM to DOM without Lightview
442
+ function vdomToDOM(vnode) {
443
+ if (typeof vnode !== 'object' || vnode === null) {
444
+ return document.createTextNode(vnode);
445
+ }
446
+
447
+ const el = document.createElement(vnode.tag);
448
+
449
+ const attrs = vnode.attributes;
450
+ if (attrs) {
451
+ for (const key in attrs) {
452
+ if (key === 'class') el.className = attrs[key];
453
+ else el.setAttribute(key, attrs[key]);
454
+ }
455
+ }
456
+
457
+ const children = vnode.children;
458
+ if (children) {
459
+ el.append(...children.map(vdomToDOM));
460
+ }
461
+
462
+ return el;
463
+ }
464
+
465
+ function generateODOM(count) {
466
+ const items = [];
467
+ for (let i = 0; i < count; i++) {
468
+ items.push({
469
+ article: {
470
+ class: 'item-card',
471
+ children: [
472
+ { header: { h3: { class: 'item-title', children: [`Item ${i}`] } } },
473
+ { div: { class: 'item-content', p: { class: 'item-description', children: [`Detailed description for item ${i} in the benchmark list.`] } } },
474
+ { footer: { class: 'item-footer', span: `Metadata for item ${i}` } }
475
+ ]
476
+ }
477
+ });
478
+ }
479
+ return {
480
+ section: {
481
+ class: 'benchmark-container',
482
+ children: [
483
+ { header: { h2: 'Benchmark Results' } },
484
+ { div: { class: 'items-grid', children: items } }
485
+ ]
486
+ }
487
+ };
488
+ }
489
+
490
+ function odomToDOM(onode) {
491
+ if (typeof onode !== 'object' || onode === null) {
492
+ return document.createTextNode(onode);
493
+ }
494
+
495
+ const tag = Object.keys(onode)[0];
496
+ const el = document.createElement(tag);
497
+ const content = onode[tag];
498
+
499
+ if (typeof content === 'object' && content !== null && !Array.isArray(content)) {
500
+ for (const key in content) {
501
+ if (key === 'children') {
502
+ el.append(...content.children.map(odomToDOM));
503
+ } else if (key === 'class') {
504
+ el.className = content[key];
505
+ } else if (typeof content[key] === 'object' && !Array.isArray(content[key])) {
506
+ // Nested shorthand tag
507
+ const child = {};
508
+ child[key] = content[key];
509
+ el.append(odomToDOM(child));
510
+ } else if (Array.isArray(content[key])) {
511
+ // Property with array value (e.g., children shorthand)
512
+ el.append(...content[key].map(odomToDOM));
513
+ } else {
514
+ el.setAttribute(key, content[key]);
515
+ }
516
+ }
517
+ } else {
518
+ if (Array.isArray(content)) {
519
+ el.append(...content.map(odomToDOM));
520
+ } else {
521
+ el.append(odomToDOM(content));
522
+ }
523
+ }
524
+
525
+ return el;
526
+ }
527
+
528
+ function odomToDOM_Juris(onode) {
529
+ if (typeof onode !== 'object' || onode === null) {
530
+ return document.createTextNode(onode);
531
+ }
532
+
533
+ // Create a temporary container for Juris to render into
534
+ const tempContainer = document.createElement('div');
535
+
536
+ // Temporarily disable all console methods to prevent Juris logging from affecting benchmark
537
+ const originalConsole = {
538
+ log: console.log,
539
+ dir: console.dir,
540
+ warn: console.warn,
541
+ error: console.error,
542
+ info: console.info,
543
+ debug: console.debug
544
+ };
545
+ const noop = () => { };
546
+ console.log = noop;
547
+ console.dir = noop;
548
+ console.warn = noop;
549
+ console.error = noop;
550
+ console.info = noop;
551
+ console.debug = noop;
552
+
553
+ try {
554
+ // Create a Juris instance with the oDOM structure as the layout
555
+ const jurisInstance = new Juris({
556
+ debug: false, // Disable logging for performance
557
+ layout: onode
558
+ });
559
+
560
+ // Render to the temporary container
561
+ jurisInstance.render(tempContainer);
562
+ } finally {
563
+ // Restore original console methods
564
+ console.log = originalConsole.log;
565
+ console.dir = originalConsole.dir;
566
+ console.warn = originalConsole.warn;
567
+ console.error = originalConsole.error;
568
+ console.info = originalConsole.info;
569
+ console.debug = originalConsole.debug;
570
+ }
571
+
572
+ // Return the first child (the actual rendered content)
573
+ return tempContainer.firstChild || tempContainer;
574
+ }
575
+
576
+
577
+ async function getGzipSize(str) {
578
+ try {
579
+ const stream = new Blob([str]).stream();
580
+ const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
581
+ const chunks = [];
582
+ for await (const chunk of compressedStream) {
583
+ chunks.push(chunk);
584
+ }
585
+ const blob = new Blob(chunks);
586
+ return blob.size;
587
+ } catch (e) {
588
+ console.error("CompressionStream not supported", e);
589
+ return 0;
590
+ }
591
+ }
592
+
593
+ const target = document.getElementById('benchmark-target');
594
+
595
+ function calculateStats(times) {
596
+ const min = Math.min(...times);
597
+ const max = Math.max(...times);
598
+ const avg = times.reduce((a, b) => a + b, 0) / times.length;
599
+ const std = Math.sqrt(times.map(x => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / times.length);
600
+ return { min, max, avg, std };
601
+ }
602
+
603
+ function updateUIResult(id, stats, size, gzipSize) {
604
+ const card = document.getElementById(`res-${id}`);
605
+ card.querySelector('.time').innerText = stats.avg.toFixed(2);
606
+ card.querySelector('.min').innerText = stats.min.toFixed(2);
607
+ card.querySelector('.avg').innerText = stats.avg.toFixed(2);
608
+ card.querySelector('.max').innerText = stats.max.toFixed(2);
609
+ card.querySelector('.std').innerText = stats.std.toFixed(2);
610
+ if (size !== undefined) {
611
+ card.querySelector('.size').innerText = (size / 1024).toFixed(2);
612
+ }
613
+ if (gzipSize !== undefined) {
614
+ card.querySelector('.gzip-size').innerText = (gzipSize / 1024).toFixed(2);
615
+ }
616
+
617
+ card.classList.add('active');
618
+
619
+ const fill = document.getElementById(`fill-${id}`);
620
+ const percentage = Math.min(100, (stats.avg / 200) * 100);
621
+ fill.style.width = `${percentage}%`;
622
+ }
623
+
624
+ function clearTarget() {
625
+ target.replaceChildren();
626
+ target.innerHTML = '<div class="p-8 text-center text-slate-400 italic">Target is empty.</div>';
627
+ ['innerHTML', 'DOMParser', 'vDOM', 'RawVDOM', 'RawoDOM', 'JurisODOM', 'TaggedScript', 'BauTaggedScript'].forEach(id => {
628
+ const card = document.getElementById(`res-${id}`);
629
+ card.classList.remove('active');
630
+ card.querySelector('.time').innerText = '--';
631
+ card.querySelector('.min').innerText = '--';
632
+ card.querySelector('.avg').innerText = '--';
633
+ card.querySelector('.max').innerText = '--';
634
+ card.querySelector('.std').innerText = '--';
635
+ card.querySelector('.size').innerText = '--';
636
+ card.querySelector('.gzip-size').innerText = '--';
637
+ const fillEl = document.getElementById(`fill-${id}`);
638
+ if (fillEl) fillEl.style.width = '0%';
639
+ });
640
+ }
641
+
642
+ async function runCycle(type, data, count) {
643
+ // 2. Settle phase (allow GC to run and memory pressure to stabilize)
644
+ if (window.gc) window.gc();
645
+ // Give 100ms for browser housekeeping between cycles
646
+ await new Promise(r => setTimeout(r, 100));
647
+
648
+ let time = 0;
649
+ if (type === 'innerHTML') {
650
+ const start = performance.now();
651
+ target.innerHTML = data;
652
+ time = performance.now() - start;
653
+ } else if (type === 'DOMParser') {
654
+ const parser = new DOMParser();
655
+ const start = performance.now();
656
+ const doc = parser.parseFromString(data, 'text/html');
657
+ target.replaceChildren(...doc.body.childNodes);
658
+ time = performance.now() - start;
659
+ } else if (type === 'vDOM') {
660
+ const start = performance.now();
661
+ const json = JSON.parse(data);
662
+ const { domEl } = Lightview.element(json.tag, json.attributes, json.children);
663
+ target.replaceChildren(domEl);
664
+ time = performance.now() - start;
665
+ } else if (type === 'RawVDOM') {
666
+ const start = performance.now();
667
+ const json = JSON.parse(data);
668
+ const domEl = vdomToDOM(json);
669
+ target.replaceChildren(domEl);
670
+ time = performance.now() - start;
671
+ } else if (type === 'RawoDOM') {
672
+ const start = performance.now();
673
+ const json = JSON.parse(data);
674
+ const domEl = odomToDOM(json);
675
+ target.replaceChildren(domEl);
676
+ time = performance.now() - start;
677
+ } else if (type === 'JurisODOM') {
678
+ const start = performance.now();
679
+ const json = JSON.parse(data);
680
+ const domEl = odomToDOM_Juris(json);
681
+ target.replaceChildren(domEl);
682
+ time = performance.now() - start;
683
+ } else if (type === 'TaggedScript') {
684
+ const existing = document.getElementById('tagged-script-tag');
685
+ if (existing) existing.remove();
686
+
687
+ await new Promise(resolve => {
688
+ window.__resolveTaggedBenchmark = resolve;
689
+ const script = document.createElement('script');
690
+ script.id = 'tagged-script-tag';
691
+ script.src = `./benchmarks/tagged-fragment.js?count=${count}&t=${Date.now()}`;
692
+ document.body.appendChild(script);
693
+ });
694
+ time = window.__taggedBenchmarkTime;
695
+ } else if (type === 'BauTaggedScript') {
696
+ const existing = document.getElementById('bau-tagged-script-tag');
697
+ if (existing) existing.remove();
698
+
699
+ await new Promise(resolve => {
700
+ window.__resolveBauTaggedBenchmark = resolve;
701
+ const script = document.createElement('script');
702
+ script.type = 'module';
703
+ script.id = 'bau-tagged-script-tag';
704
+ script.src = `./benchmarks/bau-tagged-fragment.js?count=${count}&t=${Date.now()}`;
705
+ document.body.appendChild(script);
706
+ });
707
+ time = window.__bauTaggedBenchmarkTime;
708
+ }
709
+ return time;
710
+ }
711
+
712
+ async function runBenchmark(type, preGeneratedData = null) {
713
+ const count = parseInt(document.getElementById('row-count').value);
714
+ const cycles = parseInt(document.getElementById('cycle-count').value) || 1;
715
+ const times = [];
716
+
717
+ let data = preGeneratedData;
718
+
719
+ // 1. Preparation phase (outside of timing)
720
+ if (!data && type !== 'TaggedScript' && type !== 'BauTaggedScript') {
721
+ if (type === 'innerHTML' || type === 'DOMParser') data = generateHTML(count);
722
+ if (type === 'vDOM' || type === 'RawVDOM') data = generateVDOM(count);
723
+ if (type === 'RawoDOM' || type === 'JurisODOM') data = generateODOM(count);
724
+ }
725
+
726
+ console.log(`Starting ${cycles} cycles for ${type}...`);
727
+ const stringData = type === 'TaggedScript' ? '' : (typeof data === 'string' ? data : JSON.stringify(data));
728
+ const size = stringData.length;
729
+ const gzipSize = size > 0 ? await getGzipSize(stringData) : 0;
730
+
731
+ for (let i = 0; i < cycles; i++) {
732
+ const time = await runCycle(type, stringData, count);
733
+ times.push(time);
734
+ }
735
+
736
+ const stats = calculateStats(times);
737
+ updateUIResult(type, stats, size, gzipSize);
738
+ return stats;
739
+ }
740
+
741
+ async function runAll() {
742
+ clearTarget();
743
+ const count = parseInt(document.getElementById('row-count').value);
744
+
745
+ console.log("Pre-generating data for Run All...");
746
+ const htmlData = generateHTML(count);
747
+ const vdomData = generateVDOM(count);
748
+ const odomData = generateODOM(count);
749
+
750
+ const configs = [
751
+ { id: 'innerHTML', data: htmlData },
752
+ { id: 'DOMParser', data: htmlData },
753
+ { id: 'RawVDOM', data: vdomData },
754
+ { id: 'RawoDOM', data: odomData },
755
+ { id: 'vDOM', data: vdomData },
756
+ { id: 'JurisODOM', data: odomData },
757
+ { id: 'TaggedScript', data: null },
758
+ { id: 'BauTaggedScript', data: null }
759
+ ];
760
+
761
+ for (const config of configs) {
762
+ await runBenchmark(config.id, config.data);
763
+ // Pause between benchmarks to ensure clean state
764
+ await new Promise(r => setTimeout(r, 500));
765
+ }
766
+ console.log("Run All completed.");
767
+ }
768
+ </script>
769
+ </body>
770
+
771
+ </html>