Performance

WakaPAC is designed around one core insight: DOM manipulation is expensive; everything else is cheap. Its internal architecture is built to do as little DOM work as possible, batch what it can't avoid, and never pay for something twice. This page covers the specific techniques that keep WakaPAC applications fast.

Expression Caching

Every binding expression in your HTML like data-pac-bind="enable: count > 0", has to be parsed from a string into an AST before it can be evaluated. That parsing is not free, and in a reactive application the same expression can be evaluated on every state change.

WakaPAC uses a dedicated ExpressionCache that stores the parsed result of every expression and binding string the first time it is seen. Subsequent evaluations skip straight to execution against the current data. The cache covers both interpolated expressions ({{ … }}) and full binding strings (data-pac-bind="…"), using the raw string as the key after whitespace normalization so equivalent expressions always hit the same slot.

Cache size is bounded at 1000 entries. When that limit is reached, the oldest entry is evicted before inserting the new one — a simple FIFO strategy that prevents unbounded memory growth in applications.

DOM Write Diffing

Reactive updates fire whenever data changes, but an update does not mean a DOM write. Before touching the DOM, WakaPAC compares the newly computed value against the last value it wrote to that binding and skips the write entirely if they are equal.

The comparison uses a structural deep equality check — not reference equality — so it handles objects and arrays correctly. The fast path is a strict === check that returns immediately for primitives. Only when that fails does it fall into recursive key-by-key comparison, and even then it short-circuits as soon as a difference is found. Previous values are stored directly on each element, keeping lookup O(1) with no secondary data structure.

Scoped Binding Maps

When a component is initialized, WakaPAC scans its DOM subtree once and builds two maps: an interpolation map that indexes every element with bindings, and a separate set of text node records for {{ … }} interpolations. All subsequent reactive updates iterate these maps directly — there is no re-querying of the DOM and no walking of the full document tree. Only nodes that belong to the component are ever visited.

Within each element's binding loop, the scope resolver (which handles foreach item aliasing) is constructed once per element, not once per binding key. Binding keys are iterated with a plain for loop rather than array methods to avoid a closure allocation on every iteration, and the two most common non-applicable binding types (foreach and click) are filtered with direct equality checks rather than Array.includes().

Batch DOM Rendering for Lists

When a foreach binding re-renders — because an array was mutated — WakaPAC builds the complete HTML string for all list items first, then sets innerHTML once. This produces a single layout reflow instead of one per item, which matters significantly for large lists.

Partial templates referenced inside a foreach block are expanded once before the item loop, not once per item. Because partials are static markup, they never need to be re-expanded per iteration.

Event Delegation

Browser event listeners are attached once, at the document level, not per component or per element. A single handler for each event type (mousedown, keydown, input, change, drag events, and so on) receives every matching event from the entire page and routes it to the correct component by walking up the DOM to find the nearest [data-pac-id] ancestor. This means the number of registered event listeners stays constant regardless of how many components or elements are on the page.

Click handlers declared via data-pac-bind="click: method" follow the same model — they are not wired individually per element.

High-Frequency Event Coalescing

Events that fire continuously — mousemove, touchmove, scroll, and resize — are throttled through a requestAnimationFrame-based coalescer. Instead of dispatching every raw DOM event, WakaPAC stores only the most recent event in a slot and fires it at most once per animation frame. Only one rAF callback is ever queued at a time; any subsequent events arriving before the next frame simply overwrite the pending slot. This ensures mousemove and touchmove never dispatch faster than the display refresh rate, and an optional FPS cap can reduce delivery further (the default for mousemove is configurable via wakaPAC.mouseMoveThrottleFps).

Window scroll and resize events are routed through the same coalescer at 60 fps. Container scroll state is similarly debounced with a requestAnimationFrame to absorb rapid scroll bursts without triggering a reactive update on every pixel.

Selective Reactivity

WakaPAC wraps your component data in a deep Proxy that intercepts reads and writes to trigger DOM updates. Not every property needs to participate in this system. Properties whose names start with _ or $ are excluded from the reactive proxy — they are stored and read as plain values. This convention exists precisely for cases where you need to attach DOM references, internal state, or complex objects to the abstraction without the overhead of wrapping them in a proxy and potentially triggering spurious updates.

Deferred Input Updates

Input bindings support three update triggers: immediate (default), delay: N, and change (on blur). The delay and change modes defer writing user input back into the abstraction, which defers all downstream reactive updates. A single setTimeout manages all pending delay-triggered updates; if a faster-firing entry arrives it replaces the scheduled time rather than stacking new timers.

Shared Browser Observers

Viewport visibility and element size are tracked via a single shared IntersectionObserver and a single shared ResizeObserver for the entire WakaPAC runtime. Each component registers its container element with these shared observers rather than instantiating its own. Observer callbacks receive all notifications from a frame in a single batch and process them in one pass.

One-Time Static Analysis

Several pieces of work that would otherwise repeat on every update are done once and stored. Module-level lookup tables (VK_MAP for virtual key codes, KEY_MODIFIER_MAP for modifier names) are allocated when the script loads, not on each event. The reverse mapping from key codes to human-readable names is built lazily on first use and then cached for the lifetime of the page. Partial template content is collected from the document exactly once on the first wakaPAC() call.

Component hierarchy — the parent/child relationships between nested components — is built in a single O(n) DOM traversal after initialization, using a pre-constructed element-to-component index for constant-time parent lookup. Rapid registrations (such as a parent dynamically creating children) are batched with a 10 ms coalesce window before hierarchy resolution runs.

Paint Queue

For components that use the canvas API via the MSG_PAINT message, dirty regions are accumulated in a map and flushed in a single requestAnimationFrame callback. Queuing an invalidation when a flush is already scheduled is a no-op — there is never more than one pending frame. Sub-regions queued for the same component are collected into a region list, and getDC() clips rendering to that entire region so only genuinely dirty area is drawn.