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

lit-view

v0.1.1

Published

Stateful views for lit-html: a small lifecycle base over AsyncDirective

Readme

lit-view

A view that works like a custom element, without being one.

lit-html templates are plain functions — enough until a view has to hold something with a lifetime: a service subscription, a timer, an owned DOM node. The usual fix is to promote the view to a custom element for its lifecycle callbacks, and with it take on a tag name, a registry entry, shadow DOM, and a component boundary. lit-view gives you just the lifecycle: connect and acquire, disconnect and release, re-render yourself — in a class you call like a function inside any template. It is a small base over lit-html's own AsyncDirective, four lifecycle members and nothing else.

npm install lit-view lit-html

Example

import { html } from "lit-html";
import { view, View } from "lit-view";

class SkillList extends View {
  #unsubscribe?: () => void;

  connected()    { this.#unsubscribe = service.subscribe(() => this.render()); }
  disconnected() { this.#unsubscribe?.(); this.#unsubscribe = undefined; }

  template() { return html`…derived from current state…`; }
}

export const SkillListView = view(SkillList);

// elsewhere, in any template, at any depth:
html`<section>${SkillListView()}</section>`;

The view subscribes when it enters the page, re-renders itself whenever the service notifies, and unsubscribes when it leaves. The host template just calls a function.

Lifecycle

view(Class) turns the subclass into that template-callable. lit creates one instance per template position and reuses it for every later render at that position — which is what makes instance state meaningful.

A subclass defines up to four members:

  • connected() — the view is live: acquire. Subscribe, start timers. Runs on first render, and again each time the view is reattached (a keyed list move, a cache() swap back in), so acquisition must be re-runnable.

  • disconnected() — the view is paused or gone: release. Fires when the view's DOM leaves the document. It may be followed by connected() again.

  • template(...args) — derive output from the latest host arguments; return any lit-renderable value. The default renders nothing, for views that paint entirely by hand.

  • this.render(t?) — commit. Called bare, it commits template() with the latest host arguments; render(t) commits t directly. Safe to call from anywhere — subscriptions, timers, event handlers. While the view is detached it is a no-op, and during a host render the host's own result carries, so nothing double-commits.

Host arguments are typed on the class: class Row extends View<[label: string, count: number]> receives RowView("a", 1) in template.

reconnected() is used by the base itself — do not override it.

Guarantees

  • connected() runs exactly once per period of attachment; ordinary host re-renders never repeat it.
  • After a reattachment, the committed output reflects current state with no subclass code. If the same render pass re-renders the view, that render carries it; otherwise the base commits once in a microtask. A restored view cannot show stale state.
  • render() never throws for lifecycle reasons — detached and mid-render calls are no-ops, including calls triggered from inside connected().

Owning a DOM node

For identity-critical content — a live iframe, a hand-managed canvas — create the node once, mutate it, and return the same node every time:

class Frame extends View<[src: string]> {
  #iframe?: HTMLIFrameElement;
  template(src: string) {
    this.#iframe ??= document.createElement("iframe");
    if (this.#iframe.src !== src) this.#iframe.src = src;
    return this.#iframe;
  }
}

lit dirty-checks the committed value by identity, so returning the same node is a no-op: the iframe survives host re-renders with its document intact.

What it is not

No scheduling or batching — commits are synchronous, the one exception being the reconnect catch-up, deferred one microtask. No reactive properties — arguments arrive from the host, and shared state belongs to whatever owns it. No shadow DOM, no styles, no element identity: if a view needs to be a real custom element, make one. This class is for views that don't.