Compile-time reactivity for JS

Overview

silmaril

Compile-time reactivity for JS

NPM JavaScript Style Guide

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

License

MIT © lxsmnsyc

You might also like...
Compile Jade templates to React.DOM expressions

jade-react Compile Jade templates to React de-sugared JSX. .container-fluid.readme .row h1= this.storeName ul each product in this.produ

Compile Jade templates into React de-sugared JSX with Gulp

Compile Jade templates to React.DOM Uses jade-react to compile templates to React.DOM directives as part of your Gulp build process. The files are kin

Online React Editor and IDE: compile, run, and host React apps
Online React Editor and IDE: compile, run, and host React apps

react-add-subtract Create a simple React application and learn how to manage the state and render JSX You need to provide an input which accept number

react  compile tool -  Prefix named Luban, salute the God of craftsman
react compile tool - Prefix named Luban, salute the God of craftsman

Prefix named Luban, salute the God of craftsman usage react build tool, Support Typescript yarn add luban-pack --save-dev npm i luban-pack --

A basic movie rating application which created with using Redux Toolkit, Axios for API calls, React Router DOM for Routing and Node-SASS for my SCSS compile to CSS..

A basic movie rating application which created with using Redux Toolkit, Axios for API calls, React Router DOM for Routing and Node-SASS for my SCSS compile to CSS..

A basic movie rating application which created with using Redux Toolkit, Axios for API calls, React Router DOM for Routing and Node-SASS for my SCSS compile to CSS

A basic movie rating application which created with using Redux Toolkit, Axios for API calls, React Router DOM for Routing and Node-SASS for my SCSS compile to CSS..

genji is A small vue state management framewok by vue3 reactivity.
genji is A small vue state management framewok by vue3 reactivity.

genji is A small vue state management framewok by vue3 reactivity. Why calls genji ? It's inspired by Overwatch. Genji flings prec

Reactivity layer for Alpine 2.x

alpine-reactive This package provides a reactivity layer for Alpine 2.x. Problem When you create a component that uses a value defined outside of it,

basic implementation of the Vue 3 reactivity engine - from scratch

Vue 3 Reactivity This material was created by Marc Backes in order to show how reactivity is solved in Vue 3. It contains a basic implementation of th

Meteor Reactivity for your React Native application :)

react-native-meteor Meteor-like methods for React Native. If you have questions, you can open a new issue in the repository or ask in the our Gitter c

A tiny ( 1KB) reactivity library

influer A tiny ( 1KB) reactivity library yarn add influer Introduction influer is a tiny reactivity library. Exported in ESM, CJS, and IIFE, all 1K

A reactive filesystem interface based on Vue 3 reactivity system.

A reactive filesystem interface based on Vue 3 reactivity system.

Create reusable components with Alpine JS reactivity 🦑

Apline JS Component Alpine JS plugin x-component allows you to create reusable components, sprinkled with Alpine JS reactive data 🧁 Example 👀 Page W

Framework frontend Javascript yg dibuat untuk belajar lebih memahami tentang sistem reactivity pada framework2 frontend modern.

Fremwok-Fremwokan Framework frontend Javascript yg dibuat untuk belajar lebih memahami tentang sistem reactivity pada framework2 frontend modern. jika

Solidjs-meteor-data - Helpers for combining SolidJS and Meteor reactivity

solidjs-meteor-data This package provides helper functions for combining the rea

A high-performance framework with fine-grained observable-based reactivity for building rich applications

A high-performance framework with fine-grained observable-based reactivity for building rich applications

Toy Level Reactivity like Vue 3.x

reactivity Reactivity like Vue 3.x Features Typescript JSX Support Components and Fragment Dependency-Collect Reactivity Reactive Object based on ES6

Solid.js reactivity patterns for classes, and class components.

Tools for class-based reactivity powered by Solid.js, and for using classes as Solid components

A mini version of Vue 3 with reactivity, runtime, and compiler modules

A mini version of Vue 3 with reactivity, runtime, and compiler modules

Owner
Alexis H. Munsayac
I do everything for fun
Alexis H. Munsayac
basic implementation of the Vue 3 reactivity engine - from scratch

Vue 3 Reactivity This material was created by Marc Backes in order to show how reactivity is solved in Vue 3. It contains a basic implementation of th

Marc Backes 13 Oct 6, 2022
A tiny ( 1KB) reactivity library

influer A tiny (< 1KB) reactivity library yarn add influer Introduction influer is a tiny reactivity library. Exported in ESM, CJS, and IIFE, all < 1K

Tom Lienard 7 Jul 13, 2022
A reactive filesystem interface based on Vue 3 reactivity system.

A reactive filesystem interface based on Vue 3 reactivity system.

Guillaume Chau 37 Oct 8, 2022
Toy Level Reactivity like Vue 3.x

reactivity Reactivity like Vue 3.x Features Typescript JSX Support Components and Fragment Dependency-Collect Reactivity Reactive Object based on ES6

Hydrogen 4 Oct 17, 2022
A lightweight reactivity API for other UI libraries to be built on top of.

This is a tiny (~850B minzipped) library for creating reactive observables via functions. You can use observables to store state, create computed properties (y = mx + b), and subscribe to updates as its value changes.

Maverick 512 Nov 26, 2022
Fire7 is a small library that implements real-time data binding between Firebase Cloud Firestore and your Framework7 app.

Fire7 is a small library that implements real-time data binding between Firebase Cloud Firestore and your Framework7 app.

Sergei Voroshilov 6 May 15, 2022
RTConnect is an easy-to-use React component library that enables developers to set up live, real-time video calls, between multiple connected peers on different computers

RTConnect is an easy-to-use React component library that enables developers to set up live, real-time video calls, between multiple connected peers on different computers in a React app.

OSLabs Beta 63 Nov 21, 2022
Compile time interactive, responsive and theming utilities for React Native.

Compile time interactive, responsive and theming utilities for React Native.

Nishan 35 Nov 21, 2022
Compile-time atomic CSS library.

pieces-js Compile-time atomic CSS library. Heavily inspired by stylex and linaria. Install Usage Dynamic styles Setup with bundler with Next.js with V

Yunfei He 2 Mar 9, 2022
A familiar and performant compile time CSS-in-JS library for React.

Compiled A familiar and performant compile time CSS-in-JS library for React. Get started now ➚ Usage import { styled, ClassNames } from '@compiled/rea

Atlassian Labs 1.8k Dec 2, 2022