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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@zahidala/mini-framework

v1.0.24

Published

A minimal framework inspired by React for building web apps with a virtual DOM, state management, routing, and rendering.

Downloads

71

Readme

@zahidala/mini-framework

A minimal framework inspired by React for building web apps with a virtual DOM, state management, routing, and rendering.

📦 Installation

npm install @zahidala/mini-framework
# or
yarn add @zahidala/mini-framework

Or scaffold a new project with:

npx @zahidala/mini-framework

🚀 Getting Started

After installing or creating a new project:

npm run dev

This will start a local development server (using Parcel) so you can begin building your app.

✅ Example Usage

// main.ts
import { h, render } from "@zahidala/mini-framework";

const App = ({ getState, setState }) => {
  const { count } = getState();

  return h(
    "div",
    {},
    h("h1", {}, `Count: ${count}`),
    h("button", { onclick: () => setState({ count: count + 1 }) }, "Increment")
  );
};

const root = document.getElementById("app");
if (root) render(App, { count: 0 }, root);

🔧 API Reference

h(tag, props, ...children)

Used to create an element. Returns a virtual DOM node.

h("div", { id: "main" }, ["Hello"]);

Parameters:

  • tag: string | Function
    HTML tag name (e.g. "div") or a functional component.

  • props: object
    Attributes, properties, or event handlers for the element.

  • children Array<VNode | string | number | boolean | null | undefined> | VNode | string | number | boolean | null | undefined
    Nested h(...) nodes, strings, numbers, booleans, null, or undefined. You can pass a single child or an array of children.

Example:

const MyComponent = () => h("span", {}, ["Hi!"]);
const vnode = h("div", { class: "box" }, ["Hello", MyComponent()]);

render(component, initialState, root, router?)

Renders a reactive component into a DOM root element. Ensure to add this at the end of your index ts file for the component to render.

type Component<T> = (state: {
  getState: () => T;
  setState: (value: T) => void;
}) => VNode;

render<T>(
  component: Component<T>,
  initialState: T,
  root: HTMLElement,
  router?: ReturnType<typeof createRouter>
): void;

Parameters:

  • component: A function that takes getState and setState, and returns a virtual DOM (VNode).

  • initialState: The initial state of the component. An empty object can be passed if state is not required.

  • root: The HTML element to mount the app into.

  • router (optional): A router instance created via createRouter(). Enables re-rendering on route change.

Behavior:

  • Initializes and mounts the virtual DOM.

  • Subscribes to state changes and applies DOM diffs.

  • If a router is provided, listens for navigation updates and re-renders accordingly.

Example:

import { createRouter, h, render } from "@zahidala/mini-framework";

const router = createRouter();

function App({}) {
  return h("span", {}, ["Hello World"]);
}

const root = document.getElementById("app");
if (root) render(App, {}, root, router);

createState<T>(initialValue: T)

Creates a simple reactive state container with getState, setState, and subscribe.

Note: You typically don't need to call createState manually — it's automatically handled by the render function when rendering a component.

const state = createState(0);

state.subscribe((value) => {
  console.log("State changed to:", value);
});

state.setState(42); // Logs: State changed to: 42

const current = state.getState(); // 42

Parameters:

  • initialValue: T — The starting state value.

Returns

An object with:

  • getState(): T: Returns the current state.

  • setState(newValue: T): void: Updates the state and notifies all subscribers (if the value changes).

  • subscribe(listener: (state: T) => void): () => void: Subscribes to state changes (listener is also called immediately once). Returns an unsubscribe function.

createRouter()

Creates a basic client-side router using the History API and supports subscribing to route changes.

This is a simple routing utility for SPAs (Single Page Applications). It does not handle hash-based routing or route matching — it focuses purely on tracking and reacting to path changes.

import { createRouter, h, render } from "@zahidala/mini-framework";

// Create the router instance
const router = createRouter();

// Simple routes-to-components mapping
function App({
  getState,
  setState,
}: {
  getState: () => {};
  setState: (v: {}) => void;
}) {
  const path = router.getRoute();

  return h("main", { class: "container" }, [
    h("nav", {}, [
      h("a", { href: "/", onclick: handleLinkClick("/") }, ["Home"]),
      " | ",
      h("a", { href: "/about", onclick: handleLinkClick("/about") }, ["About"]),
    ]),
    h("hr", {}, []),
    path === "/about"
      ? h("section", {}, ["This is the About page."])
      : h("section", {}, ["Welcome to the Home page!"]),
  ]);
}

// Helper to intercept link clicks and update route
function handleLinkClick(path: string) {
  return (e: Event) => {
    e.preventDefault();
    router.setRoute(path);
  };
}

// Mount to DOM
const root = document.getElementById("app");
if (root) render(App, {}, root, router);

Returns

An object with the following methods:

  • getRoute(): string: Returns the current full path (pathname + search).

  • getSearchParams(): URLSearchParams: Returns a URLSearchParams object based on the current query string.

  • setRoute(path: string): void: Pushes a new route to the browser history and notifies subscribers.

  • subscribe(listener: (path: string) => void): () => void: Subscribes to route changes. The listener is called immediately with the current path and on each subsequent change. Returns an unsubscribe function.

Why Things Work the Way They Work

render() and App State — How It Works & Why

This mini-framework is built around a simple but powerful idea:

A UI is just a function of state and routing, rendered into the DOM using a virtual DOM diff.

Instead of complex abstractions, we intentionally keep the model flat, reactive, and easy to understand.

✅ One Function to Render It All

The render(component, initialState, root, router?) function does three things:

Initial render – Turns your component into virtual DOM, builds real DOM from it, and inserts it into the page.

Reactivity – Whenever you update state using setState(...), it re-runs your component and updates only the changed parts of the DOM.

Routing (optional) – If a router is passed, the UI also re-renders on route changes.

💡 Why One Big State Object?

Instead of using isolated, per-component state (like React’s useState), we keep a single global state object.

Why this design?

  • 🧠 Mental simplicity: There’s only one source of truth — getState(). You don’t have to chase local state in nested components.

  • 🧪 Predictable updates: You can always inspect the full app state. Great for debugging or adding dev tools.

  • 🧩 Functional rendering: The component function becomes a pure-ish transformation of state → UI. It’s testable and side-effect free.

  • 🔄 Easy re-renders: When state or route changes, the component is re-run with fresh state, and only the changed DOM parts are updated.

Think of it like: UI = render(state, path)

✍️ Example: Full-State Updates

function Counter(state: {
  getState: () => { count: number };
  setState: (newState: { count: number }) => void;
}) {
  const { count } = state.getState();

  return h("div", {}, [
    h("p", {}, [`Count is ${count}`]),
    h("button", { onClick: () => state.setState({ count: count + 1 }) }, [
      "Increment",
    ]),
  ]);
}

render(Counter, { count: 0 }, document.getElementById("app")!);

🔁 Why Not Partial setState?

We don’t support merging partial state like React's old this.setState(). Why?

It keeps state updates explicit — every update is a full replacement.

Encourages immutable patterns — which are easier to diff and reason about.

Less internal complexity — no diffing or merging of nested structures.

Of course, helpers like updateState(draft => { draft.count++ }) could be added for convenience later but the core stays simple.

📦 Summary

  • State is a single object — updated completely via setState(newState)

  • Your app is a pure function of state and routing

Renders are triggered by:

  • setState

  • Route changes (if router is passed)

  • It's like React, but flatter, simpler, and easier to reason about

🔧 How the Virtual DOM Works (And Why)

The VDOM is a lightweight, tree-like structure that mirrors the real DOM. Instead of modifying the DOM directly (which is slow and error-prone), we:

  1. Build a virtual representation of what the UI should look like

  2. Compare it with the previous version

  3. Apply minimal updates to the real DOM to match the new version

This strategy is known as diffing and allows us to avoid full DOM re-renders.

🏗 What is a VNode?

A VNode (Virtual Node) looks like this:

{
  tag: "div",
  props: { id: "header", class: "dark" },
  children: [
    { tag: "h1", props: {}, children: ["Hello"] },
    "Just a text node"
  ]
}

We define VNode using a function called h():

const v = h("div", { class: "box" }, [
  h("p", {}, ["Text content"]),
  h("button", { onclick: handleClick }, ["Click me"]),
]);

This is inspired by JSX but works with plain JavaScript.

⚙️ createElement(): Building the Real DOM

The createElement() function turns a VNode into a real DOM element, recursively:

It creates the correct tag

Assigns props like id, class, or style

Attaches event listeners (delegated)

Appends all child nodes

This is used during initial rendering.

🔁 updateElement(): Smart DOM Updating

On state or route changes, the UI is re-rendered virtually. We don’t replace the entire DOM — we diff and patch:

  • If text changes, we replace the text node

  • If tag changes (divspan), we replace the element

  • If props or styles change, we update them

  • If child order changes (especially with keys), we rearrange the DOM

This is what makes your updates fast.

You can think of this like a simplified React reconciliation engine.

🪝 Event Delegation: Why and How

Instead of attaching event listeners to each node directly (which is slow), we use delegated event handling:

  • All listeners are attached to the root (once per event type)

  • We register event handlers by assigning unique data-event-id-* attributes

  • When an event bubbles up, we match it and call the correct handler

This means fewer listeners, less memory use, and easy updates.

🗝️ Keys: Efficient Child Updates

When rendering lists, you should always give items a key prop:

items.map((item) => h("li", { key: item.id }, [item.name]));

This helps the framework know which DOM node maps to which data — avoiding full re-renders when order changes.

🧠 Why This VDOM Design?

  • Simple: No class-based components, no lifecycle headaches

  • Fast enough: Minimal DOM touches, smart diffs

  • Transparent: You can follow every update manually

  • Hackable: Easy to extend, since the logic is clean and unabstracted

🌐 Routing System

This framework includes a minimal, client-side router to manage page navigation and keep the UI in sync with the browser URL — without reloading the page.

✅ Why Not a Full Router?

This router avoids hash-based routing and heavy abstractions. Instead, it:

  • Uses the browser’s native pushState and popstate

  • Keeps the current path in internal state

  • Notifies subscribers of route changes (reactive pattern)

  • Supports search param parsing

No separate "router state" is needed — it's all handled inside a singleton returned by createRouter().

🧱 createRouter(): What It Does

const router = createRouter();

This function sets up the routing logic and returns methods to control or react to navigation.

🧭 router.getRoute()

Returns the current full path (/path?query=1).

const path = router.getRoute(); // "/about?section=team"

🔎 router.getSearchParams()

Returns a URLSearchParams object for reading query parameters:

const params = router.getSearchParams();
const userId = params.get("user");

This is useful for parsing things like ?page=2 or ?tab=profile.

🚀 router.setRoute(path: string)

Changes the URL without reloading the page, and notifies all subscribers.

router.setRoute("/home"); // updates the URL and triggers all listeners

If the path is already the current one, it does nothing.

📡 router.subscribe(callback: (path: string) => void)

Lets you listen for route changes, including manual back/forward navigation.

const unsubscribe = router.subscribe((path) => {
  console.log("Route changed to", path);
});
  • Immediately calls the listener with the current path

  • Returns an unsubscribe() function

This is how your app’s rendering stays in sync with the URL.

📜 History + popstate Support

The router also listens for the browser's native popstate event:

window.addEventListener("popstate", ...)

This ensures Back and Forward buttons correctly trigger route updates.

🧠 Design Philosophy

This router intentionally avoids:

  • Complex route matching (like "/posts/:id")

  • Middleware, guards, loaders, etc.

Instead, it gives you direct control over routing — minimal but functional.

This approach works well for:

  • Small- to medium-scale SPAs

  • Apps that want full control over their routes

  • Developers who value simplicity and explicit behavior