Coming From...
Already know React, Vue, Solid, Svelte, or Alpine? This guide maps concepts you already understand to WildflowerJS equivalents.
data-* attributes instead of custom template syntax. State is mutated directly (no immutable patterns). There's no build step, no virtual DOM, no JSX, and no framework-specific file formats. HTML stays HTML. JavaScript stays JavaScript.
Quick Concept Map
No matter which framework you're coming from, here's how the core ideas translate:
| Concept | React | Vue | Svelte | WildflowerJS |
|---|---|---|---|---|
| Component | function + JSX | .vue SFC | .svelte file | data-component + JS object |
| State | useState() |
ref() / reactive() |
$state() |
state: {} |
| Computed | useMemo() |
computed() |
$derived() |
computed: {} |
| Text output | {count} |
{{ count }} |
{count} |
data-bind="count" |
| Event | onClick={fn} |
@click="fn" |
onclick={fn} |
data-action="fn" |
| Conditional | {cond && ...} |
v-if / v-show |
{#if} |
data-show / data-render |
| List | .map() |
v-for |
{#each} |
data-list |
| Two-way bind | Manual onChange | v-model |
bind:value |
data-model |
| Global state | Context / Redux | Pinia | Svelte stores | wildflower.store() |
| Server data | TanStack Query / SWR / RTK Query | TanStack Query / useFetch | TanStack Query / load functions | data-query (built in) |
| Lifecycle | useEffect() |
onMounted() |
onMount() |
init() / destroy() |
| Build step | Required | Recommended | Required | None |
Reading from an endpoint, keeping it fresh, and tracking loading and error states are declared on the query itself. There is no equivalent of TanStack Query or SWR to install. See Data queries.
Coming from React
The biggest shifts: no JSX, no hooks, no immutable state patterns.
Component Definition
React
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
WildflowerJS
<div data-component="counter">
<button data-action="increment">
Count: <span data-bind="count">0</span>
</button>
</div>
wildflower.component('counter', {
state: { count: 0 },
increment() { this.count++; }
});
State Updates
React requires immutable updates. WildflowerJS supports both patterns, but direct mutation is simpler and enables targeted DOM updates:
React
// Must create new references
setItems([...items, newItem]);
setItems(items.filter(i => i.id !== id));
setUser({ ...user, name: 'Alice' });
WildflowerJS
// Direct mutation: simpler and faster
this.items.push(newItem);
this.items.splice(index, 1);
this.user.name = 'Alice';
// Immutable patterns also work if you prefer
this.items = [...this.items, newItem];
Hooks → Component Properties
React
const [count, setCount] = useState(0);
const doubled = useMemo(() => count * 2, [count]);
useEffect(() => {
console.log('mounted');
return () => console.log('cleanup');
}, []);
WildflowerJS
wildflower.component('example', {
state: { count: 0 },
computed: {
doubled() { return this.count * 2; }
},
init() { console.log('mounted'); },
destroy() { console.log('cleanup'); }
});
Context / Redux → Stores
React (Context)
const CartContext = createContext();
function CartProvider({ children }) {
const [items, setItems] = useState([]);
return (
<CartContext.Provider value={{ items, setItems }}>
{children}
</CartContext.Provider>
);
}
// In child:
const { items } = useContext(CartContext);
WildflowerJS
wildflower.store('cart', {
state: { items: [] },
addItem(item) { this.items.push(item); }
});
// In any component:
wildflower.component('checkout', {
subscribe: { cart: ['items'] },
computed: {
total() {
return this.stores.cart.items
.reduce((s, i) => s + i.price, 0);
}
}
});
memo, useCallback). State is a plain object; methods are plain functions.
Deep Dive: Migrating from React →. Mental model shift, side-by-side Todo app, pattern-by-pattern migration, gotchas, and checklist.
Coming from Vue
Vue and WildflowerJS share a lot of philosophy: reactive state, direct mutation, declarative templates. The main difference is syntax: Vue uses custom directives (v-if, @click); WildflowerJS uses standard data-* attributes.
Directive Mapping
| Vue | WildflowerJS | Notes |
|---|---|---|
{{ count }} | data-bind="count" | Text interpolation |
v-html="content" | data-bind-html="content" | Raw HTML |
@click="method" | data-action="method" | Click is default event |
@input="method" | data-action="input:method" | Prefix with event name |
v-model="prop" | data-model="prop" | Two-way binding |
v-if="cond" | data-render="cond" | DOM insertion/removal |
v-show="cond" | data-show="cond" | CSS display toggle |
v-for="item in items" | data-list="items" | Uses <template> child |
:key="item.id" | data-key="id" | On the list container |
:class="expr" | data-bind-class="expr" | Dynamic classes |
:style="expr" | data-bind-style="expr" | Dynamic styles |
Composition API → Component Definition
Vue 3 (script setup)
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() { count.value++ }
WildflowerJS
wildflower.component('counter', {
state: { count: 0 },
computed: {
doubled() { return this.count * 2; }
},
increment() { this.count++; }
});
Pinia → Stores
Vue (Pinia)
export const useCartStore = defineStore('cart', {
state: () => ({ items: [] }),
getters: {
total: (state) =>
state.items.reduce((s, i) => s + i.price, 0)
},
actions: {
addItem(item) { this.items.push(item); }
}
});
WildflowerJS
wildflower.store('cart', {
state: { items: [] },
computed: {
total() {
return this.items.reduce((s, i) => s + i.price, 0);
}
},
addItem(item) { this.items.push(item); }
});
.value unwrapping. No build step needed. No custom directives: data-* attributes are valid HTML. Stores and plugins have the same structure as components (state, computed, methods), with no separate getters/actions blocks.
Deep Dive: Migrating from Vue →. Directive mapping, side-by-side Todo app, pattern-by-pattern migration, gotchas, and checklist.
Coming from Solid
Solid and WildflowerJS both avoid the virtual DOM and use fine-grained reactivity. The key difference: Solid requires signal getters (count()); WildflowerJS resolves properties automatically.
Signals → State
Solid
// Signals: for primitive values
const [count, setCount] = createSignal(0);
// Read: count() Write: setCount(count() + 1)
// Stores: for nested objects/arrays
const [items, setItems] = createStore([]);
// Mutate: setItems(produce(s => s.push(item)))
// Or: setItems(items.length, newItem)
WildflowerJS
state: { count: 0, items: [] }
// Read: this.count (property access)
// Write: this.count++
// Write: this.items.push(item)
createMemo → Computed
Solid
const doubled = createMemo(() => count() * 2);
// Access: doubled()
WildflowerJS
computed: {
doubled() { return this.count * 2; }
}
// Access: this.doubled (or data-bind="doubled")
JSX Control Flow → Data Attributes
Solid
<Show when={isVisible()}>
<div>Visible</div>
</Show>
<For each={items()}>
{item => <li>{item.name}</li>}
</For>
WildflowerJS
<div data-show="isVisible">Visible</div>
<ul data-list="items" data-key="id">
<template>
<li data-bind="name"></li>
</template>
</ul>
this.count instead of count(). No produce() for mutations. No JSX or build step. Same fine-grained reactivity, different authoring model.
Deep Dive: Migrating from Solid →. Signal-to-state mapping, side-by-side Todo app, pattern-by-pattern migration, gotchas, and checklist.
Coming from Svelte
Svelte and WildflowerJS both favor direct mutation and minimal boilerplate. The difference: Svelte compiles .svelte files; WildflowerJS works directly in the browser with plain HTML.
Reactivity
Svelte 5
let count = $state(0);
let doubled = $derived(count * 2);
function increment() { count++; }
WildflowerJS
wildflower.component('counter', {
state: { count: 0 },
computed: {
doubled() { return this.count * 2; }
},
increment() { this.count++; }
});
Template Syntax
Svelte
<button onclick={increment}>{count}</button>
{#if isVisible}<div>Shown</div>{/if}
{#each items as item (item.id)}
<li>{item.name}</li>
{/each}
<input bind:value={name} />
WildflowerJS
<button data-action="increment">
<span data-bind="count"></span>
</button>
<div data-render="isVisible">Shown</div>
<ul data-list="items" data-key="id">
<template><li data-bind="name"></li></template>
</ul>
<input data-model="name" />
.svelte file format. No $state() / $derived() runes. Standard HTML works in any editor, any server, any CDN, with no build pipeline required.
Deep Dive: Migrating from Svelte →. Runes-to-state mapping, side-by-side Todo app, pattern-by-pattern migration, gotchas, and checklist.
Coming from Alpine.js
Alpine is the closest relative to WildflowerJS. Both are attribute-based, no-build-step frameworks. WildflowerJS adds proper component lifecycle, stores with subscriptions, computed caching, and keyed list reconciliation.
Attribute Mapping
| Alpine | WildflowerJS | Notes |
|---|---|---|
x-data="{ count: 0 }" | data-component="name" | State is defined in JS, not inline |
x-text="count" | data-bind="count" | |
x-html="content" | data-bind-html="content" | |
@click="count++" | data-action="increment" | Logic lives in JS methods, not inline |
x-model="name" | data-model="name" | |
x-show="isOpen" | data-show="isOpen" | Nearly identical |
x-if="isOpen" | data-render="isOpen" | DOM insertion/removal |
x-for="item in items" | data-list="items" | Uses <template> child |
:class="expr" | data-bind-class="expr" | |
Alpine.store('name', {}) | wildflower.store('name', {}) | Very similar API |
Inline Logic → Defined Methods
Alpine
<div x-data="{ count: 0, get doubled() {
return this.count * 2 } }">
<button @click="count++">
<span x-text="doubled"></span>
</button>
</div>
WildflowerJS
<div data-component="counter">
<button data-action="increment">
<span data-bind="doubled"></span>
</button>
</div>
wildflower.component('counter', {
state: { count: 0 },
computed: {
doubled() { return this.count * 2; }
},
increment() { this.count++; }
});
init, destroy, watch). Store subscriptions with automatic cleanup. Separation of logic from markup, with no inline JavaScript in attributes.
Deep Dive: Migrating from Alpine.js →. Attribute mapping, scaling beyond Alpine, side-by-side Todo app, gotchas, and checklist.
Universal Patterns
A few WildflowerJS patterns that don't have direct equivalents in other frameworks:
Cross-Entity Access
Access any store or component directly in HTML with the $ prefix:
<!-- Read from a store in any component's template -->
<span data-bind="$user.name"></span>
<div data-show="$auth.isLoggedIn">Welcome!</div>
<div data-list="$cart.items" data-key="id">
<template><span data-bind="name"></span></template>
</div>
Store Subscriptions
Declarative store wiring with automatic cleanup on component destroy:
wildflower.component('dashboard', {
subscribe: {
user: ['profile', 'preferences'],
cart: ['items']
},
// Fires when any subscribed path changes
onStoreUpdate(storeName, path, newValue, oldValue) {
if (storeName === 'cart') this.refreshTotals();
}
});
List Action Context
Action handlers inside lists receive full context, including the item, its index, and parent list info:
deleteCard(event, element, details) {
const card = details.item; // Current list item
const index = details.index; // Position in array
const column = details.parent.item; // Parent list item (nested lists)
this.items.splice(index, 1);
}
Built-in Store Persistence
One-line localStorage persistence, no middleware or plugins needed:
wildflower.store('settings', {
storageKey: 'app-settings', // localStorage key
autoSave: true, // Persists on every state change
state: { theme: 'light', fontSize: 14 }
});