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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lit-labs/signals

v0.1.1

Published

JavaScript Signals proposal integration for Lit

Downloads

3,374

Readme

@lit-labs/signals

TC39 Signals Proposal integration for Lit.

[!WARNING]

@lit-labs/signals is part of the Lit Labs set of packages – it is published in order to get feedback on the design and may receive breaking changes.

RFC: https://github.com/lit/rfcs/blob/main/rfcs/0005-standard-signals.md

Give feedback: https://github.com/lit/lit/discussions/4779

Overview

@lit-labs/signals integrates the TC39 Signals Proposal with Lit's template system and reactive update lifecycle. Signals used within an element's update lifecycle, such as in a template, will cause the element to re-render when the signal value changes. Signals can also be used for targetted or "pin-point" DOM updates, which can update the DOM without running the entire render() method.

The TC39 Signals Proposal

The TC39 Signals Proposal is a proposal to add standard signals to the JavaScript language.

This is very exciting for web components, since it means that different web components that don't use the same libraries can interoperably consume and produce signals.

It also means that many existing state management systems and observability libraries that might currently each require their own adapter to integrated with the Lit lifecycle, might converge on using standard signals so that we only need one Lit adapter, and eventually no adapter at all as support for signals is directly added to Lit

Why Signals?

Signals have several nice attributes for use with reactive components like Lit:

  1. Signals are an easy way to create shared observable state - state that many elements can use and update when it changes. This is great for things like a game state that many components need to read.

  2. Signals can be individually observed, and when used in a template binding, can be handled so that they only update the DOM their bound to. These targetted DOM updates don't re-render the entire template.

  3. Standard signals are an observability interoperabiliy point that many different libraries can use. Any library that produces signals will work with any standard signal watcher.

  4. Signals can be good for performance. Signals track dependencies and changes so that only signals that miht have changed and have been read are re-computd. This can help perform minimal computations and DOM updates when doing small updates to large signal graphs or UIs.

  5. Signal auto-tracking can reduce the need for component-specific lifecycle APIs. For example, rather than having lifecycle callbacks for when updates have happened, or when specific reactive properties have changed, any code could create a reactive effect that simple accesses the signals it uses, and is automatically re-run when they change.

  6. Signals may allow for interoperable synchronous and batched DOM updates. There are ways to respond to signal changes synchronously but also batched, so if reactive properties were backed by signals, an element could re-render itself once a batch of them had been updated. Elements could take care to update children inside of batches, meaning entire subtrees could be updated synchrously. The batching mechanism isn't standard yet, but could be an extension to the proposal.

Signals are a natural fit for Lit: a LitElement render method is already somewhat like a computed signal in that it is computed based on updates to inputs (reactive properties).

The difference between Lit renders and signals is that in Lit the data flow is push-based, rather than pull-based as in signals. Lit elements react when changes are pushed into them, whereas signals automatically subscribe to the other signals they access. But these approaches are very compatible, and we can easily make elements subscribe to the signals they access and trigger an update with an integration library like this one.

On Proposals and Polyfills

Like all Lit Labs packages, @lit-labs/signals package may change frequently, have serious bugs, or not be maintained as well as Lit's core packages.

Additionally, this package depends on the API defined in the TC39 Signals proposal and directly depends on the Signals polyfill, which add more potential sources of instability and bugs. The proposal may change, and the polfyill may have bugs or serious performance issues. If multiple versions of the polyfill are included on a page, interoperabiilty may fail.

As the Signals proposal and polyfill progress we will update this package. At some point we will remove the dependency on the polyfill and assume the standard signal APIs exist, and pages will have to install the polyfill if needed.

So @lit-labs/signals is not recommended for production use. If you choose to use it, please thouroughly test and check the performance of your components and/or app at scale, with the number of signals and component instances that you expect in real-world usage.

Please file feedback and bugs with the Lit project, the Signals Proposal, and the Signals polyfill a appropriate.

Usage

There are three main exports:

  • The SignalWatcher mixin
  • The watch() directive
  • The html template tag, and withWatch() template tag factory

SignalWatcher

SignalWatcher is the core of signals integration. It's a mixin that makes an element watch all signal accesses during the element's reactive update lifecycle, then triggers an element update when signals change. This includes signals read in shouldUpdate(), willUpdate(), update(), render(), updated(), firstUpdated(), and reactive controller's hostUpdate() and hostUpdated().

This effectively makes the the return result of render() a computed signal.

import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {SignalWatcher, signal} from '@lit-labs/signals';

const count = signal(0);

@customElement('signal-example')
export class SignalExample extends SignalWatcher(LitElement) {
  static styles = css`
    :host {
      display: block;
    }
  `;

  render() {
    return html`
      <p>The count is ${count.get()}</p>
      <button @click=${this.#onClick}>Increment</button>
    `;
  }

  #onClick() {
    count.set(count.get() + 1);
  }
}

Elements should not write to signals in these lifecycle methods or they might cause an infinite loop.

watch() directive

The watch() directive accepts a single Signal and renders its value, subscribing to updates and updating the DOM when the signal changes. This allows for very targeted updates of the DOM, which can be good for performance (but as always, measure!).

import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {SignalWatcher, watch, signal} from '@lit-labs/signals';

const count = signal(0);

@customElement('signal-example')
export class SignalExample extends SignalWatcher(LitElement) {
  static styles = css`
    :host {
      display: block;
    }
  `;

  render() {
    return html`
      <p>The count is ${watch(count)}</p>
      <button @click=${this.#onClick}>Increment</button>
    `;
  }

  #onClick() {
    count.set(count.get() + 1);
  }
}

watch() updates are batched and run in coordination with the reactive update lifecycle. When a watched signal changes, it is added to a batch and a reactive update is requested. Other changes, to reactive properties or signals accessed outside of watch(), are trigger reactive updates as usual.

[!NOTE]

During a reactive update, if there are only updates from watch() directives, then those updates are commited directly without a full template render. If any other changes triggered the reactive update, then the whole template is re-rendered, along with the latest signal values.

This approach preserves both DOM coherence and targeted updates, and coalesces updates when both signals and reactive properties change.

watch() must be used in conjunction with the SignalWatcher mixin.

You can mix and match targeted updates with watch() directive and auto-tracking with SignalWatcher. When you pass a signal directly to watch() it is not accessed in a callback watched by SignalWatcher, so an update to that signal will only cause a targeted DOM update and not an full template render.

html tag and withWatch()

This package also exports an html template tag that can be used in place of Lit's default html tag and automatically wraps any signals in watch().

import {LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {SignalWatcher, html, signal} from '@lit-labs/signals';

const count = signal(0);

@customElement('signal-example')
export class SignalExample extends SignalWatcher(LitElement) {
  static styles = css`
    :host {
      display: block;
    }
  `;

  render() {
    return html`
      <p>The count is ${count}</p>
      <button @click=${this.#onClick}>Increment</button>
    `;
  }

  #onClick() {
    count.set(count.get() + 1);
  }
}

withWatch()

withWatch() is a function that wraps an html tag function with the auto-watching functionality. The html tag exported by @lit-labs/signals is a convenient export of the core lit-html template tag wrapped with withWatch().

withWatch() allows you to compose the signal watching wrapper with other lit-html tag wrappers like Lit's withStatic() utility.

import {html as coreHtml} from 'lit';
import {withStatic} from 'lit/static-html.js';
import {withWatch} from '@lit-labs/signals';

/**
 * A Lit template tag that support static values and pinpoint signal updates.
 */
const html = withWatch(withStatic(coreHtml));

Future Work

This library will change based on feedback from developers. Some existing dieas we have for futher development are:

  • A signal-aware repeat() directive that can update items in a list independently of the entire list.
  • Signal aware when() directive that wraps the condition in a computed signal and watches it.
  • A @property() decorator that creates a signal-backed property that can be watched.
  • An @effect() method decorator that runs a method inside a watched computed signal, and re-runs it when any signal dependencies change. This would be an alternative the the common @observe() feature request.
  • Batched synchronous updates, when using a utility like []batchedEffect()](https://github.com/proposal-signals/signal-utils?tab=readme-ov-file#batched-effects)

Related Libraries

signal-utils

The signal-utils project contains a lot of utilities for building signals-based data models.

Some of these are especially useful for use cases around shared observable state. The signal-backed collections (arrays, maps, and sets) can help address cases where Lit's reactive properties cannot see internal changes to objects.