Migrating from Vue

HellaJS offers a fine-grained reactive approach to building UIs with concepts familiar to Vue 3 developers. This guide will help you transition from Vue 3 to HellaJS by showing equivalent patterns and highlighting architectural differences.

Key Differences

  • Granular DOM Updates: HellaJS updates individual DOM nodes, while Vue 3 updates components with optimized diffing
  • Signals vs Refs: HellaJS signals use function calls, while Vue 3 refs use .value property access
  • Dedicated List Function: HellaJS uses forEach for optimized list rendering, while Vue 3 uses v-for directive
  • Element Lifecycle: HellaJS lifecycle hooks are tied to elements, while Vue 3 hooks are component-scoped

Template Syntax

Vue Template Syntax

Template-based with directives and interpolation.

<template>
  <div>
    <h1>{{ title }}</h1>
    <p v-if="showMessage">{{ message }}</p>
    <p v-else>No message</p>
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    <button 
      :class="{ active: isActive }"
      :disabled="loading"
      @click="handleClick"
    >
      {{ buttonText }}
    </button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const title = ref('Hello');
const showMessage = ref(true);
const message = ref('Welcome');
const items = ref([{ id: 1, name: 'Item 1' }]);
const isActive = ref(false);
const loading = ref(false);
const buttonText = ref('Click me');

const handleClick = () => {
  console.log('Clicked');
};
</script>

HellaJS JSX Syntax

JSX-based with JavaScript expressions and conditional rendering.

const Component = () => {
  const title = signal('Hello');
  const showMessage = signal(true);
  const message = signal('Welcome');
  const items = signal([{ id: 1, name: 'Item 1' }]);
  const isActive = signal(false);
  const loading = signal(false);
  const buttonText = signal('Click me');
  
  const handleClick = () => {
    console.log('Clicked');
  };
  
  return (
    <div>
      <h1>{title}</h1>
      {showMessage() ? <p>{message}</p> : <p>No message</p>}
      <ul>
        {forEach(items, item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button 
        class={isActive() ? 'active' : ''}
        disabled={loading()}
        onClick={handleClick}
      >
        {buttonText}
      </button>
    </div>
  );
};

Key Differences

  • Template Language: HellaJS uses JSX, while Vue 3 uses template syntax with directives and compile-time optimizations
  • Conditional Rendering: HellaJS uses JavaScript conditionals, while Vue 3 uses v-if/v-else with efficient rendering
  • Class Binding: HellaJS uses string concatenation or computed values, while Vue 3 uses object/array syntax with reactive classes
  • Event Binding: HellaJS uses standard event props, while Vue 3 uses @ syntax with automatic listeners

Component Rendering

Vue Virtual DOM

Uses a virtual DOM with compile-time optimizations and re-renders components when state changes, then diffs the virtual DOM to update the real DOM.

<template>
  <div>{{ count }}</div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);
</script>

HellaJS DOM Binding

Creates direct reactive bindings between signals and DOM elements, eliminating the need for diffing and re-renders.

const Counter = () => {
  const count = signal(0);
  return <div>{count}</div>; // Only this text node updates
};

Key Differences

  • Direct Binding vs Virtual DOM: HellaJS binds signals directly to DOM elements, while Vue 3 uses optimized virtual DOM with compile-time hints
  • Granular Updates vs Re-rendering: HellaJS updates only the specific DOM nodes that change, while Vue 3 re-executes render functions with optimizations
  • Update Granularity: HellaJS updates at individual DOM node level, while Vue 3 updates at component level

Component Communication

Vue Props and Emits

Props down, events up pattern with defineProps and defineEmits.

<!-- Child.vue -->
<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="$emit('increment', count + 1)">
      Count: {{ count }}
    </button>
  </div>
</template>

<script setup>
defineProps({
  title: String,
  count: Number
});

defineEmits(['increment']);
</script>

<!-- Parent.vue -->
<template>
  <Child 
    :title="title" 
    :count="counter" 
    @increment="counter = $event"
  />
</template>

<script setup>
import { ref } from 'vue';

const title = ref('My Counter');
const counter = ref(0);
</script>

HellaJS Props and Callbacks

Direct prop passing with callback functions for communication.

const Child = ({ title, count, onIncrement }) => {
  return (
    <div>
      <h2>{title}</h2>
      <button onClick={() => onIncrement(count() + 1)}>
        Count: {count}
      </button>
    </div>
  );
};

const Parent = () => {
  const title = signal('My Counter');
  const counter = signal(0);
  
  return (
    <Child 
      title={title}
      count={counter}
      onIncrement={(newCount) => counter(newCount)}
    />
  );
};

Key Differences

  • Props Declaration: HellaJS uses function parameters, while Vue 3 uses defineProps with validation
  • Event Communication: HellaJS uses callback props, while Vue 3 uses defineEmits for type-safe events
  • Reactivity: HellaJS passes signals directly for granular reactivity, while Vue 3 props are automatically reactive
  • Type Safety: HellaJS relies on TypeScript for type safety, while Vue 3 has built-in props validation and TypeScript support

Component State

Vue ref and reactive

Manages component state with ref for primitives and reactive for objects.

<template>
  <div>
    <h1>Hello, {{ name }}!</h1>
    <p>Count: {{ count }}</p>
    <button @click="count++">+</button>
    <input v-model="name" />
  </div>
</template>

<script setup>
import { ref } from 'vue';

const name = ref('World');
const count = ref(0);
</script>

HellaJS signal

Signals provide reactive state that automatically updates dependent DOM elements.

const App = () => {
  const name = signal('World');
  const count = signal(0);
  
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Count: {count}</p>
      <button onClick={() => count(count() + 1)}>+</button>
      <input value={name} onChange={e => name(e.target.value)} />
    </div>
  );
};

Key Differences

  • API Pattern: HellaJS uses function call pattern for signals, while Vue 3 uses .value for refs
  • Reactivity: HellaJS updates specific DOM bindings directly, while Vue 3 triggers component re-renders
  • Dependency Tracking: HellaJS tracks dependencies at DOM element level, while Vue 3 tracks at component level

Cached State

Vue computed

Computed properties for expensive calculations with automatic dependency tracking.

<template>
  <div>
    <p>{{ filteredTodos.length }} todos</p>
    <button @click="addTodo">Add</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const todos = ref([]);
const filter = ref('all');

const filteredTodos = computed(() => {
  return todos.value.filter(todo => {
    if (filter.value === 'completed') return todo.done;
    if (filter.value === 'active') return !todo.done;
    return true;
  });
});

const addTodo = () => {
  todos.value.push({ id: Date.now(), text: 'New todo', done: false });
};
</script>

HellaJS computed

Values automatically cache results and recompute only when their dependencies change.

const TodoApp = () => {
  const todos = signal([]);
  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 addTodo = () => {
    todos([...todos(), { id: Date.now(), text: 'New todo', done: false }]);
  };
  
  return (
    <div>
      <p>{filteredTodos().length} todos</p>
      <button onClick={addTodo}>Add</button>
    </div>
  );
};

Key Differences

  • Dependency Management: HellaJS computed automatically tracks dependencies, similar to Vue 3 computed
  • Caching Strategy: HellaJS uses lazy evaluation and automatic invalidation when dependencies change, like Vue 3
  • API Consistency: HellaJS uses consistent function call pattern for all reactives, while Vue 3 uses .value

Global State

Vue Pinia Store

Pinia provides centralized state management with stores and actions.

<!-- store/counter.js -->
<script>
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    theme: 'light'
  }),
  actions: {
    increment() {
      this.count++;
    },
    setTheme(newTheme) {
      this.theme = newTheme;
    }
  }
});
</script>

<!-- Component.vue -->
<template>
  <button @click="store.increment">
    Current count: {{ store.count }}
  </button>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter';

const store = useCounterStore();
</script>

HellaJS Global Signals

Global signals can be imported and used directly in any component without stores or providers.

// Global signals - no store setup needed
const count = signal(0);
const theme = signal('light');

const Button = () => {
  return (
    <button onClick={() => count(count() + 1)}>
      Current count: {count}
    </button>
  );
};

// Any component can use the global signals directly
const App = () => {
  return (
    <div class={`app theme-${theme()}`}>
      <Button />
      <button onClick={() => theme(theme() === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
};

HellaJS Stores

Fine-grained reactivity where each property is independently reactive.

// Store
const user = store({
  name: '',
  email: '',
  preferences: {
    theme: 'light'
  }
});

// Component
const UserProfile = () => {
  return (
    <div>
      <input 
        value={user.name} 
        onInput={e => user.name(e.target.value)}
      />
      <p>Theme: {user.preferences.theme}</p>
      <button onClick={() => user.preferences.theme('dark')}>
        Dark Theme
      </button>
    </div>
  );
};

Key Differences

  • Store Pattern: HellaJS uses global signals with direct imports, while Vue 3 Pinia provides structured stores with composition
  • Boilerplate: HellaJS works with direct reactive primitives, while Vue 3 Pinia requires store setup and actions
  • Update Granularity: HellaJS updates only DOM elements bound to specific signals, while Vue 3 Pinia updates components that use the store

Side Effects

Vue watchEffect and watch

Watch functions for side effects with manual dependency tracking or explicit watchers.

<template>
  <div v-if="loading">Loading...</div>
  <div v-else>{{ user?.name }}</div>
</template>

<script setup>
import { ref, watchEffect } from 'vue';

const userId = ref(1);
const user = ref(null);
const loading = ref(false);

watchEffect(async () => {
  if (!userId.value) return;
  
  loading.value = true;
  try {
    const response = await fetch(`/api/users/${userId.value}`);
    const data = await response.json();
    user.value = data;
  } catch (err) {
    console.error('Failed to load user:', err);
  } finally {
    loading.value = false;
  }
});
</script>

HellaJS effect

Automatically tracks dependencies and handles async operations with built-in cancellation.

const UserProfile = ({ userId }) => {
  const user = signal(null);
  const loading = signal(false);
  
  effect(async () => {
    const id = userId(); // Automatically tracked
    if (!id) return;
    
    loading(true);
    try {
      const response = await fetch(`/api/users/${id}`);
      const data = await response.json();
      user(data);
    } catch (err) {
      console.error('Failed to load user:', err);
    } finally {
      loading(false);
    }
  });
  
  return (
    <div>
      {loading() ? <div>Loading...</div> : <div>{user()?.name}</div>}
    </div>
  );
};

Key Differences

  • Dependency Tracking: HellaJS effect automatically tracks reactive dependencies, similar to Vue 3 watchEffect
  • Async Handling: HellaJS effects support async/await with automatic cancellation, while Vue 3 watchEffect supports async functions
  • Cleanup: HellaJS provides automatic cleanup and request cancellation, while Vue 3 watchEffect returns cleanup functions
  • Race Conditions: HellaJS automatically cancels previous async operations, while Vue 3 requires manual race condition handling

Lists and Iteration

Vue v-for

Render lists using v-for directive with required key attributes for efficient reconciliation.

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <input 
        type="checkbox" 
        v-model="todo.done"
        @change="toggleTodo(todo, $event)"
      />
      {{ todo.text }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';

const todos = ref([
  { id: 1, text: 'Learn Vue', done: false },
  { id: 2, text: 'Build app', done: true }
]);

const toggleTodo = (todo, event) => {
  todo.done = event.target.checked;
};
</script>

HellaJS forEach

Optimized function for 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, checked) => {
    todos(todos().map(t => 
      t.id === todo.id ? { ...t, done: checked } : t
    ));
  };
  
  return (
    <ul>
      {forEach(todos, todo => (
        <li key={todo.id}>
          <input 
            type="checkbox" 
            checked={todo.done}
            onChange={(e) => toggleTodo(todo, e.target.checked)}
          />
          {todo.text}
        </li>
      ))}
    </ul>
  );
};

Key Differences

  • List Rendering: HellaJS uses dedicated forEach function, while Vue 3 uses v-for directive with keyed reconciliation
  • Performance: HellaJS uses Longest Increasing Subsequence (LIS) for minimal DOM operations, while Vue 3 reconciles lists with optimized algorithms
  • Reactivity: HellaJS forEach maintains granular reactive connections to individual items, while Vue 3 v-for re-renders when array changes
  • Mutability: HellaJS requires immutable updates for proper reactivity, while Vue 3 can mutate reactive arrays directly

Lifecycle Hooks

Vue Lifecycle Hooks

Component-scoped lifecycle with composition API hooks.

<template>
  <div>Timer: {{ count }}</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const count = ref(0);
let interval;

onMounted(() => {
  interval = setInterval(() => {
    count.value++;
  }, 1000);
});

onUnmounted(() => {
  clearInterval(interval);
});
</script>

HellaJS onDestroy

Element-level lifecycle hooks that are tied directly to DOM elements rather than component instances.

const Timer = () => {
  const count = signal(0);
  
  const interval = setInterval(() => {
    count(count() + 1);
  }, 1000);
  
  return (
    <div onDestroy={() => clearInterval(interval)}>
      Timer: {count}
    </div>
  );
};

Key Differences

  • Lifecycle Scope: HellaJS hooks are element-scoped, while Vue 3 hooks are component-scoped
  • Cleanup Pattern: HellaJS uses onDestroy element attribute, while Vue 3 uses onUnmounted and cleanup functions
  • Automatic Management: HellaJS automatically handles element cleanup when removed from DOM, while Vue 3 handles component cleanup
  • Granular Control: HellaJS allows different lifecycle behavior per element, while Vue 3 manages component lifecycle

Event Handling

Vue Event Handling

Event listeners with @ syntax and event modifiers.

<template>
  <div>
    <button @click="handleClick">Click me</button>
    <input @keyup.enter="handleEnter" v-model="inputValue" />
    <form @submit.prevent="handleSubmit">
      <button type="submit">Submit</button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const inputValue = ref('');

const handleClick = () => {
  console.log('Button clicked');
};

const handleEnter = () => {
  console.log('Enter pressed');
};

const handleSubmit = () => {
  console.log('Form submitted');
};
</script>

HellaJS Event Handling

Direct event handler assignment with standard DOM events.

const EventExample = () => {
  const inputValue = signal('');
  
  const handleClick = () => {
    console.log('Button clicked');
  };
  
  const handleKeyUp = (e) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  };
  
  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <input 
        onKeyUp={handleKeyUp}
        value={inputValue}
        onChange={e => inputValue(e.target.value)}
      />
      <form onSubmit={handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

Key Differences

  • Event Syntax: HellaJS uses standard DOM event props, while Vue 3 uses @ syntax with built-in modifiers
  • Event Modifiers: HellaJS uses standard JavaScript patterns, while Vue 3 provides convenient modifiers (.prevent, .stop, etc.)