xxdom.js Benchmark Suite

Reproducible, client-side benchmarks measuring xxdom.js performance. All tests run in your browser right now. ← Back to community site

Methodology: Each benchmark uses a fresh iframe with xxdom.js loaded from scratch. Timings use performance.now() for sub-millisecond precision. Multiple iterations report the median to reduce noise. Tests follow the js-framework-benchmark methodology where applicable. Run in a fresh tab with devtools closed for best results.

Ready. Click "Run All Benchmarks" to start.

1. Bundle Size

MetricValueNotes
Source size39.2 KB (1,373 LOC)Single file, zero dependencies
Minified13.9 KBterser with compress + mangle
Gzipped5.2 KBTransfer size over network

2. Initialization

Time to parse JS, scan DOM, and complete first render.

TestMedianMinMaxIterations
Parse + init + first render (minimal template)

3. Create Rows

Create N rows from scratch — the standard framework benchmark. Uses xx-for with xx-text and xx-class.

TestMedianMinMaxOps/sec
Create 100 rows
Create 1,000 rows
Create 10,000 rows

4. Update Rows (Partial)

Update every 10th row label in a 1,000-row list. Measures incremental re-render cost.

TestMedianMinMax
Update every 10th row (of 1,000)

5. Replace All Rows

Replace 1,000 rows with 1,000 new rows. Full DOM reconciliation.

TestMedianMinMax
Replace 1,000 rows

6. Select Row

Toggle selected class on one row in a 1,000-row list. Minimal-change re-render.

TestMedianMinMax
Select row (class toggle, 1,000 rows)

7. Swap Rows

Swap 2nd and 999th row. Compares identity-diff vs keyed-diff (xx-key).

TestMedianMinMax
Swap rows (identity diff)
Swap rows (keyed, xx-key)

8. Remove Row

TestMedianMinMax
Remove row from middle (1,000 rows)

9. Clear All

TestMedianMinMax
Clear 1,000 rows

10. Expression Throughput

Re-render 1,000 interpolation expressions on existing DOM.

TestMedianMinMax
1,000 mustache expressions

Raw Log

'); doc.close(); // Wait for xx to init function check() { if (iframe.contentWindow.xx && iframe.contentWindow.xx.render) { resolve({ win: iframe.contentWindow, doc: iframe.contentDocument, frame: iframe }); } else { setTimeout(check, 10); } } check(); }); } function destroyFrame(ctx) { if (ctx.frame && ctx.frame.parentNode) ctx.frame.parentNode.removeChild(ctx.frame); } // Data generator (same as js-framework-benchmark) var adjectives = ["pretty","large","big","small","tall","short","long","handsome","plain","quaint","clean","elegant","easy","angry","crazy","helpful","mushy","odd","unsightly","adorable","important","inexpensive","cheap","expensive","fancy"]; var colours = ["red","yellow","blue","green","pink","brown","purple","brown","white","black","orange"]; var nouns = ["table","chair","house","bbq","desk","car","pony","cookie","sandwich","burger","pizza","mouse","keyboard"]; var nextId = 1; function buildData(count) { var data = []; for (var i = 0; i < count; i++) { data.push({ id: nextId++, label: adjectives[Math.random()*adjectives.length|0]+' '+colours[Math.random()*colours.length|0]+' '+nouns[Math.random()*nouns.length|0] }); } return data; } function reportTimes(prefix, id, times) { var med = median(times); var mn = Math.min.apply(null, times); var mx = Math.max.apply(null, times); setEl(id+'-med', fmtMs(med)); setEl(id+'-min', fmtMs(mn)); setEl(id+'-max', fmtMs(mx)); colorEl(id+'-med', med, 50, 200); log(prefix+': median='+fmtMs(med)+' min='+fmtMs(mn)+' max='+fmtMs(mx)+' ('+times.length+' runs)'); return med; } // ===================================================================== // Benchmark implementations // ===================================================================== // Bench: Init — measure time from template injection to rendered DOM async function benchInit() { log('\n=== INITIALIZATION ==='); var times = []; for (var i = 0; i < 20; i++) { var iframe = document.createElement('iframe'); iframe.style.cssText = 'position:absolute;left:-9999px;top:0;width:800px;height:600px;border:0;'; document.body.appendChild(iframe); var doc = iframe.contentDocument; var html = '
'; var t0 = performance.now(); doc.open(); doc.write(html + '<' + 'script src="../src/xx.js">'); doc.close(); // Wait for xx to render await new Promise(function(resolve) { (function poll() { if (iframe.contentWindow.xx && iframe.contentDocument.querySelector('span') && iframe.contentDocument.querySelector('span').textContent === '42') resolve(); else setTimeout(poll, 1); })(); }); var t1 = performance.now(); times.push(t1 - t0); iframe.parentNode.removeChild(iframe); } var med = reportTimes('Init+Parse+Render', 'r-init', times); setEl('r-init-n', times.length + ''); colorEl('r-init-med', med, 20, 50); } // Generic list benchmark: creates iframe, sets up rows, runs fn multiple times async function benchList(name, id, count, iters, runFn) { log('\n=== ' + name + ' ==='); var tmpl = '
'; var ctx = await createBenchFrame(tmpl); var win = ctx.win; // Expose data on iframe's global win.rows = []; win.sel = 0; win.xx.render(); var times = []; for (var i = 0; i < iters; i++) { // Setup: fresh 1000 rows if needed by runFn runFn.setup(win, count); win.xx.render(); var t0 = win.performance.now(); runFn.run(win, count, i); win.xx.render(); var t1 = win.performance.now(); times.push(t1 - t0); } destroyFrame(ctx); return reportTimes(name, id, times); } // Keyed list benchmark async function benchListKeyed(name, id, count, iters, runFn) { log('\n=== ' + name + ' ==='); var tmpl = '
'; var ctx = await createBenchFrame(tmpl); var win = ctx.win; win.rows = []; win.sel = 0; win.xx.render(); var times = []; for (var i = 0; i < iters; i++) { runFn.setup(win, count); win.xx.render(); var t0 = win.performance.now(); runFn.run(win, count, i); win.xx.render(); var t1 = win.performance.now(); times.push(t1 - t0); } destroyFrame(ctx); return reportTimes(name, id, times); } function noop() {} // ===================================================================== // Run All // ===================================================================== async function runAllBenchmarks() { benchLog.textContent = ''; setStatus('Running benchmarks...'); log('xxdom.js Benchmark Suite'); log('========================'); log('User Agent: ' + navigator.userAgent.substring(0, 120)); log('Date: ' + new Date().toISOString()); log(''); // 1. Init setStatus('1/9: Initialization...'); await benchInit(); // 2. Create 100 setStatus('2/9: Create 100 rows...'); var c100 = await benchList('Create 100 rows', 'r-c100', 100, 10, { setup: function(win) { win.rows = []; }, run: function(win, n) { win.rows = buildData(n); } }); setEl('r-c100-ops', Math.round(1000/c100) + ' ops/s'); // 3. Create 1000 setStatus('3/9: Create 1,000 rows...'); var c1k = await benchList('Create 1,000 rows', 'r-c1k', 1000, 5, { setup: function(win) { win.rows = []; }, run: function(win, n) { win.rows = buildData(n); } }); setEl('r-c1k-ops', Math.round(1000/c1k) + ' ops/s'); // 4. Create 10000 setStatus('4/9: Create 10,000 rows...'); var c10k = await benchList('Create 10,000 rows', 'r-c10k', 10000, 3, { setup: function(win) { win.rows = []; }, run: function(win, n) { win.rows = buildData(n); } }); setEl('r-c10k-ops', Math.round(1000/c10k) + ' ops/s'); // 5. Update every 10th setStatus('5/9: Update partial...'); await benchList('Update every 10th row', 'r-upd', 1000, 10, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { for (var i = 0; i < win.rows.length; i += 10) win.rows[i].label += ' !!!'; } }); // 6. Replace setStatus('6/9: Replace all rows...'); await benchList('Replace 1,000 rows', 'r-repl', 1000, 5, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { win.rows = buildData(1000); } }); // 7. Select setStatus('7/9: Select row...'); await benchList('Select row', 'r-sel', 1000, 20, { setup: function(win) { win.rows = buildData(1000); win.sel = 0; }, run: function(win, n, i) { win.sel = win.rows[i % win.rows.length].id; } }); // 8. Swap (identity + keyed) setStatus('8/9: Swap rows...'); await benchList('Swap rows (identity diff)', 'r-swap', 1000, 10, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { var t = win.rows[1]; win.rows[1] = win.rows[998]; win.rows[998] = t; } }); await benchListKeyed('Swap rows (keyed)', 'r-swapk', 1000, 10, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { var t = win.rows[1]; win.rows[1] = win.rows[998]; win.rows[998] = t; } }); // 9. Remove + Clear + Expressions setStatus('9/9: Remove, Clear, Expressions...'); await benchList('Remove row from middle', 'r-rem', 1000, 10, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { win.rows.splice(500, 1); } }); await benchList('Clear 1,000 rows', 'r-clear', 1000, 10, { setup: function(win) { win.rows = buildData(1000); }, run: function(win) { win.rows = []; } }); // Expression throughput log('\n=== EXPRESSION THROUGHPUT ==='); var exprHtml = '
'; for (var i = 0; i < 1000; i++) exprHtml += '{{Math.round(' + i + ' * 1.5)}}'; exprHtml += '
'; var ectx = await createBenchFrame(exprHtml); ectx.win.xx.render(); // first render to compile expressions var etimes = []; for (var ei = 0; ei < 20; ei++) { var et0 = ectx.win.performance.now(); ectx.win.xx.render(); var et1 = ectx.win.performance.now(); etimes.push(et1 - et0); } destroyFrame(ectx); reportTimes('1,000 mustache expressions', 'r-expr', etimes); log('\n========================'); log('All benchmarks complete.'); setStatus('Complete! All benchmarks finished.'); } function clearResults() { benchLog.textContent = ''; document.querySelectorAll('.val').forEach(function(el) { if (!el.id.match(/r-(src|min|gz)/)) { el.textContent = '—'; el.className = 'val'; } }); document.querySelectorAll('[id$="-min"],[id$="-max"],[id$="-n"],[id$="-ops"]').forEach(function(el) { if (!el.classList.contains('val')) el.textContent = '—'; }); setStatus('Ready. Click "Run All Benchmarks" to start.'); }