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

silmaril

v0.3.3

Published

Compile-time reactivity for JS

Downloads

14

Readme

silmaril

Compile-time reactivity for JS

NPM JavaScript Style Guide Open in CodeSandbox

Install

npm install --save silmaril
yarn add silmaril
pnpm add silmaril

Features

  • Compile-time reactivity
  • Minimal reactive runtime
  • Auto-memoization
  • Stores

Requirement

Due to the compile-time nature of this library, it requires the use of Babel. silmaril provides a Babel plugin under silmaril/babel.

Usage

Basic reactivity

$$ defines the reactive boundary in your JS code. Any top-level variables (function-scoped) declared in $$ will be treated as "reactive" as possible. $ can be used to asynchronously react to variable changes.

Variable changes and reactions are only limited in $$ (even for nested $$ calls).

import { $$, $ } from 'silmaril';

$$(() => {
  // Create a "reactive" variable
  let count = 0;

  // Log count for changes
  $(console.log('Count: ', count));


  function multiply() {
    // Update count
    count *= 100;
  }

  multiply();
  // After some time, this code logs `Count: 100`.
});

$ will know which variables to track with, but it can only know if the variable is accessed in that same call.

import { $$, $ } from 'silmaril';

$$(() => {
  // Create a "reactive" variable
  let count = 0;
  let prefix = 'Count';

  function log(current) {
    // `prefix` is not tracked
    console.log(`${prefix}: `, current);
  }

  // This only tracks `count`
  $(log(count));
});

$ can also accept a function expression, and has the same tracking capabilities.

$(() => {
  // This tracks `count`
  console.log('Count:', count);
});

$ will only run if the tracked variables have actually changed (except for the first run), which means that it has some "auto-memoization".

Computed variables

If a reactive variable references another, the variable becomes computed, which means that it will re-evaluate everytime the referenced variables changes.

import { $$, $ } from 'silmaril';

$$(() => {
  // Create a "reactive" variable
  let count = 0;

  // Create a "reactive" const variable.
  const message = `Count: ${count}`;

  // This only tracks `message`
  $(console.log(message));

  count = 100; // Logs 'Count: 100'
});

Updates on computed variables are synchronous.

import { $$ } from 'silmaril';

$$(() => {
  let count = 0;
  const message = `Count: ${count}`;
  count = 100; // message = Count: 100
  count = 200; // message = Count: 200
});

Computed variables are also writable if declared with let.

import { $$, $sync } from 'silmaril';

$$(() => {
  let count = 0;
  let message = `Count: ${count}`;
  $sync(console.log('Log', message)); // Log Count: 0
  count = 100; // Log Count: 100
  message = 'Hello World'; // Log Hello World
  count = 200; // Log Count: 200
});

Lifecycles

onMount

onMount can be used to detect once $$ has finished the setup.

import { $$, onMount } from 'silmaril';

$$(() => {
  onMount(() => {
    console.log('Mounted!');
  });
  console.log('Not mounted yet!');
});

onMount can also be used in $, $sync, $composable and computed variables.

onDestroy

$$ returns a callback that allows disposing the reactive boundary. You can use onDestroy to detect when this happens.

import { $$, onDestroy } from 'silmaril';

const stop = $$(() => {
  onDestroy(() => {
    console.log('Destroyed!');
  });
});

// ...
stop();

onDestroy can also be used in $, $sync, $composable and computed variables.

Synchronous tracking

$ is deferred by a timeout schedule which means that $ asynchronously reacts on variable updates, this is so that updates on variables are batched by default (writing multiple times synchronously will only cause a single asynchronous update).

$sync provides synchronous tracking.

import { $$, $, $sync } from 'silmaril';

$$(() => {
  // Create a "reactive" variable
  let count = 0;

  // Create a "reactive" const variable.
  const message = `Count: ${count}`;

  $sync(console.log('Sync', message)); // Logs "Sync Count: 0"
  $(console.log('Async', message));

  count = 100; // Logs "Sync Count: 100"
  count = 200; // Logs "Sync Count: 200"

  // After some time the code ends, logs "Async Count: 200"
});

Stores

Reactivity is isolated in $$, but there are multiple ways to expose it outside $$ e.g. emulating event emitters, using observables, global state management, etc.

silmaril/store provides a simple API for this, and $store allows two-way (or one-way) binding for stores.

import { $$, $, $sync, $store } from 'silmaril';
import Store from 'silmaril/store';

// Create a store
const count = new Store(100);

// Subscribe to it
count.subscribe((current) => {
  console.log('Raw Count:', current);
});

$$(() => {
  // Bind the store to a reactive variable
  let current = $store(count);
  // `const` can also be used as an alternative
  // for enforcing one-way binding
  
  // Tracking the bound variable
  $sync(console.log('Sync Count:', current));
  $(console.log('Async Count:', current));

  // Mutate the variable (also mutates the store)
  current += 100;

  // Logs
  // Sync Count: 100
  // Raw Count: 200
  // Sync Count: 200
  // Async Count: 200
});

$store can accept any kind of implementation as long as it follows the following interface:

  • subscribe(callback: Function): Function: accepts a callback and returns a cleanup callback
  • get(): returns the current state of the store
  • set(state): optional, mutates the state of the store.

Composition

$composable

$composable allows composing functions that can be used in $$, $sync, $, another $composable or computed variables.

import { $$, $sync, $composable, $store, onDestroy } from 'silmaril';
import Store from 'silmaril/store';

// Create a composable
const useSquared = $composable((store) => {
  // Bind the input store to a variable
  const input = $store(store);

  // Create a store
  const squaredStore = new Store(0);

  // Make sure to cleanup the store
  onDestroy(() => squaredStore.destroy());

  // Update the store based on the bound input store
  $sync(squaredStore.set(input ** 2));

  // Return the store
  return squaredStore;
});

$$(() => {
  // Create a store
  const store = new Store(0);

  // Bind it
  let input = $store(store);

  // Track the value of the store
  $sync(console.log('Value', input));

  // Create a "squared" store based on the input store
  // then bind it
  const squared = $store(useSquared(store));

  // Track the squared store
  $sync(console.log('Squared', squared));

  // Update the input store
  input = 100;

  // Logs
  // Count: 0
  // Count: 100
  // Count: 200
});

$ and $sync

Both $ and $sync behave much like $$: variables become reactive, onMount and onDestroy can be used, same goes to other APIs.

import { $$, $, onDestroy } from 'silmaril';

$$(() => {
  let y = 0;
  $(() => {
    let x = 0;

    $(console.log(x + y));

    onDestroy(() => {
      console.log('This will be cleaned up when `y` changes');
    });

    x += 100;
  });
  y += 100;
});

Inspirations/Prior Art

Sponsors

Sponsors

License

MIT © lxsmnsyc