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() |
| Lifecycle | useEffect() |
onMounted() |
onMount() |
init() / destroy() |
| Build step | Required | Recommended | Required | None |
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 }
});