Thanks to visit codestin.com
Credit goes to github.com

Skip to content

BoBch27/hamsterio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

66 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐹 hamsterio

A tiny, Alpine-like reactive runtime that uses vanilla JS signals and squeaks fast.

hamsterio takes Alpine's delightful HTML-first syntax and marries it with Solid's signal-based reactivity. The result? A tiny powerhouse that delivers updates so fast they'll make your hamster wheel spin.

"Why hamsters? Because they're small, fast, and surprisingly powerful. Also, they fit in your pocket." 🐹

Why hamsterio?

βœ… Tiny: Small enough to fit in a hamster's cheek pouch. It's under 3KB gzipped (~7KB minified).
βœ… Fast: Signal-based reactivity means surgical DOM updates, not sledgehammer re-renders.
βœ… Familiar: If you know Alpine.js, you already know hamsterio.
βœ… No Build Step: Drop it in via CDN and start coding. Your hamster doesn't have time for webpack configs.

πŸ“Œ Installation

⬆️ Migrating from hamsterx? This package was renamed from hamsterx to hamsterio in v0.5.0. Update your directives from x-* to h-* and you're good to go!

πŸ“¦ CDN (Recommended for the Alpine.js vibes)

<script defer src="https://cdn.jsdelivr.net/npm/hamsterio@latest/dist/hamsterio.min.js"></script>

The defer attribute is optional but recommended for performance - it lets your HTML load first before the hamster starts running.

πŸ“¦ NPM (For the build tool enthusiasts)

npm install hamsterio
// Import everything
import hamsterio from 'hamsterio';

// Or import just what you need
import { init, cleanup, createSignal, createEffect } from 'hamsterio';

βš™οΈ Disabling Auto-Init

If you need manual control over initialisation, set this before loading hamsterio:

<!-- For CDN -->
<script>window.hamsterioAutoInit = false;</script>
<script defer src="https://cdn.jsdelivr.net/npm/hamsterio@latest/dist/hamsterio.min.js"></script>
<script defer>
  // Now you control when to init
  hamsterio.init();
  console.log('🐹 hamsterio initialised!');
</script>

Note: When using npm/modules, auto-init only works in browser environments. If you're using a bundler, you'll typically want to call init() manually anyway:

import hamsterio from 'hamsterio';

// Call init when your app is ready
hamsterio.init();

⏰ The hamsterio:ready Event

When auto-init runs (CDN or browser usage), hamsterio dispatches a hamsterio:ready event on the document. This is useful if you need to run code after the library has initialised:

<script>
  document.addEventListener('hamsterio:ready', () => {
    console.log('🐹 hamster is ready to run!');
    // Your initialisation logic here
  });
</script>
<script defer src="https://cdn.jsdelivr.net/npm/hamsterio@latest/dist/hamsterio.min.js"></script>

Note that init() is synchronous, so you don't need to wait for this event when calling init() manually.

πŸš€ Quick Start

<div h-data="{ count: 0 }">
  <button h-on:click="count++">🐹 Feed the hamster</button>
  <p h-text="count"></p>
  <p h-show="count > 5">The hamster is getting chubby!</p>
</div>

That's it. No compilation, no virtual DOM, no existential crisis about framework choices.

Table of Contents

πŸ“– Directives

h-data

Defines reactive data for a component. Think of it as the hamster's data pellets.

<div h-data="{ name: 'Whiskers', age: 2 }">
  <!-- Your component here -->
</div>

Methods in h-data

You can define methods that access your reactive data using this:

<div 
  h-data="{ 
    count: 0,
    increment() {
      this.count++
    },
    reset() {
      this.count = 0
    }
  }"
>
  <button h-on:click="increment()">Add seed to pouches</button>
  <button h-on:click="reset()">Empty the pouches</button>
  <span h-text="count"></span>
</div>

Methods have full access to all reactive data through this and can be called from any directive.

h-text

Reactively updates text content. Like a hamster's name tag that magically changes.

<span h-text="name"></span>

h-html

Reactively updates inner HTML. For when your hamster needs to render rich content (use responsibly - sanitise user input!).

<div h-html="`<strong>${name} is hungry!</strong>`"></div>

⚠️ Security Warning: Never use h-html with unsanitised user input. Your hamster doesn't want XSS vulnerabilities in its cage!

h-show

Toggles visibility based on a condition. Your hamster appears and disappears (it's not magic, just CSS).

<div h-show="isVisible">🐹 Peek-a-boo!</div>

h-bind:[attribute]

Reactively binds attributes. Your hamster's outfit changes with its mood.

<!-- Boolean attributes (hamsters can be disabled too) -->
<button h-bind:disabled="isLoading">Submit</button>
<input h-bind:readonly="!isEditing">

<!-- Dynamic attributes (hamster images are important) -->
<img h-bind:src="hamsterPhotoUrl" h-bind:alt="hamsterName">
<a h-bind:href="hamsterBlogUrl">Read more about hamsters</a>

<!-- Conditional classes (object syntax - the hamster's favorite) -->
<div h-bind:class="{ 'active': isActive, 'sleepy': !isAwake }">
  Hamster status indicator
</div>

<!-- Dynamic styles (because hamsters appreciate good design) -->
<div h-bind:style="{ color: furColor, fontSize: size + 'px' }">
  Color-coordinated hamster
</div>

Class binding supports object syntax for conditional classes. Your original HTML classes are preserved (hamsters don't forget their roots):

<div class="hamster-card cozy" h-bind:class="{ 'running': isActive, 'napping': isLoading }">
  Base classes stay, dynamic classes toggle like a hamster wheel
</div>

h-on:[event]

Listens to events. Your hamster responds to pokes (gentle ones, we hope). With async support for hamsters who need to wait for things!

<button h-on:click="count++">Click me</button>
<input h-on:input="search = $event.target.value">

<!-- Async event handlers - because sometimes hamsters need to fetch snacks -->
<button h-on:click="await saveData(); showSuccess = true">
  Save to hamster database
</button>

<form 
  h-on:submit="
    $event.preventDefault();
    const result = await fetch('/api/hamster-signup', { 
      method: 'POST', 
      body: JSON.stringify($data) 
    });
    registered = await result.json();
  "
>
  <input h-bind:value="email" h-on:input="email = $event.target.value">
  <button type="submit">Join the hamster club</button>
</form>

Special variables:

  • $event - The native event object
  • $el - The element itself
  • $data - All your reactive data

Pro tip: Event handlers fully support await for async operations. Your hamster can now fetch data, call APIs, and wait for promises without breaking a sweat (or whisker).

h-for

Loops through arrays. Like multiple hamsters running on multiple wheels.

<!-- Simple syntax -->
<template h-for="item in items">
  <li h-text="item"></li>
</template>

<!-- With index -->
<template h-for="(item, index) in items">
  <li h-text="`${index}: ${item}`"></li>
</template>

h-init

Runs initialisation code when your component first loads. Perfect for fetching data, setting up timers, or waking your hamster up in the morning. Fully supports await for async operations!

<!-- Simple initialisation -->
<div h-data="{ greeting: '' }" h-init="greeting = 'Hello from hamster HQ! 🐹'">
  <p h-text="greeting"></p>
</div>

<!-- Fetch data on mount - hamsters love fresh data -->
<div 
  h-data="{ hamsters: [], loading: true }"
  h-init="
    hamsters = await (await fetch('/api/hamsters')).json();
    loading = false;
  "
>
  <div h-show="loading">Loading hamster profiles...</div>
  <ul h-show="!loading">
    <template h-for="hamster in hamsters">
      <li h-text="hamster.name"></li>
    </template>
  </ul>
</div>

<!-- Multiple async operations - because hamsters multitask -->
<div 
  h-data="{ user: null, settings: null, ready: false }"
  h-init="
    user = await (await fetch('/api/user')).json();
    settings = await (await fetch('/api/settings')).json();
    ready = true;
    console.log('🐹 Hamster profile loaded!');
  "
>
  <div h-show="ready">
    <h1 h-text="user.name"></h1>
    <p h-text="`Favorite food: ${settings.favoriteSnack}`"></p>
  </div>
</div>

Key points:

  • Runs once when the element is initialised (not reactive)
  • Runs after all other directives are set up (so your bindings are ready)
  • Fully supports await for fetching data or other async operations
  • Access to $el and all reactive data via $data

Pro tip: Use h-init for data fetching, third-party library initialisation, or any setup logic your hamster needs before getting to work!

🎨 Transitions

Make your hamster's entrances and exits graceful! hamsterio supports smooth transitions using h-transition-enter and h-transition-leave with h-show.

How It Works

When you toggle visibility with h-show, hamsterio can apply CSS classes for smooth animations:

<style>
  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  @keyframes fadeOut {
    from { opacity: 1; }
    to { opacity: 0; }
  }
  .fade-in { animation: fadeIn 300ms ease-out; }
  .fade-out { animation: fadeOut 300ms ease-in; }
</style>

<div h-data="{ visible: false }">
  <button h-on:click="visible = !visible">
    Toggle Hamster Visibility 🐹
  </button>
  
  <div 
    h-show="visible"
    h-transition-enter="fade-in"
    h-transition-leave="fade-out"
  >
    🐹 The hamster appears gracefully and exits with dignity!
  </div>
</div>

With CSS Transitions

You can also use CSS transitions instead of animations:

<style>
  .opacity-enter { 
    opacity: 1; 
    transition: opacity 300ms; 
  }
  .opacity-leave { 
    opacity: 0; 
    transition: opacity 300ms; 
  }
</style>

<div 
  h-show="open"
  h-transition-enter="opacity-enter"
  h-transition-leave="opacity-leave"
>
  Smoothly fading hamster content
</div>

Works With Popular CSS Libraries

hamsterio transitions work perfectly with Tailwind, Animate.css, or any CSS framework:

<!-- Tailwind classes -->
<div 
  h-show="open"
  h-transition-enter="transition ease-out duration-300 opacity-100 scale-100"
  h-transition-leave="transition ease-in duration-200 opacity-0 scale-95"
>
  Tailwind-powered hamster
</div>

Preventing Flash of Content

To prevent elements from briefly appearing before hamsterio loads, use inline styling: style="display: none;":

<div style="display: none;" h-show="open" h-transition-enter="fade-in">
  <!-- Hidden until hamsterio initialises, no flash! -->
  🐹 No premature hamster sightings
</div>

hamsterio automatically removes the display: none attribute during initialisation, then h-show takes over.

Note: Transitions work seamlessly with flexbox, grid, and any display type. hamsterio remembers your element's original display value! 🎯

πŸ’‘ Real-World Examples

Dropdown Menu (Every hamster needs options)

<style>
  @keyframes slideDown {
    from { 
      opacity: 0; 
      transform: translateY(-10px); 
    }
    to { 
      opacity: 1; 
      transform: translateY(0); 
    }
  }
  @keyframes slideUp {
    from { 
      opacity: 1; 
      transform: translateY(0); 
    }
    to { 
      opacity: 0; 
      transform: translateY(-10px); 
    }
  }
  .slide-down { animation: slideDown 200ms ease-out; }
  .slide-up { animation: slideUp 150ms ease-in; }
  .dropdown { display: flex; fleh-direction: column; }
</style>

<div h-data="{ open: false }">
  <button h-on:click="open = !open">
    🐹 Hamster Menu
  </button>
  
  <div 
    h-show="open"
    h-transition-enter="slide-down"
    h-transition-leave="slide-up"
    class="dropdown"
    style="display: none;"
  >
    <a href="#">Feed hamster</a>
    <a href="#">Pet hamster</a>
    <a href="#">Give hamster a wheel</a>
  </div>
</div>

Form Validation (Even hamsters validate their input)

<div 
  h-data="{ 
    email: '',
    isValid() {
      return this.email.includes('@') && this.email.includes('hamster')
    }
  }"
>
  <input 
    h-bind:value="email"
    h-on:input="email = $event.target.value"
    h-bind:class="{ 'border-red-500': email && !isValid() }"
    class="border"
    placeholder="[email protected]"
  />
  
  <span h-show="email && !isValid()">Hamsters need valid emails!</span>
</div>

Async Form Submission (Hamsters wait for the server)

<div 
  h-data="{ 
    name: '',
    email: '',
    submitting: false,
    success: false,
    async submit(e) {
      e.preventDefault();
      this.submitting = true;
      const response = await fetch('/api/hamster-signup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: this.name, email: this.email })
      });
      this.success = response.ok;
      this.submitting = false;
    }
  }"
>
  <form h-on:submit="await submit($event)">
    <input 
      h-bind:value="name"
      h-on:input="name = $event.target.value"
      placeholder="Hamster name"
    />
    <input 
      h-bind:value="email"
      h-on:input="email = $event.target.value"
      placeholder="[email protected]"
    />
    <button 
      type="submit"
      h-bind:disabled="submitting"
    >
      <span h-show="!submitting">Join the colony 🐹</span>
      <span h-show="submitting">Scurrying to server...</span>
    </button>
  </form>
  
  <div h-show="success">Welcome to the hamster family!</div>
</div>

Rich Content Rendering (Hamsters love formatted text)

<div 
  h-data="{ 
    content: '<h2>🐹 Hamster Care Guide</h2><p>Feed your hamster <strong>twice daily</strong> with fresh seeds and vegetables.</p>'
  }"
>
  <div h-html="content"></div>
  
  <button h-on:click="content = '<p>Content updated! Your hamster is happy! 😊</p>'">
    Update Guide
  </button>
</div>

Tab Navigation (Hamsters exploring different tunnels)

<div 
  h-data="{ 
    activeTab: 'home',
    setTab(tab) { this.activeTab = tab }
  }"
>
  <button 
    h-on:click="setTab('home')"
    h-bind:class="{ 'bg-hamster-blue': activeTab === 'home' }"
  >
    🏠 Home Cage
  </button>
  <button 
    h-on:click="setTab('wheel')"
    h-bind:class="{ 'bg-hamster-blue': activeTab === 'wheel' }"
  >
    βš™οΈ Exercise Wheel
  </button>
  <button 
    h-on:click="setTab('food')"
    h-bind:class="{ 'bg-hamster-blue': activeTab === 'food' }"
  >
    πŸ₯œ Food Stash
  </button>
  
  <div h-show="activeTab === 'home'" h-text="'Welcome to the hamster home!'"></div>
  <div h-show="activeTab === 'wheel'" h-text="'Time to run in circles!'"></div>
  <div h-show="activeTab === 'food'" h-text="'Cheeks full of seeds 🌻'"></div>
</div>

Data Fetching on Init (Hamsters love fresh data)

<div 
  h-data="{ 
    posts: [],
    loading: true,
    error: null
  }"
  h-init="
    try {
      const response = await fetch('/api/hamster_news?limit=5');
      posts = await response.json();
    } catch (e) {
      error = 'Failed to fetch hamster news 😒';
    } finally {
      loading = false;
    }
  "
>
  <div h-show="loading">🐹 Hamster is fetching data...</div>
  <div h-show="error" h-text="error"></div>
  
  <ul h-show="!loading && !error">
    <template h-for="post in posts">
      <li>
        <h3 h-text="post.title"></h3>
        <p h-text="post.body"></p>
      </li>
    </template>
  </ul>
</div>

⚑ Working with Signals (For the nerds)

Under the hood, hamsterio uses signals - a reactive primitive that's simpler than your hamster's exercise routine.

import { createSignal, createEffect } from 'hamsterio';

const [count, setCount] = createSignal(0);

createEffect(() => {
  console.log('Count is:', count());
});

setCount(5); // Logs: "Count is: 5"

Signals automatically track dependencies and only update what's necessary. It's like your hamster knowing exactly which food pellet changed.

🎯 Dynamic Content & Cleanup

Adding hamsters (elements) after page load? Use init(). Need to remove them cleanly? Use cleanup():

// Adding new content
const div = document.createElement('div');
div.setAttribute('h-data', '{ happy: true }');
document.body.appendChild(div);
hamsterio.init(div);

// Cleaning up before removal (prevents memory leaks!)
hamsterio.cleanup(div);
div.remove();

Why cleanup matters

Your hamster is tidy and doesn't like memory leaks! When you remove elements with h-data, always call cleanup() first to:

  • 🧹 Remove event listeners (no ghost clicks!)
  • 🧹 Dispose reactive effects (no phantom updates!)
  • 🧹 Free up memory (more room for hamster snacks!)

Good hamster practices:

// βœ… Clean hamster - proper cleanup
const modal = document.querySelector('#hamster-modal');
hamsterio.cleanup(modal);
modal.remove();

// βœ… Re-initialising? Clean first!
const component = document.querySelector('[h-data]');
hamsterio.cleanup(component);  // Clear old effects
hamsterio.init(component);     // Set up fresh ones

// ❌ Messy hamster - memory leak city!
document.querySelector('#dirty-modal').remove();  // Event listeners still attached! 😱

Note: h-for automatically calls cleanup() on its rendered items when the list changes, so you don't need to worry about that. Your hamster has your back! 🐹

πŸ’» Programmatic Access

Need to update data from outside (like reaching into the hamster cage)? Use getData():

const el = document.querySelector('[h-data]');
const data = hamsterio.getData(el);
data.count = 42; // Reactively updates! The hamster notices immediately.

Use cases:

  • Integration with third-party libraries (teaching old hamsters new tricks)
  • External form handling (hamster data entry)
  • Unit testing (making sure your hamster behaves)
  • Console debugging (console.log(hamsterio.getData(el)) - peek at the hamster)

Example - Plotly chart integration:

const chart = document.getElementById('hamster-activity-chart');
chart.on('plotly_click', (data) => {
  const hamsterData = hamsterio.getData(document.getElementById('stats'));
  hamsterData.selectedDay = data.points[0].x;
  hamsterData.wheelRotations = data.points[0].y;
});

🌐 Browser Support

Works in all modern browsers (anything that understands WeakMap, Proxy, and the concept of a hamster).

πŸ“Š Size Comparison

Framework Size (min + gzip)
hamsterio ~3KB 🐹
Alpine.js ~15KB πŸ”οΈ
Vue.js ~40KB πŸ—»
React ~45KB πŸ”οΈπŸ”οΈ

Your hamster is judging your bundle size.

⚠️ Caveats

  • Uses new Function() and with statements for expression evaluation (keep user input sanitised, or your hamster might escape)
  • h-html can be dangerous with unsanitised user input - your hamster doesn't want XSS in its cage!
  • No virtual DOM diffing - this is by design for simplicity
  • Doesn't include every Alpine.js feature (we're a hamster, not a capybara)

🀝 Contributing

Found a bug? Want to add features? Your hamster wheel contributions are welcome!

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/faster-hamster)
  3. Commit your changes (git commit -am 'Make hamster go zoom')
  4. Push to the branch (git push origin feature/faster-hamster)
  5. Open a Pull Request

πŸ—ΊοΈ Roadmap

  • Methods in h-data
  • h-bind directive (attribute binding)
  • Transition support
  • h-init directive (hook into element initialisation)
  • Async/await support in h-on and h-init
  • h-html directive (inner HTML binding)
  • Proper cleanup system
  • Event modifiers (.prevent, .stop, .once)
  • Benchmarks
  • Even more hamster emojis

πŸ’­ Philosophy

hamsterio believes in:

  • Simplicity over complexity - Like a hamster wheel, not a Rube Goldberg machine.
  • HTML-first - Your markup should read like English, not assembly code.
  • Minimal abstractions - Signals are simple. Keep it that way.
  • Fast enough - Your users won't wait for your JavaScript hamster to wake up.
  • Clean cages - Proper cleanup means no memory leaks. A tidy hamster is a happy hamster!

πŸ“œ License

MIT - Free as a hamster running in an open field

πŸ™ Credits

Inspired by the brilliant work of:

Built by Bobby Donev


Remember: With great reactivity comes great responsibility. Use your hamster powers wisely. 🐹✨