lightview 2.3.8 → 2.4.4
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/.gemini/CODE_ANALYSIS_AND_IMPROVEMENT_PLAN.md +56 -0
- package/AI-GUIDANCE.md +259 -0
- package/README.md +35 -0
- package/components/data-display/diff.js +36 -4
- package/docs/api/hypermedia.html +75 -5
- package/docs/api/index.html +3 -3
- package/docs/api/nav.html +0 -16
- package/docs/articles/html-vs-json-partials.md +102 -0
- package/docs/articles/lightview-vs-htmx.md +610 -0
- package/docs/benchmarks/tagged-fragment.js +36 -0
- package/docs/components/chart.html +157 -210
- package/docs/components/component-nav.html +1 -1
- package/docs/components/diff.html +33 -21
- package/docs/components/gallery.html +107 -4
- package/docs/components/index.css +18 -3
- package/docs/components/index.html +20 -9
- package/docs/dom-benchmark.html +644 -0
- package/docs/getting-started/index.html +2 -2
- package/docs/hypermedia/index.html +391 -0
- package/docs/hypermedia/nav.html +17 -0
- package/docs/index.html +128 -18
- package/index.html +59 -10
- package/lightview-all.js +223 -67
- package/lightview-cdom.js +1 -2
- package/lightview-x.js +144 -13
- package/lightview.js +85 -277
- package/package.json +2 -2
- package/src/lightview-cdom.js +1 -5
- package/src/lightview-x.js +158 -27
- package/src/lightview.js +94 -60
- package/docs/articles/calculator-no-javascript-hackernoon.md +0 -283
- package/docs/articles/calculator-no-javascript.md +0 -290
- package/docs/articles/part1-reference.md +0 -236
- package/lightview.js.bak +0 -1
- package/test-xpath.html +0 -63
- package/test_error.txt +0 -0
- package/test_output.txt +0 -0
- package/test_output_full.txt +0 -0
|
@@ -0,0 +1,644 @@
|
|
|
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('TaggedScript')"
|
|
165
|
+
class="btn btn-outline btn-benchmark border-slate-300">Run Lightview Tagged
|
|
166
|
+
Script</button>
|
|
167
|
+
<div class="divider"></div>
|
|
168
|
+
<button onclick="runAll()" class="btn btn-primary text-white">Run All Benchmarks</button>
|
|
169
|
+
<button onclick="clearTarget()" class="btn btn-ghost">Clear Target</button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- Results -->
|
|
176
|
+
<div class="lg:col-span-2 space-y-4">
|
|
177
|
+
<div id="results-container" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
178
|
+
<!-- Template for result cards -->
|
|
179
|
+
<div class="result-card" id="res-innerHTML">
|
|
180
|
+
<div class="flex justify-between items-start mb-2">
|
|
181
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">innerHTML</h3>
|
|
182
|
+
<div class="flex flex-col items-end gap-1">
|
|
183
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
184
|
+
</div>
|
|
185
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
186
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
187
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
191
|
+
|
|
192
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
193
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
194
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
195
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
196
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="progress-bar-custom">
|
|
200
|
+
<div class="progress-fill" id="fill-innerHTML"></div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="result-card" id="res-DOMParser">
|
|
205
|
+
<div class="flex justify-between items-start mb-2">
|
|
206
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">DOMParser</h3>
|
|
207
|
+
<div class="flex flex-col items-end gap-1">
|
|
208
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
209
|
+
</div>
|
|
210
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
211
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
212
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
216
|
+
|
|
217
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
218
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
219
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
220
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
221
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="progress-bar-custom">
|
|
225
|
+
<div class="progress-fill" id="fill-DOMParser"></div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div class="result-card" id="res-RawVDOM">
|
|
230
|
+
<div class="flex justify-between items-start mb-2">
|
|
231
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Raw vDOM (No Lib)
|
|
232
|
+
</h3>
|
|
233
|
+
<div class="flex flex-col items-end gap-1">
|
|
234
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
235
|
+
</div>
|
|
236
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
237
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
238
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
242
|
+
|
|
243
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
244
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
245
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
246
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
247
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="progress-bar-custom">
|
|
251
|
+
<div class="progress-fill" id="fill-RawVDOM"></div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="result-card" id="res-RawoDOM">
|
|
256
|
+
<div class="flex justify-between items-start mb-2">
|
|
257
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Raw oDOM (No Lib)
|
|
258
|
+
</h3>
|
|
259
|
+
<div class="flex flex-col items-end gap-1">
|
|
260
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
261
|
+
</div>
|
|
262
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
263
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
264
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
268
|
+
|
|
269
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
270
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
271
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
272
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
273
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div class="progress-bar-custom">
|
|
277
|
+
<div class="progress-fill" id="fill-RawoDOM"></div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div class="result-card" id="res-vDOM">
|
|
282
|
+
<div class="flex justify-between items-start mb-2">
|
|
283
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Lightview vDOM</h3>
|
|
284
|
+
<div class="flex flex-col items-end gap-1">
|
|
285
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
286
|
+
</div>
|
|
287
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
288
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
289
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
293
|
+
|
|
294
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
295
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
296
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
297
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
298
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<div class="progress-bar-custom">
|
|
302
|
+
<div class="progress-fill" id="fill-vDOM"></div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="result-card" id="res-TaggedScript">
|
|
307
|
+
<div class="flex justify-between items-start mb-2">
|
|
308
|
+
<h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Lightview Tagged
|
|
309
|
+
Script</h3>
|
|
310
|
+
<div class="flex flex-col items-end gap-1">
|
|
311
|
+
<div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
|
|
312
|
+
</div>
|
|
313
|
+
<div class="badge badge-accent badge-xs text-white opacity-70"><span
|
|
314
|
+
class="gzip-size">--</span> KB gzip</div>
|
|
315
|
+
<div class="badge badge-ghost badge-sm opacity-50">ms</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
|
|
319
|
+
|
|
320
|
+
<div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
|
|
321
|
+
<div>Min <span class="min block text-slate-600">--</span></div>
|
|
322
|
+
<div>Avg <span class="avg block text-slate-600">--</span></div>
|
|
323
|
+
<div>Max <span class="max block text-slate-600">--</span></div>
|
|
324
|
+
<div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<div class="progress-bar-custom">
|
|
328
|
+
<div class="progress-fill" id="fill-TaggedScript"></div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<div class="card bg-white shadow-sm border border-slate-200">
|
|
334
|
+
<div class="card-body">
|
|
335
|
+
<h2 class="card-title text-slate-800">Benchmark Target</h2>
|
|
336
|
+
<p class="text-sm text-slate-500 mb-2">Live DOM update area:</p>
|
|
337
|
+
<div id="benchmark-target">
|
|
338
|
+
<div class="p-8 text-center text-slate-400 italic">Target is empty. Run a benchmark to
|
|
339
|
+
populate.</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<!-- Core Libraries -->
|
|
348
|
+
<script src="/lightview.js"></script>
|
|
349
|
+
<script src="/lightview-x.js"></script>
|
|
350
|
+
|
|
351
|
+
<script>
|
|
352
|
+
function generateHTML(count) {
|
|
353
|
+
let html = '<section class="benchmark-container"><header><h2>Benchmark Results</h2></header><div class="items-grid">';
|
|
354
|
+
for (let i = 0; i < count; i++) {
|
|
355
|
+
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>`;
|
|
356
|
+
}
|
|
357
|
+
html += '</div></section>';
|
|
358
|
+
return html;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function generateVDOM(count) {
|
|
362
|
+
const items = [];
|
|
363
|
+
for (let i = 0; i < count; i++) {
|
|
364
|
+
items.push({
|
|
365
|
+
tag: 'article',
|
|
366
|
+
attributes: { class: 'item-card' },
|
|
367
|
+
children: [
|
|
368
|
+
{ tag: 'header', children: [{ tag: 'h3', attributes: { class: 'item-title' }, children: [`Item ${i}`] }] },
|
|
369
|
+
{ tag: 'div', attributes: { class: 'item-content' }, children: [{ tag: 'p', attributes: { class: 'item-description' }, children: [`Detailed description for item ${i} in the benchmark list.`] }] },
|
|
370
|
+
{ tag: 'footer', attributes: { class: 'item-footer' }, children: [{ tag: 'span', children: [`Metadata for item ${i}`] }] }
|
|
371
|
+
]
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
tag: 'section',
|
|
376
|
+
attributes: { class: 'benchmark-container' },
|
|
377
|
+
children: [
|
|
378
|
+
{ tag: 'header', children: [{ tag: 'h2', children: ['Benchmark Results'] }] },
|
|
379
|
+
{ tag: 'div', attributes: { class: 'items-grid' }, children: items }
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Dedicated helper to convert vDOM to DOM without Lightview
|
|
385
|
+
function vdomToDOM(vnode) {
|
|
386
|
+
if (typeof vnode !== 'object' || vnode === null) {
|
|
387
|
+
return document.createTextNode(vnode);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const el = document.createElement(vnode.tag);
|
|
391
|
+
|
|
392
|
+
const attrs = vnode.attributes;
|
|
393
|
+
if (attrs) {
|
|
394
|
+
for (const key in attrs) {
|
|
395
|
+
if (key === 'class') el.className = attrs[key];
|
|
396
|
+
else el.setAttribute(key, attrs[key]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const children = vnode.children;
|
|
401
|
+
if (children) {
|
|
402
|
+
el.append(...children.map(vdomToDOM));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return el;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function generateODOM(count) {
|
|
409
|
+
const items = [];
|
|
410
|
+
for (let i = 0; i < count; i++) {
|
|
411
|
+
items.push({
|
|
412
|
+
article: {
|
|
413
|
+
class: 'item-card',
|
|
414
|
+
children: [
|
|
415
|
+
{ header: { h3: { class: 'item-title', children: [`Item ${i}`] } } },
|
|
416
|
+
{ div: { class: 'item-content', p: { class: 'item-description', children: [`Detailed description for item ${i} in the benchmark list.`] } } },
|
|
417
|
+
{ footer: { class: 'item-footer', span: `Metadata for item ${i}` } }
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
section: {
|
|
424
|
+
class: 'benchmark-container',
|
|
425
|
+
children: [
|
|
426
|
+
{ header: { h2: 'Benchmark Results' } },
|
|
427
|
+
{ div: { class: 'items-grid', children: items } }
|
|
428
|
+
]
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function odomToDOM(onode) {
|
|
434
|
+
if (typeof onode !== 'object' || onode === null) {
|
|
435
|
+
return document.createTextNode(onode);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const tag = Object.keys(onode)[0];
|
|
439
|
+
const el = document.createElement(tag);
|
|
440
|
+
const content = onode[tag];
|
|
441
|
+
|
|
442
|
+
if (typeof content === 'object' && content !== null && !Array.isArray(content)) {
|
|
443
|
+
for (const key in content) {
|
|
444
|
+
if (key === 'children') {
|
|
445
|
+
el.append(...content.children.map(odomToDOM));
|
|
446
|
+
} else if (key === 'class') {
|
|
447
|
+
el.className = content[key];
|
|
448
|
+
} else if (typeof content[key] === 'object' && !Array.isArray(content[key])) {
|
|
449
|
+
// Nested shorthand tag
|
|
450
|
+
const child = {};
|
|
451
|
+
child[key] = content[key];
|
|
452
|
+
el.append(odomToDOM(child));
|
|
453
|
+
} else if (Array.isArray(content[key])) {
|
|
454
|
+
// Property with array value (e.g., children shorthand)
|
|
455
|
+
el.append(...content[key].map(odomToDOM));
|
|
456
|
+
} else {
|
|
457
|
+
el.setAttribute(key, content[key]);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
if (Array.isArray(content)) {
|
|
462
|
+
el.append(...content.map(odomToDOM));
|
|
463
|
+
} else {
|
|
464
|
+
el.append(odomToDOM(content));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return el;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function getGzipSize(str) {
|
|
472
|
+
try {
|
|
473
|
+
const stream = new Blob([str]).stream();
|
|
474
|
+
const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
|
|
475
|
+
const chunks = [];
|
|
476
|
+
for await (const chunk of compressedStream) {
|
|
477
|
+
chunks.push(chunk);
|
|
478
|
+
}
|
|
479
|
+
const blob = new Blob(chunks);
|
|
480
|
+
return blob.size;
|
|
481
|
+
} catch (e) {
|
|
482
|
+
console.error("CompressionStream not supported", e);
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const target = document.getElementById('benchmark-target');
|
|
488
|
+
|
|
489
|
+
function calculateStats(times) {
|
|
490
|
+
const min = Math.min(...times);
|
|
491
|
+
const max = Math.max(...times);
|
|
492
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
493
|
+
const std = Math.sqrt(times.map(x => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / times.length);
|
|
494
|
+
return { min, max, avg, std };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function updateUIResult(id, stats, size, gzipSize) {
|
|
498
|
+
const card = document.getElementById(`res-${id}`);
|
|
499
|
+
card.querySelector('.time').innerText = stats.avg.toFixed(2);
|
|
500
|
+
card.querySelector('.min').innerText = stats.min.toFixed(2);
|
|
501
|
+
card.querySelector('.avg').innerText = stats.avg.toFixed(2);
|
|
502
|
+
card.querySelector('.max').innerText = stats.max.toFixed(2);
|
|
503
|
+
card.querySelector('.std').innerText = stats.std.toFixed(2);
|
|
504
|
+
if (size !== undefined) {
|
|
505
|
+
card.querySelector('.size').innerText = (size / 1024).toFixed(2);
|
|
506
|
+
}
|
|
507
|
+
if (gzipSize !== undefined) {
|
|
508
|
+
card.querySelector('.gzip-size').innerText = (gzipSize / 1024).toFixed(2);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
card.classList.add('active');
|
|
512
|
+
|
|
513
|
+
const fill = document.getElementById(`fill-${id}`);
|
|
514
|
+
const percentage = Math.min(100, (stats.avg / 200) * 100);
|
|
515
|
+
fill.style.width = `${percentage}%`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function clearTarget() {
|
|
519
|
+
target.replaceChildren();
|
|
520
|
+
target.innerHTML = '<div class="p-8 text-center text-slate-400 italic">Target is empty.</div>';
|
|
521
|
+
['innerHTML', 'DOMParser', 'vDOM', 'RawVDOM', 'RawoDOM', 'TaggedScript'].forEach(id => {
|
|
522
|
+
const card = document.getElementById(`res-${id}`);
|
|
523
|
+
card.classList.remove('active');
|
|
524
|
+
card.querySelector('.time').innerText = '--';
|
|
525
|
+
card.querySelector('.min').innerText = '--';
|
|
526
|
+
card.querySelector('.avg').innerText = '--';
|
|
527
|
+
card.querySelector('.max').innerText = '--';
|
|
528
|
+
card.querySelector('.std').innerText = '--';
|
|
529
|
+
card.querySelector('.size').innerText = '--';
|
|
530
|
+
card.querySelector('.gzip-size').innerText = '--';
|
|
531
|
+
const fillEl = document.getElementById(`fill-${id}`);
|
|
532
|
+
if (fillEl) fillEl.style.width = '0%';
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function runCycle(type, data, count) {
|
|
537
|
+
// 2. Settle phase (allow GC to run and memory pressure to stabilize)
|
|
538
|
+
if (window.gc) window.gc();
|
|
539
|
+
// Give 100ms for browser housekeeping between cycles
|
|
540
|
+
await new Promise(r => setTimeout(r, 100));
|
|
541
|
+
|
|
542
|
+
let time = 0;
|
|
543
|
+
if (type === 'innerHTML') {
|
|
544
|
+
const start = performance.now();
|
|
545
|
+
target.innerHTML = data;
|
|
546
|
+
time = performance.now() - start;
|
|
547
|
+
} else if (type === 'DOMParser') {
|
|
548
|
+
const parser = new DOMParser();
|
|
549
|
+
const start = performance.now();
|
|
550
|
+
const doc = parser.parseFromString(data, 'text/html');
|
|
551
|
+
target.replaceChildren(...doc.body.childNodes);
|
|
552
|
+
time = performance.now() - start;
|
|
553
|
+
} else if (type === 'vDOM') {
|
|
554
|
+
const start = performance.now();
|
|
555
|
+
const json = JSON.parse(data);
|
|
556
|
+
const { domEl } = Lightview.element(json.tag, json.attributes, json.children);
|
|
557
|
+
target.replaceChildren(domEl);
|
|
558
|
+
time = performance.now() - start;
|
|
559
|
+
} else if (type === 'RawVDOM') {
|
|
560
|
+
const start = performance.now();
|
|
561
|
+
const json = JSON.parse(data);
|
|
562
|
+
const domEl = vdomToDOM(json);
|
|
563
|
+
target.replaceChildren(domEl);
|
|
564
|
+
time = performance.now() - start;
|
|
565
|
+
} else if (type === 'RawoDOM') {
|
|
566
|
+
const start = performance.now();
|
|
567
|
+
const json = JSON.parse(data);
|
|
568
|
+
const domEl = odomToDOM(json);
|
|
569
|
+
target.replaceChildren(domEl);
|
|
570
|
+
time = performance.now() - start;
|
|
571
|
+
} else if (type === 'TaggedScript') {
|
|
572
|
+
const existing = document.getElementById('tagged-script-tag');
|
|
573
|
+
if (existing) existing.remove();
|
|
574
|
+
|
|
575
|
+
await new Promise(resolve => {
|
|
576
|
+
window.__resolveTaggedBenchmark = resolve;
|
|
577
|
+
const script = document.createElement('script');
|
|
578
|
+
script.id = 'tagged-script-tag';
|
|
579
|
+
script.src = `./benchmarks/tagged-fragment.js?count=${count}&t=${Date.now()}`;
|
|
580
|
+
document.body.appendChild(script);
|
|
581
|
+
});
|
|
582
|
+
time = window.__taggedBenchmarkTime;
|
|
583
|
+
}
|
|
584
|
+
return time;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function runBenchmark(type, preGeneratedData = null) {
|
|
588
|
+
const count = parseInt(document.getElementById('row-count').value);
|
|
589
|
+
const cycles = parseInt(document.getElementById('cycle-count').value) || 1;
|
|
590
|
+
const times = [];
|
|
591
|
+
|
|
592
|
+
let data = preGeneratedData;
|
|
593
|
+
|
|
594
|
+
// 1. Preparation phase (outside of timing)
|
|
595
|
+
if (!data && type !== 'TaggedScript') {
|
|
596
|
+
if (type === 'innerHTML' || type === 'DOMParser') data = generateHTML(count);
|
|
597
|
+
if (type === 'vDOM' || type === 'RawVDOM') data = generateVDOM(count);
|
|
598
|
+
if (type === 'RawoDOM') data = generateODOM(count);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
console.log(`Starting ${cycles} cycles for ${type}...`);
|
|
602
|
+
const stringData = type === 'TaggedScript' ? '' : (typeof data === 'string' ? data : JSON.stringify(data));
|
|
603
|
+
const size = stringData.length;
|
|
604
|
+
const gzipSize = size > 0 ? await getGzipSize(stringData) : 0;
|
|
605
|
+
|
|
606
|
+
for (let i = 0; i < cycles; i++) {
|
|
607
|
+
const time = await runCycle(type, stringData, count);
|
|
608
|
+
times.push(time);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const stats = calculateStats(times);
|
|
612
|
+
updateUIResult(type, stats, size, gzipSize);
|
|
613
|
+
return stats;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function runAll() {
|
|
617
|
+
clearTarget();
|
|
618
|
+
const count = parseInt(document.getElementById('row-count').value);
|
|
619
|
+
|
|
620
|
+
console.log("Pre-generating data for Run All...");
|
|
621
|
+
const htmlData = generateHTML(count);
|
|
622
|
+
const vdomData = generateVDOM(count);
|
|
623
|
+
const odomData = generateODOM(count);
|
|
624
|
+
|
|
625
|
+
const configs = [
|
|
626
|
+
{ id: 'innerHTML', data: htmlData },
|
|
627
|
+
{ id: 'DOMParser', data: htmlData },
|
|
628
|
+
{ id: 'RawVDOM', data: vdomData },
|
|
629
|
+
{ id: 'RawoDOM', data: odomData },
|
|
630
|
+
{ id: 'vDOM', data: vdomData },
|
|
631
|
+
{ id: 'TaggedScript', data: null }
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
for (const config of configs) {
|
|
635
|
+
await runBenchmark(config.id, config.data);
|
|
636
|
+
// Pause between benchmarks to ensure clean state
|
|
637
|
+
await new Promise(r => setTimeout(r, 500));
|
|
638
|
+
}
|
|
639
|
+
console.log("Run All completed.");
|
|
640
|
+
}
|
|
641
|
+
</script>
|
|
642
|
+
</body>
|
|
643
|
+
|
|
644
|
+
</html>
|
|
@@ -141,7 +141,7 @@ const { div, h1, p, style } = tags;
|
|
|
141
141
|
// These functions return Lightview elements (access raw DOM via .domEl)
|
|
142
142
|
const App = div({ class: 'hero' },
|
|
143
143
|
h1('Welcome to Lightview'),
|
|
144
|
-
p('Lightview is a library for
|
|
144
|
+
p('Lightview is a library for humans and LLMs to build modern web interfaces.')
|
|
145
145
|
);
|
|
146
146
|
|
|
147
147
|
// 2. The $ Function for Selections & Content
|
|
@@ -477,7 +477,7 @@ $('#app').content(App);`,
|
|
|
477
477
|
$('#app').content(App);`,
|
|
478
478
|
concepts: `
|
|
479
479
|
<h3 style="margin-top: 0; color: var(--site-primary);">Step 4: Hypermedia + Multiple Formats</h3>
|
|
480
|
-
<p>Lightview's <
|
|
480
|
+
<p>Lightview's <a href="/docs/hypermedia/">hypermedia</a> feature allows your application to load partial content dynamically from various file formats.</p>
|
|
481
481
|
|
|
482
482
|
<h4>Key Concepts:</h4>
|
|
483
483
|
<ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
|