npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rabbx/fx

v1.2.0

Published

JSX that compiles to raw DOM. Signals that update exactly what changed. Zero virtual DOM.

Readme

@rabbx/fx

JSX that compiles to raw DOM. Signals that update exactly what changed. Zero virtual DOM.

npm license ko-fi

Docs · GitHub · Ko-fi


⚡ What is @rabbx/fx?

@rabbx/fx is a fine-grained reactive framework that compiles JSX directly into imperative DOM operations at build time. There is no virtual DOM, no diffing algorithm, and no runtime template compiler. When a signal changes, only the exact text node, attribute, or element bound to that signal updates — nothing else is touched.

Write familiar JSX. Get Svelte-level performance. Ship ~3kb of runtime.

const count = $state(0);

return (
  <button onclick={() => { count.value += 1; }}>
    Clicked {count.value} times
  </button>
);
// Compiles to:
// const el = _el("button");
// on(el, "click", () => { count.value += 1; });
// append(el, () => `Clicked ${count.value} times`);

🚀 Cool Features

| Feature | Description | |---|---| | Compile-Time Macros | <For>, <Each>, <Show> are erased at build time. Zero runtime cost for control flow. | | Keyed Reconciliation | <For> uses O(n) keyed diffing via reconcileKeyed(). Preserves focus, scroll, and animations. | | Svelte-Style Directives | use:tooltip={{ text }} with auto-cleanup and reactive param detection. | | Reactive Array Chains | .filter().map() compiles to a single $effect with keyed output. No intermediate allocations. | | Partial Hydration Islands | Embed interactive components in static SSR HTML. Each island hydrates independently. | | Replace-on-Hydrate | Client renders fresh DOM off-screen, then atomically swaps SSR content. Zero cursor-alignment bugs. | | Zero-Styling Primitives | FxButton, FxInput, FxFetch etc. ship with no CSS. Full attribute pass-through. Your design system, your rules. | | Context Without Traversal | Module-scoped signal stacks. No provider wrapper elements in output. O(1) lookup. | | SSR That Actually Works | All macros, directives, and signals produce correct server HTML. Inert on server, alive on client. |


📦 Installation

npm install @rabbx/fx

Vite Plugin

// vite.config.ts
import { defineConfig } from 'vite';
import { rabbxPlugin } from '@rabbx/fx/vite';

export default defineConfig({
  plugins: [rabbxPlugin()],
});

🎯 Quick Start

export default function Counter() {
  const count = $state(0);
const  doubled = $computed(()=>count.value*2);

  // Runs whenever `count` changes
  $effect(() => {
    document.title = `Count: ${count.value}`;
      
    // Optional cleanup
    return () => {
      document.title = 'Rabbx App';
    };
  });



  return (
    <div class="p-4 space-y-3 shadow-2xl w-50 h-40 ">
    <FxReturn value={()=>(
      <p class="text-gray-700">Count: {count.value} | Doubled: {doubled.value}</p>
      )} deps={[count.value,doubled.value]}/>
      <button
        class="px-4 py-2 bg-blue-600 text-white rounded cursor-pointer"
        onclick={() =>{count.set(c => c + 1)}}
      >
        Increment
      </button>
    </div>
  );
}

📖 API Reference

Signals

| Primitive | Signature | Description | |---|---|---| | $state<T> | (init: T) → Signal<T> | Mutable signal. Read/write via .value. Functional update via .set(fn). | | $computed<T> | (fn: () → T) → Signal<T> | Derived signal. Auto-tracks dependencies. Lazy evaluation. | | $effect | (fn: EffectFn) → () → void | Side effect. Re-runs when tracked signals change. Returns dispose fn. | | $effect.root | (fn: EffectFn) → () → void | Untracked root scope. Collects nested cleanups. | | $effect.defer | (fn: EffectFn) → () → void | Microtask-deferred effect. Avoids blocking initial render. | | $bindable<T> | (init: T, cb?) → Signal<T> | Two-way binding signal with optional change callback. | | $resource<T> | (fetcher: () → Promise<T>) → Resource<T> | Async data fetching. Returns { data, loading, error } signals. | | untracked | (fn: () → T) → T | Read signals without subscribing to current effect. | | batch | (fn: () → void) → void | Group multiple writes into single flush cycle. |

Compiler Macros

Macros are transformed at build time. They produce zero runtime overhead.

<For> — Reactive Keyed List

<For items={todos.value} as="todo">
  <li key={todo.id}>
    <span>{todo.text}</span>
    <button onclick={() => remove(todo.id)}>✕</button>
  </li>
</For>

Compiles to $effect + reconcileKeyed(). Re-renders only changed items. Preserves DOM identity.

<Each> — Static List

<Each items={navItems} as="item">
  <a href={item.href}>{item.label}</a>
</Each>

Compiles to raw for loop. Zero reactivity overhead. Ideal for static data.

<Show> — Conditional Rendering

<Show when={isLoggedIn.value} fallback={<LoginForm />}>
  <Dashboard />
</Show>

Compiles to $effect + if/else. Wrapper uses display:contents for zero layout impact.

<Suspense> — Async Boundary

<Suspense fallback={<Spinner />}>
  <FxFetch url="/api/data">{(data) => <DataView data={data} />}</FxFetch>
</Suspense>

Directives

Svelte-style use: directives attach behavior to DOM elements.

<button use:tooltip={{ text: 'Save' }}>Save</button>
<input use:focus />
<div use:clickOutside={() => { open.value = false; }}>...</div>
  • Actions run after element is appended to DOM
  • Cleanup registered automatically via onDestroy
  • Params containing .value are wrapped in reactive getters
  • SSR-safe: actions never execute on server

Zero-overhead. Module-scoped signal stacks. No VDOM traversal. No wrapper elements in output.

Store

Global reactive state with external pub/sub.

import { createStore } from '@rabbx/fx/runtime/store';

export const userStore = createStore({ name: '', theme: 'dark' });

// Auto-tracks in components
<FxReturn value={() => <span>{userStore.value.name}</span>} deps={[userStore.value]} />

// External subscriber (persistence, logging)
const unsub = userStore.subscribe(v => localStorage.setItem('user', JSON.stringify(v)));

// Functional update
userStore.set(prev => ({ ...prev, theme: 'light' }));

Fx Primitives

All primitives are unstyled. Full attribute pass-through via [key: string]: any.

| Component | Purpose | |---|---| | FxButton | Button with pass-through attrs | | FxInput / FxTextArea | Input primitives with event support | | FxForm | Form with preventDefault default | | FxLink | Anchor primitive | | FxText | Text with optional tag wrapper | | FxImage | Image with onload/onerror | | FxHtml | Raw HTML injection with optional sanitization | | FxFetch | Declarative fetch with children/fallback/error callbacks | | FxAwait | Declarative promise resolution | | FxEffect | Declarative $effect wrapper | | FxReturn | Render delegation with optional reactive deps | | FxShow / FxIf / FxElse | Conditional rendering primitives |

Islands (Partial Hydration)

import { registerIsland, bootstrapIslands } from '@rabbx/fx/client/bootstrap-islands'; 
// Register
registerIsland('Counter', () => import('./islands/Counter.js'));
bootstrapIslands();

// Use in page
<Island islandId="Counter" component={Counter} strategy="visible" props={{ initial: 42 }} />

| Strategy | Behavior | |---|---| | eager / immediate | Hydrate on microtask after paint | | visible | Hydrate when entering viewport (200px margin) | | idle | Hydrate during browser idle time |


🔥 Canonical Syntax Reference

This is the definitive pattern for all @rabbx/fx components:

import { $state, $computed } from '@rabbx/fx/runtime';

interface Todo { id: number; text: string; done: boolean }

export default function TodoList() {
  const todos = $state<Todo[]>([...]);
  const inputVal = $bindable('');
  let nextId = 4;

  const remaining = $computed(() => todos.value.filter(t => !t.done).length);
  const total = $computed(() => todos.value.length);

  function addTodo() { /* ... */ }
  function toggleTodo(id: number) { /* ... */ }
  function removeTodo(id: number) { /* ... */ }

  function renderHeader() {
    return (
      <div class="flex items-center justify-between mb-4">
        <h2 class="text-xl font-bold text-white">Tasks</h2>
        <span class="text-sm font-mono text-gray-500">
          {remaining.value}/{total.value} remaining
        </span>
      </div>
    );
  }

  function strikeTodo(todo: Todo) {
    return (
      <button
        class={`w-5 h-5 rounded border flex items-center justify-center text-xs cursor-pointer transition-colors shrink-0 ${
          todo.done ? 'bg-blue-500 border-blue-500 text-white' : 'border-gray-600 text-transparent hover:border-gray-400'
        }`}
        onclick={() => toggleTodo(todo.id)}
      >{todo.done ? '✓' : ''}</button>
    );
  }

  return (
    <div class="max-w-lg mx-auto p-6 bg-[#1e1f22] border border-white/10 rounded-xl space-y-4">
      <FxReturn value={renderHeader} deps={[remaining.value, total.value]} />

      <div class="flex gap-2">
        <input
          type="text" placeholder="Add a task…"
          class="flex-1 bg-[#0e0e10] border border-white/10 rounded-lg px-4 py-2 text-sm text-white outline-none focus:border-blue-500"
          value={inputVal.value}
          oninput={(e: Event) => { inputVal.value = (e.target as HTMLInputElement).value; }}
          onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter' && inputVal.value.trim()) addTodo(); }}
        />
        <button class="w-10 h-10 rounded-lg bg-blue-600 text-white font-bold cursor-pointer hover:bg-blue-500" onclick={addTodo}>+</button>
      </div>

      <FxReturn value={() => todos.value.length === 0 ? <p class="text-gray-500 text-center py-8 italic">No todos yet.</p> : null} deps={[todos.value.length]} />

      <ul class="space-y-2">
        <For items={todos.value} as="todo">
          <li key={todo.id} class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-white/[0.03] group">
            <FxReturn value={() => strikeTodo(todo)} deps={[todo.done]} />
            <FxReturn value={() => <span class={`flex-1 text-sm ${todo.done ? 'text-gray-500 line-through' : 'text-gray-200'}`}>{todo.text}</span>} deps={[todo.done]} />
            <button class="text-gray-300 hover:text-red-400 text-xs cursor-pointer opacity-0 group-hover:opacity-100 w-5 text-center" onclick={() => removeTodo(todo.id)}>✕</button>
          </li>
        </For>
      </ul>
    </div>
  );
}

📊 Comparison

| Feature | React | SolidJS | Svelte 5 | @rabbx/fx | |---|---|---|---|---| | Reactivity Model | VDOM diffing | Fine-grained signals | Runes (signals) | Fine-grained signals | | Compilation | Runtime createElement | Compile-time transforms | Compile-time runes | Compile-time imperative DOM | | List Rendering | .map() + keys | <For> | {#each} | <For> with O(n) keyed reconciliation | | Conditional | Ternary / && | <Show> | {#if} | <Show>, &&, FxReturn | | Directives | ❌ | ❌ | use: | ✅ use: with reactive params | | Partial Hydration | Server Components | Islands (experimental) | Islands | ✅ Built-in islands | | Runtime Size | ~40kb | ~7kb | ~2kb | ~3kb | | SSR Strategy | Streaming | Async hydration | Async hydration | Replace-on-hydrate | | Context | Provider tree traversal | useContext | Context API | Signal stacks (O(1)) | | TypeScript | JSX types | Generics | Generics | Full inference, no JSX types needed |


🏗️ Architecture

┌─────────────────────────────────────────────────┐
│                  User Writes JSX                 │
└──────────────────────┬──────────────────────────┘
                       │
         ┌─────────────▼──────────────┐
         │   SWC (Strip TypeScript)   │
         └─────────────┬──────────────┘
                       │
         ┌─────────────▼──────────────┐
         │  OXC Parser (TSX AST)      │
         └─────────────┬──────────────┘
                       │
    ┌──────────────────▼──────────────────────┐
    │         MagicString Transform           │
    │                                         │
    │  • Macros → imperative loops/effects    │
    │  • .map/.filter → keyed $effect         │
    │  • use: → action calls + onDestroy      │
    │  • && / ternary → conditional guards    │
    │  • Components → lifecycle wrappers      │
    │  • Imports → auto-injected              │
    └──────────────────┬──────────────────────┘
                       │
         ┌─────────────▼──────────────┐
         │   Pure Imperative JS       │
         │   (_el, setAttr, append,   │
         │    on, $effect, reconcile) │
         └────────────────────────────┘

🤝 Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

💜 Support

If you find @rabbx/fx useful, consider supporting development:


📄 License

MIT © rabbxdev


Built with signals. Compiled to nothing.

⭐ Star on GitHub · ☕ Support on Ko-fi