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

minne

v0.2.3

Published

A signals-based web component framework that requires no decorators or build step.

Downloads

16

Readme

Minne

A signals-based web component framework that requires no decorators or build step.

This framework uses lighterhtml and @preact/signals-core to create dead simple web components. It's meant to be able to provide (almost) all the features in Lit with no decorators, simpler API, and abilities like functional components.

Check out a Hello world on stackblitz!

Why "minne"?

I'm from minnesota, and this is a "minne" framework Minnesota outline

Installation:

npm i minne

Using UNPKG:

To import from a CDN, you can use the following line if you're working with modules:

import { Component, html, signal } from "https://unpkg.com/minne";

Check out an example on codepen

Components 🧩

Extend the Component class, and return an html tagged template literal in a render() function.

import { Component, html } from "minne";

class MyComponent extends Component {
  render() {
    return html`<div>Hello, world!</div>`;
  }
}

// Register the web component
MyComponent.define('my-component');

// And use in html:
<my-component></my-component>

Reactivity 👂

Reactivity relies completely on Preact's signals. Signals are included in minne to minimize setup and imports. If a signal is used in the rendering of a minne component, the minne component will react.

This means reactivity depends on using .value in the template to register when re-renders need to take place.

import { Component, html, signal } from "minne";

class MyComponent extends Component {
  counter = signal(0);

  render() {
    return html`
      <div>Counter: ${this.signal.value}</div>
      <button onclick=${() => this.counter.value++}>+</button>
    `;
  }
}

computed, effect, batched, and untracked are all available to use.

This also means that state that are not primitives need to break their referental equality when updating. For example:

// This will not reassign the reference, and so the signal
// has no idea the array has been modified
signal.value.push(item);

// This re-assigns the array reference and will trigger udpates
signal.value = [...signal.value, item];

Shadow root and shadow root options 👤

By default minne components create a shadow root on the host element. This is configurable by setting shadow root configuration object on static shadowRoot:

class MyComponent extends Component {
  static shadowRoot = {
    mode: "closed",
    delegateFocus: true,
    clonable: false,
    serializable: false,
    slotAssignment: "named",
  };

  render() {}
}

If you want no shadow root and want to mount on the host element, you can override getMountPoint()

class MyComponent extends Component {
  getMountPoint() {
    return this;
  }

  render() {} // renders without shadow dom on this (host element)
}

Or event more simply:

class MyComponent extends Component {
  static shadowRoot = false;

  render() {} // renders without shadow dom on this (host element)
}

Attributes 📋

Generally we can leverage this.getAttribute to get an attribute as an initial value:

class MyComponent extends Component {
  text = this.getAttribute("text-attr");
}

To make the text value reactive from the initial attribute, simply wrap in a signal:

class MyComponent extends Component {
  // seeds the initial value from the attribute value on component creation
  text = signal(this.getAttribute("text-attr"));

  // seeds initial value and reacts to attribute changes in DOM
  text = this.attribute("text-attr");

  // If you want the value reflected to the attr when it changes
  text = this.attribute("text-attr", true);
}

Boolean Attributes ✅

We have a special helper method to handle boolean attributes:

class MyComponent extends Component {
  // Value is reactive and reacts to attribute changes in DOM
  disabled = this.booleanAttr("disabled");

  // If you want the value reflected to the attribute when it changes
  disabled = this.booleanAttr("disabled", true);
}

Converters 🔄

Sometimes a property on a component needs to be serialized and deserialized when reactively set and got.

class MyComponent extends Component {
  // Providing a callback to parse the attribute string, and will
  // react to future attribute changes in the DOM
  // But changes will not be reflected
  date = this.converter("date-attr", (date: string) => new Date());

  // Providing a serialize function as well will make the value reflect to
  // the attribute in the DOM when the property changes
  date = this.converter(
    "date-attr",
    (date: string) => new Date(), // parser
    (date: Date) => date.toString(), // serializer
  );
}

Styles 🎨

You can add a stylesheet using the css helper:

class MyComponent extends Component {
  static css = css`
    :host {
      display: block;
      background: orange;
    }
  `;
}

If the component is using a shadow DOM the stylesheet will be adopted at the shadow root level. Otherwise the stylesheet will be adopted on the whole document.

Destroyers 💣

If you need to do some teardown functionality you can overwrite the disconnectedCallback() method (make sure to call super):

class MyComponent extends Component {
  disconnectedCallback() {
    super.disconnectedCallback();

    // Do teardown things
  }
}

An alternative is to register a destroyer. Destroyers are run on disconnected callback, but are more convenient to make. Just be aware when the function being passed needs a specific this.

class MyComponent extends Component {
  constructor() {
    super();

    window.addEventListener("resize", this.handleresize);

    this.addDestroyer(() =>
      window.removeEventListener("resize", this.handleresize),
    );
  }
}

publicReactive(): Note about externally-set reactive properties 🤝

For a signal to continue to be reactive in a consumed component, the property should be set on the .value of the signal:

// someProp is a signal inside of another component
anotherComponent.somePropThatIsASignal.value = this.someSignal.value;

Since properties are being set often by templating libraries or JSX, we can lose reactivity because those templating strategies don't set values on .value. This is true with JSX and html:

class ConsumingComponent extends Minne {
  someSignal = signal("hi");

  //...

  render() {
    return html`<another-component
      .someProp=${this.someSignal.value}
    ></another-component>`;
  }
}

Above, the string is passed in the template to the component and it's no longer a signal, so it loses reactivity. In other words, the html tagged template doesn't know to set this.someSignal.value actually to .someProp.value.

There are two solutions:

  1. You can pass signals if you are in control of how the property is being set
  2. You can "notify" minne componnents that values might be set directly, by declaring a publicReactive property:
class AnotherComponent extends Minne {
  constructor() {
    super();

    this.publicReactive("myProp", "default");
  }

  render() {
    /* ... */
  }
}

The publicReactive() method lets Minne know to set up the property so that if a preact signal or another regular value is set right on the property from outside of the component it will preserve the signal and reactivity. Unfortunately, you do need to pass in the name of the property as you set the property with this method.

Function Components (Experimental 🧪)

These components are only available inside a traditional minne web component and are meant to be internal components. These components have their own lifecycle and signal subscriptions.

import { Component, fc, html, signal } from "minne";

const MyCounter = (startAt: number = 0) => {
  const counter = signal(startAt);

  setInterval(() => counter.value++, 1000);

  return () => html`<div>Counter: ${counter.value}</div>`;
};

class TestComponent extends Component {
  render() {
    return html`<div>Parent Component</div>
      ${fc(MyCounter(77))}`;
  }
}