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

rin-lib

v0.3.0

Published

**Rin** is a minimal, TypeScript-first UI library for building reactive browser interfaces with a small runtime and predictable architecture.

Downloads

1,093

Readme

Rin Documentation

Overview

Rin is a minimal, TypeScript-first UI library for building reactive browser interfaces with a small runtime and predictable architecture.

It focuses on:

  • simplicity
  • explicit rendering flow
  • strong typing
  • minimal abstraction
  • minimal virtual DOM

Rin targets developers who want component-based UI without framework overhead.


Motivation

Modern UI frameworks often introduce:

  • virtual DOM overhead
  • hidden lifecycle complexity
  • schedulers
  • large dependency graphs
  • indirection between state and DOM

Rin instead provides:

  • direct DOM rendering
  • explicit execution model
  • predictable updates
  • very small runtime surface
  • full control over rendering behavior

Suitable use cases:

  • internal dashboards
  • Tauri apps
  • tooling interfaces
  • experimental UI systems
  • lightweight SPAs

Core Principles

Functions Are Components

Components are plain functions that return a render function:

function App() {
  return () => <div>Hello</div>
}

No classes. No decorators. No lifecycle APIs.


Transparent Virtual DOM

Rin builds minimal Virtual DOM templates strictly to power local, fast DOM patching.

Note: Rin may migrate entirely to an Ahead-Of-Time (AOT) compiler architecture in the future. This would completely eliminate the Virtual DOM and DOM-patching engines, allowing for true direct-to-DOM updates like Svelte or SolidJS, while preserving our explicit component execution model.

Rendering pipeline:

Component → DOM Nodes

This avoids diffing cost and improves predictability.


Explicit Reactivity

State changes trigger controlled rerenders explicitly.

No hidden scheduler.

No implicit batching.


TypeScript First

Rin is designed around strict typing:

  • JSX typing
  • children typing
  • DOM typing
  • component typing

Architecture

Rendering pipeline:

JSX
 ↓
jsx-runtime
 ↓
element objects
 ↓
renderer
 ↓
real DOM

JSX Runtime

Transforms:

<div>Hello</div>

into:

jsx("div", { children: "Hello" });

Element Structure

Example internal node:

{
  type: "div",
  props: {},
  children: ["Hello"]
}

Renderer

Renderer converts nodes into DOM:

string → TextNode
number → TextNode
element → HTMLElement
component → execute function
array → flatten recursively

Mount Engine

Attach component tree to container:

mount(<App />, document.body);

Comparison With React

| Feature | Rin | React | | ----------------- | ---------- | ---------- | | Virtual DOM | Minimal | Yes | | Scheduler | No | Yes | | Hooks | Optional | Core | | Lifecycle | No | Yes | | Bundle Size | Very small | Large | | Rendering | Direct DOM | Diff-based | | Abstraction Level | Low | Medium | | Control | High | Medium |

Rin prioritizes control over automation.


Component Model

Example:

function Button() {
  return <button>Click</button>;
}

Usage:

function App() {
  return (
    <div>
      <Button />
    </div>
  );
}

Props

Props are standard function parameters:

type Props = {
  title: string;
};

function Header(props: Props) {
  return <h1>{props.title}</h1>;
}

Usage:

<Header title="Dashboard" />

Children

Children pass through JSX normally:

function Card(props) {
  return () => <div>{props.children}</div>;
}

Usage:

<Card>Content here</Card>

Refs

To directly access a physical HTML Node (e.g. for interacting with a <canvas> element or a localized third-party charting library), you can securely bind a function closure directly to the ref prop.

Rin executes the function right after the component paints that node cleanly to the DOM:

function CanvasComponent(props, ctx) {
  let canvasEl = null;

  ctx.onMount(() => {
    // Guaranteed to be physically mapped here because `onMount` runs safely after execution!
    const context = canvasEl.getContext("2d");
    context.fillRect(10, 10, 100, 100);
  });

  return () => <canvas ref={(el) => canvasEl = el} width="200" height="200" />;
}

Rendering & Reactivity

Rin provides three primary APIs for rendering and updating the DOM explicitly.

Initial Mounting

Mount your root application to the DOM:

import { mount } from "rin-lib";

mount(<App />, document.getElementById("app"));

Targeted Rerendering

Rin does not use an automated Virtual DOM scheduler. Instead, reactivity is explicit and localized. You precisely dictate when updates generate patching routines by using rerender:

  1. Rerender by Component Reference: Globally updates all active instances of a specific component function on the page.
import { rerender } from "rin-lib";

function Header() {
  return <header>Local Time: {new Date().toLocaleTimeString()}</header>;
}

// Somewhere else, trigger an update for all <Header /> components:
rerender(Header);
  1. Rerender by Group string: Updates specific elements or components tagged with an explicit group property. Useful for highly specific or targeted updates.
import { rerender } from "rin-lib";

// Component or element rendered with <section group="user-stats" />
rerender("user-stats");
  1. Rerender by Component Context: Renders solely the specific component instance issuing the call. To ensure zero-magic predictability, Rin passes a reliable execution context as the second argument to any component.
function Dropdown(props, ctx) {
  let isOpen = false;

  const toggle = () => {
    isOpen = !isOpen;
    ctx.rerender(); // Triggers update ONLY for this exact dropdown instance
  };

  // Return a closure that handles future re-renders
  return () => (
    <div>
      <button onclick={toggle}>Toggle</button>
      {isOpen && <div>Content</div>}
    </div>
  );
}

Demounting & Cleanup

To prevent memory leaks when components are removed dynamically, Rin provides an explicit unmount API to clean up active trees and unbind listeners.

import { unmount } from "rin-lib";

// Tear down a whole container explicitly (e.g. when unmounting the whole application)
unmount(document.getElementById("app") as HTMLElement);

Lifecycles

While Rin eliminates complex implicit lifecycle scheduling, you will still need to perform side-effects predictably (like data fetching or WebGL rendering) when working with direct DOM mutations.

Because component functions are executed only once to initialize state, your ctx.onMount logic is inherently immune to infinite-loop fetch bugs. Rin saves and executes the inner closure returned by your component on every ctx.rerender:

function APIDataLoader(props, ctx) {
  let data = null;

  // Triggers precisely once after the element first hits the DOM.
  ctx.onMount(() => {
    fetch("/api/data").then(async res => {
      data = await res.json();
      ctx.rerender(); // Trigger local UI update
    });
  });

  // Triggers precisely once when unmount() destroys this node.
  ctx.onUnmount(() => {
    console.log("Cleaning up active subscriptions...");
  });

  // The rendering logic closure dynamically evaluated on every update
  return () => <div>{data ? data : "Loading..."}</div>;
}

Example Counter

function Counter(props, ctx) {
  // Initialization - runs exactly once
  let count = 0;

  const increment = () => {
    count++;
    ctx.rerender(); // rerender self only
  };

  // Rendering - execution closure bound to rerender
  return () => (
    <div>
      <p>{count}</p>
      <button onclick={increment}>Increment</button>
    </div>
  );
}

JSX Runtime Setup

Example tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "rin-lib"
  }
}

Example Project Structure

rin-lib/
 ├── jsx-runtime.ts
 ├── types.ts
 ├── mount.ts
 └── index.ts

When To Use Rin

Recommended for:

  • internal tools
  • dashboards
  • Tauri apps
  • lightweight SPA
  • experimental rendering engines

Not ideal for:

  • plugin ecosystems
  • React-dependent environments
  • large teams requiring framework conventions

Design Philosophy

Rin prioritizes:

clarity > abstraction
control > automation
size > ecosystem

It is a foundation UI library rather than a full framework.