The Entity Model
WildflowerJS was designed to be simple and cohesive. That's most visible in its four building blocks for reactive behavior: components, stores, plugins, and pools all share the same declaration shape (state, computed, methods, lifecycle). They differ in specialization: what each one uniquely adds, and what it gives up.
entity: block), but those items share the pool's single ReactiveStateManager rather than each having their own. A pool with 2600 items is one ReactiveStateManager, not 2600. That's what makes pools fast at scale.
Shared shape
Whether you're writing a component, store, plugin, or pool, every declaration uses the same four things:
state: the entity's data fieldscomputed: values derived from state (declared as functions)- methods: behaviors, declared as top-level functions on the definition object
- lifecycle hooks:
init(),tick(dt),destroy(), and so on
Learn the shape once in any of the four, and you know it everywhere.
Capability matrix
Where the four kinds differ.
| Capability | Pool entity | Store | Component | Plugin |
|---|---|---|---|---|
Declare state |
✅ (defaults, merge-on-add) | ✅ | ✅ | ✅ |
Declare computed |
✅ (uncached getters) | ✅ (reactive, cached) | ✅ (reactive, cached) | ✅ (reactive, cached) |
| Declare methods | ✅ | ✅ | ✅ | ✅ |
init / tick / destroy hooks |
via pool (onAdd, onRemove) |
✅ | ✅ | ✅ |
| Proxy-reactive state (push-based) | ❌ (plain JS, pull-based) | ✅ | ✅ | ✅ |
| Subscribe to stores | ❌ | ✅ (cross-store) | ✅ | ✅ |
localStorage persistence (storageKey) |
❌ | ✅ | ❌ | ❌ |
| Owns DOM / template | via pool template | ❌ | ✅ | ❌ |
data-action event routing |
via template | ❌ | ✅ | ❌ |
| Framework extension hooks (custom directives, globals) | ❌ | ❌ | ❌ | ✅ |
| Multiplicity | many (keyed by id) | singleton (per name) | many (per mounted element) | singleton (per name) |
How to choose
A decision tree in prose:
- Are you extending the framework itself? (custom directives, adding global methods, hooking into the framework's lifecycle) → Plugin.
- Do you have many similar things to render at high frequency? (particles, enemies, table rows, hundreds of reactive items updating per frame) → Pool entity. You give up Proxy reactivity; you gain cheap multi-instance rendering.
- Do you need global state shared across components? (user session, cart, settings, anything that survives navigation) → Store. You give up DOM ownership; you gain singleton addressability and optional persistence.
- Anything else. → Component. The default choice for UI-facing, per-element reactive logic.
What "pull-based" means for pool entities
The one row in the matrix that trips up new users is "Proxy-reactive state." Every other entity kind uses push reactivity: mutating this.count++ immediately triggers dep tracking, re-runs computed, and updates bindings. Pool entities don't.
Pool entities are plain JavaScript objects. Mutating entity.hp = 50 does nothing immediately. The pool re-reads every entity once per animation frame and syncs the DOM in a batch. This is what lets pools render thousands of entities without per-property Proxy overhead. The cost is that you don't get synchronous, per-mutation reactivity at the entity level.
For most pool use cases (animation, real-time data, games) this tradeoff is correct. See Why Pools? for the full story.
Side-by-side declarations
The same shape, declared four ways:
wildflower.component('counter', {
state: { count: 0 },
computed: {
doubled() { return this.count * 2 }
},
increment() { this.count++ },
init() { /* ... */ }
})
wildflower.store('cart', {
state: { items: [] },
computed: {
total() { return this.items.length }
},
add(item) { this.items.push(item) },
init() { /* ... */ }
})
wildflower.plugin('timer', {
state: { elapsed: 0 },
computed: {
seconds() { return this.elapsed / 1000 }
},
reset() { this.elapsed = 0 },
init() { /* ... */ }
})
pools: {
enemies: {
entity: {
state: { hp: 100 },
computed: {
isDead() { return this.hp <= 0 }
},
takeDamage(n) { this.hp -= n }
}
}
}