DOM vs Pretext Architecture
A side-by-side comparison of traditional DOM measurement vs Pretext's prepare/layout model.
This is not a rigorous benchmark — it is an architectural explainer. The key difference is not raw speed but the decoupling of analysis from layout. DOM measurement couples reading to writing. Pretext separates them cleanly.
prepare()layout() What this demonstrates
The architectural difference between DOM-based measurement (create element → append → read → remove → repeat) and Pretext's model (prepare once → layout at any width cheaply). The numbers are approximate local measurements to illustrate the pattern, not definitive benchmarks.
The key insight
DOM measurement is inherently coupled to the rendering pipeline. Every offsetHeight read forces a
layout reflow. Pretext breaks this coupling: the expensive work (Unicode analysis, font metrics) happens once
in prepare(), and layout() is pure arithmetic that can run thousands of times cheaply.
Where this matters most
- Resize handlers that measure many elements
- Virtualized lists that need height estimates
- Split-pane layouts with text content
- Any UI where text dimensions drive layout decisions
Quick start
import { prepare, layout } from '@chenglou/pretext';
// --- DOM approach: every measurement triggers a reflow ---
function measureWithDOM(texts: string[], widths: number[]) {
const container = document.createElement('div');
container.style.cssText =
'position:absolute;visibility:hidden;left:-9999px;' +
'font:16px Inter,sans-serif;line-height:24px;';
document.body.appendChild(container);
const t0 = performance.now();
for (const w of widths) {
container.style.width = w + 'px';
for (const text of texts) {
const div = document.createElement('div');
div.textContent = text;
container.appendChild(div);
const _h = div.offsetHeight; // Forces reflow every time
container.removeChild(div);
}
}
const domMs = performance.now() - t0;
document.body.removeChild(container);
return domMs;
}
// --- Pretext approach: prepare once, layout is arithmetic ---
function measureWithPretext(texts: string[], widths: number[]) {
const font = '16px Inter, sans-serif';
// Prepare step: ~1ms per text (one-time cost)
const t0 = performance.now();
const prepareds = texts.map(t => prepare(t, font));
const prepareMs = performance.now() - t0;
// Layout step: ~0.001ms per text per width (pure math)
const t1 = performance.now();
for (const w of widths) {
for (const p of prepareds) layout(p, w, 24);
}
const layoutMs = performance.now() - t1;
return { prepareMs, layoutMs };
}
// Run comparison: 100 texts × 10 widths = 1000 measurements
const texts = Array(100).fill('Sample paragraph text...');
const widths = [150, 200, 250, 300, 350, 400, 450, 500, 550, 600];
const domMs = measureWithDOM(texts, widths);
const { prepareMs, layoutMs } = measureWithPretext(texts, widths);
// Typical results: DOM ~50ms, Pretext layout ~0.3ms
const speedup = domMs / (prepareMs + layoutMs);
console.log(`DOM: ${domMs.toFixed(1)}ms`);
console.log(`Pretext prepare: ${prepareMs.toFixed(2)}ms (one-time)`);
console.log(`Pretext layout: ${layoutMs.toFixed(2)}ms (hot path)`);
console.log(`Speedup: ${speedup.toFixed(0)}x faster`);