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

usignal

v0.9.0

Published

A blend of @preact/signals-core and solid-js basic reactivity API

Downloads

728

Readme

µsignal

Downloads Coverage Status build status

Social Media Photo by Carlos Alberto Gómez Iñiguez on Unsplash

A blend of @preact/signals-core and solid-js basic reactivity API, with API and DX mostly identical to @preact/signals-core but extra goodness inspired by solid-js, 803 bytes minified with brotli.

import {signal, computed, effect, batch, Signal} from 'usignal';
// const {signal, computed, effect, batch, Signal} = require('usignal');

signal(0) instanceof Signal;          // true
computed(() => {}) instanceof Signal; // true

effect(
  () => { console.log('fx') },
  void 0,       // optional value to pass along the callback as initial/prev value
  {async: true} // optionally make the effect async: false by default
);

// try every example shown in
// https://github.com/preactjs/signals
// or see test/index.js file to see it in action

Exports

This is a dual module so it works in either CommonJS or ECMAScript module systems.

  • usignal/sync exports with an enforced sync effect
  • usignal/async exports with an enforced async effect
  • usignal in browsers exports usignal/async and usignal/sync in servers or by default
  • usignal/core just exports the effect as callback that accepts an effect and an optionally asynchronous true flag, used by all other exports by default, but you decide if a specific effect should sync or async.
  • the unpkg/usignal default export points at the pre minified es.js file without any enforcement around effect, like usignal/core, so that all effects are sync by default but can be async passing true as second parameter

Current exports are exactly these:

import {
  signal,
  computed,
  effect,
  batch,
  Signal
} from 'usignal';

The Signal export is useful only as brand check for either computed or signal references, but it cannot be used as constructor right away.

Exports - Extra

To allow developers to try and use different patterns there are a few variants of this module, still based on the very same core primitives:

  • usignal/fn, with its */sync and */async variants, where signals are callbacks so that signal() returns a its value, and signal(value) updates its value and return the new one, inspired by S. Computeds do not update anything so computed() returns values. This is a variant around the .value accessor pattern I don't necessarily disike, specially when we'd like to signal that a signal is being observed: effect(() => { mySignal(); })
  • usignal/solid, with its */sync and */async variants, where the module exports createEffect, createMemo, and createSignal, mimicking the behavior (and returned values) as solid-js basic reactivity API. This is handy to compare the two or drop-in usignal in solid-js already based code.

Differently thought ...

  • the default comparison for equality is not based on === but on Object.is. This might be a tiny, hopefully irrelevant, performance penalty, but I feel like guarding NaN cases in reactivity is a step forward to avoid infinite loops out of NaN poisoning some computation. +0 and -0 are less interesting cases to tackle, still these might be fundamental in some case, hence preserved in this moudle.

  • this library has lazy, non side-effecting, computed values, something @preact/signals-core recently introduced and Solid 2.0 is planning to improve.

  • computed accepts an initial value otherwise passed as previous one by default, mimicking solid-js useMemo(fn[, value[, options]]) signature.

  • effect passes along its initial value or the previoulsy returned one. If this is a function though, it runs it before re-executing, passing along its returned value, if any.

  • both signal(value[, options]) and computed(fn[, value[, options]]) accept an optionally options argument, currently implementing equals as explained in silid-js documentation.

  • both signal and computed also implement a thenable interface that can be used to await signal or await computed without needing to use await signal.value or await computed.value out of this poll.

  • both signal and computed also have a toJSON and a valueOf() allowing to implicitly use their values, e.g.

const one = signal(1);
const two = signal(2);
const three = computed(() => one + two);

three.value;  // 3 indeed!

Benchmark

The benchmark currently compares S, solid, preact/signals, and cellx against usignal.

Please note preact is currently not able to solve nested effects so its logic might be simpler than other libraries.

npm run benchmark

current status

Tests

This module is 100% code covered, including ~~the WeakRef~~ possible leaks which is tested through the test/leak.js file, which is part of the build script process.

To use other libraries as reference, I have also added preact/signals-core and solid-js dev-dependencies within the test folder.

Please note preact is currently not able to solve nested effects so its logic might be simpler than other libraries.

The following instructions are needed to test other libraries too:

cd usignal
npm i
cd test
npm i
cd ..

# normal tests
npm test usignal      # shows also code-coverage
npm test solid
npm test preact

# leak test
npm run leak usignal  # calculate leaks via internals
npm run leak solid
npm run leak preact

About the leak test

This file is not meant at all as meaningful benchmark against other libraries, it's simply there to allow me to spot regressions on future updates of the library:

  • ~~there should be zero leaks on signals when a computed reference is garbage collected~~ v0.5.0 removed the WeakRef, computeds go when signals go ... but why?!
  • the amount of used memory should always be lower than the initial one
  • the performance should be competitive compared to others

How to integrate with Lit

You create a following mixin function. Your class inherits from Mixin. Please see the demo for details.

import { effect } from 'usignal';

export function WithUsignal(Base){
  return class WithUsignal extends Base {
    #disposeEffect

    disconnectedCallback() {
      this.#disposeEffect?.();
    }

    performUpdate() {
      if (!this.isUpdatePending) {
        return;
      }

      if (this.#disposeEffect) {
        super.performUpdate();
        return
      }

      this.#disposeEffect = effect(() => {
        this.isUpdatePending = true;
        super.performUpdate();
      });
    }
  };
}