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

@hedystia/view

v2.4.0

Published

Reactive UI engine — fine-grained signals, no Virtual DOM, real DOM nodes

Readme

@hedystia/view

Reactive UI engine — fine-grained signals, no Virtual DOM, real DOM nodes.

Installation

bun add @hedystia/view

Quick Start

Configure your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@hedystia/view"
  }
}
import { sig, mount } from "@hedystia/view";

function Counter() {
  const [count, setCount] = sig(0);

  return (
    <div style={{ padding: "16px" }}>
      <h1>Counter: {count}</h1>
      <button type="button" onClick={() => setCount((c) => c + 1)}>+</button>
    </div>
  );
}

mount(Counter, document.getElementById("root")!);

Components run once. Reactivity comes from signal accessors or wrapping signal reads in () => ... functions.

Signals

import { sig, val, set, update, memo, batch, peek, untrack } from "@hedystia/view";

// Traditional API
const count = sig(0);
count();    // Read (now callable directly!)
val(count); // Read (tracked)
set(count, 5); // Write
update(count, (prev) => prev + 1); // Update

// Destructured API (Preferred)
const [value, setValue] = sig(0);
value(); // Read
setValue(5); // Write
setValue((prev) => prev + 1); // Update

// Universal val()
val(5);       // 5
val(value);   // 0
val(() => 1); // 1

// Derived / computed
const doubled = memo(() => value() * 2);

// Batch multiple updates into one reactive cycle
batch(() => {
  set(a, 1);
  set(b, 2);
});

// Read without tracking
peek(count);

// Run without tracking
untrack(() => val(count));

JSX reactive patterns

function App() {
  const count = sig(0);
  const doubled = memo(() => val(count) * 2);

  return (
    <div>
      {/* Reactive text — wrap in () => */}
      <span>{() => val(count)}</span>
      <span>Doubled: {() => val(doubled)}</span>

      {/* Reactive class — pass a function, signal, or array */}
      <div class={["base", active, () => "reactive"]} />

      {/* Reactive classList — object with boolean values (static or reactive) */}
      <div classList={{ active: isActive, "is-loading": loading }} />

      {/* Reactive style — pass a function */}
      <div style={() => ({ color: val(count) > 5 ? "red" : "blue" })}>
        Dynamic color
      </div>

      {/* Reactive prop — pass a function */}
      <input value={() => `Count is ${val(count)}`} />

      {/* Reactive list — function child returning array */}
      <ul>
        {() => val(items).map((item) => (
          <li>{item.name}</li>
        ))}
      </ul>

      {/* Events */}
      <button type="button" onClick={() => set(count, val(count) + 1)}>+</button>
    </div>
  );
}

Flow Components

@hedystia/view provides built-in components for control flow. Since there is no Virtual DOM, these components ensure that only the necessary parts of the DOM are updated efficiently.

Pro Tip: Use functions {() => <Component />} inside flow components to ensure they are created and cleaned up correctly within the reactive scope.

Show (Conditional Rendering)

import { sig, mount, Show } from "@hedystia/view";

function App() {
  const [loggedIn, setLoggedIn] = sig(false);

  return (
    <div>
      <button type="button" onClick={() => setLoggedIn(!loggedIn())}>
        {() => loggedIn() ? "Log out" : "Log in"}
      </button>

      <Show
        when={loggedIn}
        fallback={<p>Please log in to continue.</p>}
      >
        <div>
          <h2>Welcome back!</h2>
          <p>You are logged in.</p>
        </div>
      </Show>
    </div>
  );
}

For (Keyed List)

import { sig, val, mount, For } from "@hedystia/view";

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

function TodoList() {
  const [todos, setTodos] = sig<Todo[]>([
    { id: 1, text: "Learn signals" },
    { id: 2, text: "Build an app" },
  ]);

  return (
    <ul>
      <For each={todos} key={(todo) => todo.id}>
        {(todo, index) => (
          <li>
            {() => `${index + 1}. ${todo.text}`}
          </li>
        )}
      </For>
    </ul>
  );
}

Switch / Match (Multiple Branching)

import { sig, mount, Switch, Match } from "@hedystia/view";

function StatusView() {
  const [status, setStatus] = sig("loading");

  return (
    <Switch fallback={<p>Unknown state</p>}>
      <Match when={() => status() === "loading"}>
        <p>Loading data...</p>
      </Match>
      <Match when={() => status() === "success"}>
        {() => <SuccessContent />}
      </Match>
    </Switch>
  );
}

Portal (Out-of-tree Rendering)

import { Portal } from "@hedystia/view";

function Modal() {
  return (
    <Portal mount={document.body}>
      <div class="modal-overlay">
        <div class="modal-content">Hello from Portal!</div>
      </div>
    </Portal>
  );
}

Context

Context provides a way to share data through the component tree without passing props manually.

import { ctx, use, sig, mount } from "@hedystia/view";

const ThemeCtx = ctx("light");

function App() {
  const [theme, setTheme] = sig("dark");

  return (
    <ThemeCtx.Provider value={theme}>
      <div>
        <button type="button" onClick={() => setTheme(theme() === "dark" ? "light" : "dark")}>
          Toggle Theme
        </button>
        <ThemeDisplay />
      </div>
    </ThemeCtx.Provider>
  );
}

function ThemeDisplay() {
  const currentTheme = use(ThemeCtx);
  return <div>Current theme is: {currentTheme}</div>;
}

Store

Nested reactive state with fine-grained updates:

import { store, val, set, patch } from "@hedystia/view";

const app = store({
  user: { name: "guest", role: "viewer" },
  theme: "dark",
  count: 0,
});

function Profile() {
  return (
    <div>
      <span>{() => val(app.user.name)}</span>
      <button type="button" onClick={() => set(app.theme, "light")}>Light mode</button>
      <button type="button" onClick={() => patch(app.user, { name: "alice", role: "admin" })}>
        Login
      </button>
    </div>
  );
}

Effects & Watchers

import { effect, on, watch } from "@hedystia/view";

// Run a side effect whenever dependencies change (eager)
effect(() => {
  console.log("Count is now:", count());
});

// Run with access to previous value (untracked callback)
on(count, (current, prev) => {
  console.log(`Changed from ${prev} to ${current}`);
});

// Watch a signal or accessor directly
watch(count, (val) => {
  console.log("Updated:", val);
});

Data Fetching

import { sig, val, set, load, action } from "@hedystia/view";

const userId = sig(1);

const user = load(
  () => val(userId),
  async (id) => {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  }
);

function UserCard() {
  return (
    <div>
      {() => {
        if (val(user.loading)) return <span>Loading...</span>;
        if (val(user.error)) return <span>Error: {val(user.error)!.message}</span>;
        return <span>{val(user.data)?.name}</span>;
      }}
    </div>
  );
}

API Reference

| Category | Functions | |----------|-----------| | Signals | sig, val, set, update, memo, batch, peek, untrack, adopt | | Store | store, patch, reset, snap | | Effects | effect, on, once, watch, watchAll | | Lifecycle | onMount, onCleanup, onReady | | Context | ctx, use | | Fetch | load, action | | Flow | Show, For, Index, Switch, Match, Portal, Suspense, ErrorBoundary | | Render | mount, renderToString | | Text | prepare, layout, reactiveLayout | | Style | style, merge, toCssString | | Scheduler | tick, nextFrame, forceFlush |

License

MIT