Migrating from Svelte
HellaJS shares many philosophical similarities with Svelte, including fine-grained reactivity and compile-time optimizations. However, HellaJS takes a more functional approach while maintaining the reactive principles that make Svelte powerful.
This guide will help you transition from Svelte to HellaJS by showing equivalent patterns and highlighting key differences.
Key Differences
- Granular DOM Updates: Both frameworks update only changed parts, but HellaJS uses direct signal bindings vs Svelte’s reactive statements
- Signals vs Reactive Variables: HellaJS signals use function call syntax, while Svelte uses assignment-based reactivity
- No Compilation Required: HellaJS works with standard JavaScript, while Svelte requires compilation of
.svelte
files - Function-Based Components: HellaJS uses pure functions returning JSX, while Svelte uses component files with script/template/style blocks
Component Structure
Svelte Single File Components
Svelte components combine script, template, and styles in a single .svelte
file with clear separation.
<!-- Counter.svelte -->
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<div>
<h1>{count}</h1>
<button on:click={increment}>+</button>
</div>
<style>
div {
padding: 2rem;
text-align: center;
}
</style>
HellaJS Function Components
HellaJS components are JavaScript functions that return JSX, with styling handled separately or via CSS-in-JS.
const Counter = () => {
const count = signal(0);
const increment = () => count(count() + 1);
return (
<div class="counter">
<h1>{count}</h1>
<button onClick={increment}>+</button>
</div>
);
};
Key Differences
- File Structure: Svelte uses single-file components, HellaJS uses standard JavaScript files
- Template Syntax: Both use similar template syntax, but HellaJS uses JSX
- Script Organization: Svelte script blocks vs JavaScript function scope
- Style Handling: Svelte scoped styles vs HellaJS CSS-in-JS or external stylesheets
Reactive State
Svelte Reactive Variables
Svelte uses assignment-based reactivity where variable assignments automatically trigger updates.
<script>
let name = 'World';
let count = 0;
// Reactive statements automatically re-run when dependencies change
$: greeting = `Hello, ${name}!`;
$: doubled = count * 2;
</script>
<div>
<h1>{greeting}</h1>
<p>Count: {count}, Doubled: {doubled}</p>
<button on:click={() => count += 1}>+</button>
<input bind:value={name} />
</div>
HellaJS Signals
HellaJS uses signals with function call syntax for reactive state management.
const App = () => {
const name = signal('World');
const count = signal(0);
// Derived values using computed or simple functions
const greeting = () => `Hello, ${name()}!`;
const doubled = () => count() * 2;
return (
<div>
<h1>{greeting}</h1>
<p>Count: {count}, Doubled: {doubled}</p>
<button onClick={() => count(count() + 1)}>+</button>
<input value={name} onChange={e => name(e.target.value)} />
</div>
);
};
Key Differences
- Syntax Pattern: Svelte uses
$: reactive = expression
, HellaJS usesconst reactive = () => expression
- Value Access: Svelte accesses values directly, HellaJS requires function calls
signal()
- Updates: Svelte uses assignment (
count += 1
), HellaJS uses setters (count(count() + 1)
) - Reactivity Detection: Svelte analyzes assignments, HellaJS tracks function calls automatically
Derived State
Svelte Reactive Statements
Svelte reactive statements automatically recompute when their dependencies change.
<script>
let todos = [
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Build app', done: true }
];
let filter = 'all';
// Reactive statement - automatically updates when todos or filter change
$: filteredTodos = todos.filter(todo => {
if (filter === 'completed') return todo.done;
if (filter === 'active') return !todo.done;
return true;
});
$: todoStats = {
total: todos.length,
completed: todos.filter(t => t.done).length,
active: todos.filter(t => !t.done).length
};
</script>
<div>
<p>{filteredTodos.length} todos</p>
<p>Stats: {todoStats.active} active, {todoStats.completed} completed</p>
</div>
HellaJS Computed Values
Computed values automatically cache and recompute when dependencies change.
const TodoApp = () => {
const todos = signal([
{ id: 1, text: 'Learn HellaJS', done: false },
{ id: 2, text: 'Build app', done: true }
]);
const filter = signal('all');
const filteredTodos = computed(() => {
return todos().filter(todo => {
if (filter() === 'completed') return todo.done;
if (filter() === 'active') return !todo.done;
return true;
});
});
const todoStats = computed(() => ({
total: todos().length,
completed: todos().filter(t => t.done).length,
active: todos().filter(t => !t.done).length
}));
return (
<div>
<p>{filteredTodos().length} todos</p>
<p>Stats: {todoStats().active} active, {todoStats().completed} completed</p>
</div>
);
};
Key Differences
- Declaration: Svelte uses
$: name = expression
, HellaJS usescomputed(() => expression)
- Caching: Both cache results until dependencies change
- Access Pattern: Svelte accesses directly, HellaJS requires function calls
- Dependency Tracking: Both automatically track dependencies, but HellaJS is more explicit about function calls
Component Props
Svelte Component Props
Svelte components export variables to create props, with optional default values and reactivity.
<!-- UserCard.svelte -->
<script>
export let user;
export let showEmail = true;
export let theme = 'light';
// Reactive statement using props
$: displayName = user.firstName + ' ' + user.lastName;
</script>
<div class="user-card {theme}">
<h2>{displayName}</h2>
{#if showEmail}
<p>{user.email}</p>
{/if}
</div>
<!-- Usage -->
<UserCard {user} {showEmail} theme="dark" />
HellaJS Component Props
HellaJS components receive props as function parameters, supporting destructuring and defaults.
const UserCard = ({ user, showEmail = true, theme = 'light' }) => {
const displayName = () => user().firstName + ' ' + user().lastName;
return (
<div class={`user-card ${theme}`}>
<h2>{displayName}</h2>
{showEmail && <p>{user().email}</p>}
</div>
);
};
// Usage
<UserCard user={user} showEmail={showEmail} theme="dark" />
Key Differences
- Prop Declaration: Svelte uses
export let
, HellaJS uses function parameters - Reactivity: Both maintain reactivity when passed reactive values
- Default Values: Svelte assigns in script block, HellaJS uses parameter defaults
- Prop Passing: Both support shorthand syntax for matching variable names
Global State
Svelte Stores
Svelte provides built-in stores for global state management.
// stores.js
import { writable, derived } from 'svelte/store';
export const theme = writable('light');
export const user = writable(null);
export const isLoggedIn = derived(user, $user => !!$user);
<!-- Component.svelte -->
<script>
import { theme, user, isLoggedIn } from './stores.js';
function toggleTheme() {
theme.update(t => t === 'light' ? 'dark' : 'light');
}
</script>
<div>
<p>Theme: {$theme}</p>
{#if $isLoggedIn}
<p>Welcome, {$user.name}!</p>
{/if}
<button on:click={toggleTheme}>Toggle Theme</button>
</div>
HellaJS Global Signals
Global signals can be imported and used directly without store subscriptions.
// stores.js
export const theme = signal('light');
export const user = signal(null);
export const isLoggedIn = computed(() => !!user());
// Component.jsx
import { theme, user, isLoggedIn } from './stores.js';
const Component = () => {
const toggleTheme = () => {
theme(theme() === 'light' ? 'dark' : 'light');
};
return (
<div>
<p>Theme: {theme}</p>
{isLoggedIn() && <p>Welcome, {user().name}!</p>}
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
Key Differences
- Store System: Svelte has built-in stores, HellaJS uses signals directly
- Subscription: Svelte uses
$
prefix for auto-subscription, HellaJS calls signals directly - Updates: Svelte uses
store.update()
, HellaJS uses direct setter calls - Derived Values: Both support computed/derived values with automatic dependency tracking
Side Effects
Svelte Reactive Statements and Lifecycle
Svelte uses reactive statements for side effects and lifecycle functions for component-level effects.
<script>
import { onMount, onDestroy } from 'svelte';
let userId = 1;
let user = null;
let loading = false;
// Reactive statement runs when userId changes
$: if (userId) {
loadUser(userId);
}
async function loadUser(id) {
loading = true;
try {
const response = await fetch(`/api/users/${id}`);
user = await response.json();
} catch (error) {
console.error('Failed to load user:', error);
} finally {
loading = false;
}
}
let interval;
onMount(() => {
interval = setInterval(() => {
console.log('Timer tick');
}, 1000);
});
onDestroy(() => {
if (interval) clearInterval(interval);
});
</script>
<div>
{#if loading}
<p>Loading...</p>
{:else if user}
<p>{user.name}</p>
{/if}
</div>
HellaJS Effects
Effects automatically track dependencies and handle async operations with built-in cleanup.
const UserProfile = () => {
const userId = signal(1);
const user = signal(null);
const loading = signal(false);
// Effect automatically tracks userId dependency
effect(async () => {
const id = userId();
if (!id) return;
loading(true);
try {
const response = await fetch(`/api/users/${id}`);
const userData = await response.json();
user(userData);
} catch (error) {
console.error('Failed to load user:', error);
} finally {
loading(false);
}
});
// Setup timer with automatic cleanup
const interval = setInterval(() => {
console.log('Timer tick');
}, 1000);
return (
<div onDestroy={() => clearInterval(interval)}>
{loading() ? (
<p>Loading...</p>
) : user() ? (
<p>{user().name}</p>
) : null}
</div>
);
};
Key Differences
- Effect Syntax: Svelte uses
$: if (condition)
, HellaJS useseffect(() => {})
- Async Support: HellaJS effects support async/await directly, Svelte requires separate async functions
- Cleanup: Svelte uses lifecycle functions, HellaJS uses element-level
onDestroy
- Dependency Tracking: Both automatically track, but HellaJS is more explicit about what’s tracked
Lists and Iteration
Svelte Each Blocks
Svelte uses {#each}
blocks with keyed iteration for efficient list rendering.
<script>
let todos = [
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Build app', done: true }
];
function toggleTodo(todo) {
todo.done = !todo.done;
todos = todos; // Trigger reactivity
}
function addTodo(text) {
todos = [...todos, { id: Date.now(), text, done: false }];
}
</script>
<ul>
{#each todos as todo (todo.id)}
<li class:completed={todo.done}>
<input
type="checkbox"
bind:checked={todo.done}
on:change={() => toggleTodo(todo)}
/>
{todo.text}
</li>
{/each}
</ul>
HellaJS forEach
Optimized forEach function provides reactive list rendering with intelligent diffing.
const TodoList = () => {
const todos = signal([
{ id: 1, text: 'Learn HellaJS', done: false },
{ id: 2, text: 'Build app', done: true }
]);
const toggleTodo = (todo) => {
todos(todos().map(t =>
t.id === todo.id ? { ...t, done: !t.done } : t
));
};
const addTodo = (text) => {
todos([...todos(), { id: Date.now(), text, done: false }]);
};
return (
<ul>
{forEach(todos, todo => (
<li key={todo.id} class={todo.done ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo)}
/>
{todo.text}
</li>
))}
</ul>
);
};
Key Differences
- Syntax: Svelte uses
{#each}
blocks, HellaJS usesforEach()
function - Keying: Both support keyed iteration for performance
- Mutations: Svelte allows direct mutations with reassignment, HellaJS requires immutable updates
- Performance: Both use advanced diffing algorithms for minimal DOM operations
Event Handling
Svelte Event Directives
Svelte uses on:
directives for event handling with optional modifiers.
<script>
let count = 0;
let name = '';
function handleClick(event) {
count += 1;
console.log('Clicked!', event);
}
function handleSubmit(event) {
console.log('Submitted:', name);
}
</script>
<div>
<button on:click={handleClick}>
Clicked {count} times
</button>
<button on:click|preventDefault={handleClick}>
Click (prevent default)
</button>
<form on:submit|preventDefault={handleSubmit}>
<input bind:value={name} />
<button type="submit">Submit</button>
</form>
</div>
HellaJS Event Props
HellaJS uses standard JSX event props with inline event handling.
const EventExample = () => {
const count = signal(0);
const name = signal('');
const handleClick = (event) => {
count(count() + 1);
console.log('Clicked!', event);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Submitted:', name());
};
return (
<div>
<button onClick={handleClick}>
Clicked {count} times
</button>
<button onClick={(e) => {
e.preventDefault();
handleClick(e);
}}>
Click (prevent default)
</button>
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={e => name(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
Key Differences
- Syntax: Svelte uses
on:event
, HellaJS usesonEvent
props - Modifiers: Svelte has built-in modifiers, HellaJS handles modifiers in code
- Event Objects: Both provide access to native event objects
- Inline Handlers: Both support inline arrow functions
Lifecycle Hooks
Svelte Lifecycle Functions
Svelte provides component-level lifecycle functions.
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
let count = 0;
onMount(() => {
console.log('Component mounted');
const interval = setInterval(() => count += 1, 1000);
return () => {
console.log('Component destroyed');
clearInterval(interval);
};
});
beforeUpdate(() => {
console.log('Before update, count:', count);
});
afterUpdate(() => {
console.log('After update, count:', count);
});
</script>
<div>Count: {count}</div>
HellaJS Element Lifecycle
HellaJS lifecycle hooks are tied directly to DOM elements rather than component instances.
const LifecycleExample = () => {
const count = signal(0);
const interval = setInterval(() => {
count(count() + 1);
}, 1000);
return (
<div
onDestroy={() => {
console.log('Element destroyed');
clearInterval(interval);
}}
>
<p
onUpdate={() => console.log('Count updated to:', count())}
>
Count: {count}
</p>
</div>
);
};
Key Differences
- Scope: Svelte lifecycle is component-scoped, HellaJS is element-scoped
- Mount/Destroy: Svelte uses
onMount
/onDestroy
, HellaJS usesonDestroy
element attribute - Updates: Svelte has
beforeUpdate
/afterUpdate
, HellaJS hasonUpdate
per element - Granular Control: HellaJS allows different lifecycle behavior per element within a component
Animation and Transitions
Svelte Transitions
Svelte provides built-in transition directives and custom transitions.
<script>
import { fade, slide, scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
let items = [1, 2, 3, 4];
let visible = true;
</script>
{#if visible}
<div transition:fade>
<h1 in:scale out:slide>Hello World!</h1>
</div>
{/if}
{#each items as item (item)}
<div animate:flip transition:slide>
Item {item}
</div>
{/each}
<button on:click={() => visible = !visible}>
Toggle
</button>
HellaJS CSS Transitions
HellaJS relies on CSS transitions and animations with reactive styling.
const AnimationExample = () => {
const visible = signal(true);
const items = signal([1, 2, 3, 4]);
const containerStyle = () => css({
opacity: visible() ? 1 : 0,
transform: visible() ? 'scale(1)' : 'scale(0.8)',
transition: 'all 0.3s ease'
});
const itemStyle = css({
padding: '1rem',
margin: '0.5rem',
background: '#f0f0f0',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)'
}
});
return (
<div>
{visible() && (
<div class={containerStyle}>
<h1>Hello World!</h1>
{forEach(items, item => (
<div key={item} class={itemStyle}>
Item {item}
</div>
))}
</div>
)}
<button onClick={() => visible(!visible())}>
Toggle
</button>
</div>
);
};
Key Differences
- Built-in vs CSS: Svelte has built-in transitions, HellaJS uses CSS transitions/animations
- Declarative vs Reactive: Svelte uses transition directives, HellaJS uses reactive CSS classes
- Animation Libraries: Both can integrate with third-party animation libraries
- Performance: Both provide smooth animations, but with different implementation approaches
Form Handling
Svelte Two-Way Binding
Svelte provides convenient two-way binding with bind:
directives.
<script>
let formData = {
name: '',
email: '',
age: 0,
subscribe: false,
category: 'general'
};
function handleSubmit() {
console.log('Form submitted:', formData);
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<input bind:value={formData.name} placeholder="Name" />
<input bind:value={formData.email} type="email" placeholder="Email" />
<input bind:value={formData.age} type="number" placeholder="Age" />
<label>
<input bind:checked={formData.subscribe} type="checkbox" />
Subscribe to newsletter
</label>
<select bind:value={formData.category}>
<option value="general">General</option>
<option value="support">Support</option>
<option value="feedback">Feedback</option>
</select>
<button type="submit">Submit</button>
</form>
HellaJS Controlled Inputs
HellaJS uses controlled components with explicit event handling.
const FormExample = () => {
const formData = signal({
name: '',
email: '',
age: 0,
subscribe: false,
category: 'general'
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData());
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData().name}
onChange={e => updateField('name', e.target.value)}
placeholder="Name"
/>
<input
value={formData().email}
onChange={e => updateField('email', e.target.value)}
type="email"
placeholder="Email"
/>
<input
value={formData().age}
onChange={e => updateField('age', +e.target.value)}
type="number"
placeholder="Age"
/>
<label>
<input
checked={formData().subscribe}
onChange={e => updateField('subscribe', e.target.checked)}
type="checkbox"
/>
Subscribe to newsletter
</label>
<select
value={formData().category}
onChange={e => updateField('category', e.target.value)}
>
<option value="general">General</option>
<option value="support">Support</option>
<option value="feedback">Feedback</option>
</select>
<button type="submit">Submit</button>
</form>
);
};
Key Differences
- Binding Approach: Svelte uses
bind:
directives, HellaJS uses controlled component pattern - Two-way vs One-way: Svelte has two-way binding, HellaJS uses one-way data flow with event handlers
- Boilerplate: Svelte requires less code, HellaJS is more explicit about data flow
- Form Libraries: Both can integrate with form validation and management libraries