Skip to content

WildflowerJS v1.2.0

Latest

Choose a tag to compare

@wfjs-admin wfjs-admin released this 08 Jul 02:05

[1.2.0] - 2026-07-07

Under the hood

  • Reactivity rebuilt as a single dependency graph: state, computeds, effects, and bindings are now nodes in one unified graph resolved in a single pass, replacing the split between the reactive state manager and a parallel binding-context system. No API change and nothing to migrate; the result is a smaller core, faster reads of unchanged values, leaner scheduling and dependency re-tracking, and one place where dependency tracking is defined.
  • List rendering rebuilt on targeted updates: data-list no longer creates a reactive effect per row; each list registers its rows' bound fields on one per-list dispatcher, and an in-place field change routes to exactly the bindings that read it, down to a single direct DOM write for plainly bound fields. No API change and nothing to migrate: roughly 20 to 25% less memory per row on lists whose templates use item-level computed properties, faster targeted updates and removals, and one update path where there used to be four, which let this cycle's unification surface and fix several latent inconsistencies before they could ever ship.

Added

  • wildflower.unregister(name) unified component/store teardown: removes a component definition (destroying its live instances and disposing their reactive resources) and/or disposes a store of that name, freeing the name for a fresh registration. Fills the gap where registration was first-write-wins with no way to replace a definition. Useful for live-preview and HMR teardown, dynamic apps that swap components, and tests. Safe for an unknown name; handles a name used by both a component and a store.
  • Dev-mode warning (WF-215) when a component or store is re-registered with a different definition: re-registering an existing name is silently skipped (the original kept); when the incoming definition actually differs, development builds now warn and point at wildflower.unregister. Identical re-registrations stay quiet; the check compares method source, not just shape. Development builds only.
  • Iteration dependencies are tracked: a computed or binding that iterates a reactive object's keys (Object.keys, for...in, spread) now re-runs when a key is added or removed. Previously only reassigning the object woke iteration readers. Updating an existing key's value still does not re-run keys-only readers, so hot-path field writes stay quiet.
  • Dev-mode warning (WF-214) when a zero-arg computed in a list row reads an item property via this: item-level computeds receive the item as their first argument; a computed declared without parameters evaluates at component scope, so this reads of item fields silently resolve undefined. The warning fires only when the read misses on the component but the current item has that property, names the computed and property, and suggests the (item) signature. Development builds only; fires once per component and computed.
  • The data-csp-safe script attribute: on pages with a strict Content Security Policy, add data-csp-safe to the framework script tag and WildflowerJS starts directly in CSP-safe mode, never attempting dynamic code evaluation, so the page produces zero CSP violations and zero report-uri reports. Without it the framework still auto-detects and falls back, at the cost of one benign violation report from the startup capability probe.
  • Custom directives and lifecycle hooks now ship in every build: the declarative data-* custom-directive API and component lifecycle hooks, previously bundled only with the plugin system, are now available in all five build variants including lite and mini. plugin() and dependency injection stay gated to the full / spa / standard builds.
  • DevTools timeline observability: development-build per-frame timeline recording (microtask drains, effect runs, render-sweep duration). Stripped entirely from production builds; surfaces in the companion DevTools extension.
  • Dev-mode warning when a computed reads pool.length / pool.size: these are non-reactive getters, so a computed that reads one caches a single value and never re-runs, leaving the bound UI silently stale. The warning names the computed and suggests the mirror-in-tick() fix. Development builds only; fires at most once per pool.

Breaking Changes

  • data-bind-attr renders a non-boolean attribute bound to false as the literal attr="false" instead of removing it. Previously the component binding path removed a non-boolean attribute when its bound value was false; it now writes the literal string, matching the list path and the documented contract that non-boolean attributes hold their literal value. Boolean attributes are unchanged: false still removes them, since for a boolean attribute presence is the value. For most bindings the new behavior is more correct. aria-expanded, aria-hidden, aria-checked and similar now emit ="false" instead of vanishing, which is the accessible form (an absent aria-expanded conveys no state; aria-expanded="false" says "collapsed"). The one place it can bite is a CSS attribute-presence selector used as a flag. [data-active] { ... } matches whenever the attribute exists regardless of its value, so an element bound { 'data-active': isActive } with isActive === false now matches [data-active] (it renders data-active="false") where before the attribute was absent and did not match. Migration: if you relied on a falsey bind removing such an attribute, bind the value as null or undefined (both still remove the attribute) instead of false, or select on the value ([data-active="true"]) rather than on presence.

Fixed

Performance

  • data-list / data-pool create path rebuilt (clone + setter): rows are built by cloning a cached row prototype and writing each text binding to textContent, batched into one DocumentFragment, replacing the previous serialize-all-rows-to-HTML-then-reparse path. Substantially faster row creation, most visibly on large lists.
  • Second pass on the data-list / data-pool create path: each row reads its values from the underlying raw array instead of through the reactive proxy (skipping a per-row proxy and its access traps), resolves its bound child elements by walking element node pointers instead of indexing a live element collection, and copies only the item properties its class expressions reference. Builds without server-side rendering also drop an unused legacy list path. Most visible when building or replacing large lists.
  • Single-text-binding update fast-path: a targeted single-prop change to an item prop bound to exactly one plain text node writes textContent directly, skipping the generic per-item bind dispatch. Once such a field is identified, later writes to it update the text node directly at assignment time, bypassing the update-batching step entirely.
  • Nested-path targeted rebind: a deep item-prop change (rows[i].user.name) now skips the unaffected bindings on the row instead of rebinding the whole row. Shallow (flat) item-prop updates are unchanged.
  • Leaner data-list update path: redundant per-item class re-evaluation is skipped when the changed prop is not referenced by any class binding, and the per-update DOM re-scan for nested [data-list] elements is eliminated, dropping the per-update querySelectorAll calls to zero.
  • Faster item insertion and removal on reactive lists: adding items with push, unshift, or splice and removing them now operate on the underlying raw array, skipping a layer of reactive-proxy traversal, and single-item removal drops a redundant proxy lookup while re-indexing the remaining rows.
  • Faster cross-store computed reads and writes on shallow chains: direct property access in the proxy set traps (instead of receiver-form Reflect), single-proxy cross-store reads (eliminating a double-proxy hop), and skipping dependency re-tracking on lean re-evaluation of static-dependency cross-store computeds.
  • Targeted updates extended beyond text to class, style, and attribute bindings: eligible list rows retire their per-item update effect and apply changes through a direct per-binding writer, updating a single target without re-running the row's bindings (the single-text fast path is the special case).
  • Replacing a list's array with new objects updates each row once: a keyed list reassigned a fresh array whose items carry the same keys (the swap-in-a-new-page-of-results pattern) now applies each reused row's bindings a single time instead of twice.
  • Targeted structural updates for swap, move, and single removal: each applies a minimal DOM update classified from the exact array operation instead of re-diffing the whole list.
  • Reactive updates flush on the microtask: pending effects drain after a state change rather than waiting for the next animation frame, removing up to a frame of latency before the DOM reflects an update.
  • Lower per-row memory and allocation on large lists: per-object reactive bookkeeping moved off the row objects into a side table, repeated per-row metadata was de-duplicated, and the per-row text writer is now shared.