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

@nisli/core

v0.54.1

Published

A reactive web component framework. Signals, templates, dependency injection — no build step, no virtual DOM, no dependencies.

Readme

nisli

A small, reactive web-component framework built on browser standards. Nisli combines fine-grained signals, typed component factories, light-DOM templates, dependency injection, routing, and static generation without a virtual DOM or a framework compiler.

Install

npm install @nisli/core

@nisli/core has no runtime dependencies. It works with TypeScript or plain JavaScript and does not require a framework-specific build step.

Quick start

import { component, html, signal } from '@nisli/core';

const Counter = component('x-counter', () => {
  const count = signal(0);

  return html`
    <button @click=${() => count.value++}>
      Count: ${count}
    </button>
  `;
});

html`${Counter({})}`.mount(document.body);

Signals are read with .value in TypeScript and passed directly to templates. Only the bound text, attribute, class, or child slot updates when a signal changes.

Components that also work as HTML

component() registers a standard custom element and returns its typed factory. Factory callers can pass plain values or signals. The optional attrs map makes selected host attributes live, so plain-HTML consumers and factory consumers share the same component implementation.

import {
  children,
  component,
  html,
  type ComponentAttrs,
} from '@nisli/core';

interface DisclosureProps {
  open?: boolean;
  children?: unknown;
}

const attrs = {
  open: 'boolean',
} satisfies ComponentAttrs<DisclosureProps>;

const Disclosure = component<DisclosureProps, typeof attrs>(
  'x-disclosure',
  (props) => {
    const content = children('Nothing to show');
    return html`
      <section class:open=${props.open}>
        ${content}
      </section>
    `;
  },
  { attrs },
);
html`${Disclosure({ open: true, children: 'Factory content' })}`;
<x-disclosure open>Plain HTML content</x-disclosure>

Declared string, boolean, number, and forwarded attributes react to setAttribute() after mount. children(fallback?) projects factory children, initial light-DOM children, and late parser children through one reactive slot.

Async derivations

Use resource() when a local value is derived asynchronously and does not need query caching or invalidation policy. Only the synchronous source function is tracked; stale work is aborted and cannot overwrite a newer result.

import { component, html, resource } from '@nisli/core';

const Markdown = component<{ content: string }>('x-markdown', (props) => {
  const rendered = resource(
    () => props.content.value || undefined,
    (content, signal) => renderMarkdown(content, { signal }),
  );

  return html`<article html:inner=${rendered.data}></article>`;
});

data, loading, and error are readonly signals. refresh() reruns the current source; component teardown disposes automatically, while standalone callers can use dispose(). Returning undefined from the source disables the resource and clears its current state.

Core capabilities

  • Fine-grained reactivity: signal, computed, effect, untrack, flush, and awaitable tick.
  • Typed web components: composition-style synchronous setup, signal-backed props, typed factories, and live attribute declarations.
  • Safe light-DOM templates: signal bindings, typed events and modifiers, class:*, trusted html:inner, refs, and dynamic HTML tags through el().
  • Stable control flow: lazy when() branches and keyed each() lists that preserve focus, scroll position, and component state.
  • Two scopes of dependency injection: app-wide singleton services with inject/provide, and portal-safe subtree state with createContext.
  • Lifecycle and resilience: onMount, onCleanup, useHostEvent, automatic effect/event cleanup, move-resilient disconnects, and component error boundaries.
  • Async work: resource handles local async derivations; query and QueryClient add shared caching, invalidation, and reactive refetching.
  • Typed application events with Emitter and automatic component cleanup.

Ecosystem

The Nisli repository contains four independently versioned packages and the site that exercises them together:

  • @nisli/core — component authoring and browser runtime.
  • @nisli/router — one typed route catalog for browser, Vite, static builds, and edge/Worker matching. It includes codecs, redirects, managed SEO metadata, render-separated catalogs, and accessible outlets.
  • @nisli/ssg — renders normal Nisli templates and the shared application router to static output.
  • @nisli/ui — “shadcn for Nisli”: a CLI and source registry that copy accessible, Tailwind v4 components into your project.
  • packages/www — the private nisli.dev application and end-to-end integration surface.

Source-owned UI components

npm install -D @nisli/ui tailwindcss tw-animate-css
npx nisli-ui init
npx nisli-ui add button dialog tabs

Import tailwindcss, tw-animate-css, and the copied nisli-ui/styles/theme.css in that order. The copied ui-* components support typed Nisli factories and plain HTML; your application owns the resulting source.

One router across environments

import { html } from '@nisli/core';
import { defineRouter, route } from '@nisli/router';

export const AppRouter = defineRouter({
  home: route('/', {
    metadata: { title: 'Home' },
    render: () => html`<h1>Home</h1>`,
  }),
});

Mount AppRouter({}) in the browser, pass the same router to nisliRoutes(AppRouter) for Vite direct-route fallback, and pass it to buildStaticSite({ router: AppRouter }) for static output. Larger applications can author an environment-neutral catalog from @nisli/router/catalog and attach client renderers later with bindRenders().

Vite HMR

import { defineConfig } from 'vite';
import { nisliHmr } from '@nisli/core/vite-hmr';

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

The development-only plugin remounts an edited component in place without a full page reload.

Architecture decisions live in the docs/adr directory. Package-specific usage and release history live beside each package.

Inspiration

Nisli stands on the shoulders of React's component model, Solid's fine-grained reactivity, Lit's web components and templates, Angular's dependency injection, and Vue's composition-style authoring.

License

MIT