Chisle
The coding-agent plugin that bills 52% of a bare model across 20 live tasks. Terse persona + tool-output compressor + context diet.
翻訳待ち
Compile-time reactivity for JS
翻訳待ち
Loading the full README.
Read the full README ↗Compile-time reactivity for JS
Documentation: https://silmaril-docs.vercel.app/ (source in website)
npm install --save silmaril
yarn add silmaril
pnpm add silmaril
silmaril works at compile time, so your code must go through its compiler. silmaril/compiler is the compiler, built on oxc-parser and magic-string. silmaril/unplugin runs it in your bundler. The runtime (silmaril, silmaril/store and silmaril/solid) has no dependencies.
silmaril requires Node.js ^22.18.0 || >=24.11.0 and is published as ESM only.
silmaril/unplugin provides the compiler as an unplugin, so it works with Vite, Rollup, Rolldown, esbuild, webpack, Rspack, Rsbuild, Farm and Bun.
// vite.config.js
import { defineConfig } from 'vite';
import silmaril from 'silmaril/unplugin';
export default defineConfig({
plugins: [
silmaril.vite({
filter: {
include: 'src/**/*.{ts,tsx}',
},
}),
],
});
Use silmaril.rollup, silmaril.rolldown, silmaril.esbuild, silmaril.webpack, silmaril.rspack, silmaril.rsbuild, silmaril.farm or silmaril.bun for the other bundlers.
Options:
filter.include and filter.exclude: glob patterns or regular expressions for the modules to compile. By default, every .js, .jsx, .ts and .tsx file (including .mjs, .cjs, .mts and .cts) that contains silmaril is compiled.rollup-plugin-silmaril and vite-plugin-silmaril are deprecated in favor of silmaril/unplugin.
silmaril/compiler compiles one module. Use it to write a plugin for a tool that unplugin does not cover.
import { compile, ready, CompileError } from 'silmaril/compiler';
await ready();
try {
const { code, map } = compile(source, { filename: 'counter.ts' });
} catch (error) {
if (error instanceof CompileError) {
// error.loc has the location, error.frame a code frame
}
}
filename picks the language: .js and .jsx parse as JSX, .ts and .tsx as TypeScript (.mjs, .cjs, .mts and .cts too). It also names the source in the map.sourcemap: false skips the source map. Default true.silmaril comes back unchanged, with map set to null.ready() resolves when compile can run. Await it before the first compile in a browser. There the parser needs @oxc-parser/binding-wasm32-wasip1, an optional peer dependency.$$ defines the reactive boundary in your JS code. A variable declared in $$ is reactive when it is written after its declaration, reads a reactive variable or binds a store. Variables declared inside loops stay plain. $ 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 = 1;
// Log count for changes
$(console.log('Count: ', count));
function multiply() {
// Update count
count *= 100;
}
multiply();
// After some time, this code logs `Count: 100`.
});
$ tracks the variables read in its call. It also tracks the variables read by a local function that it calls by name.
import { $$, $ } from 'silmaril';
$$(() => {
// Create a "reactive" variable
let count = 0;
let prefix = 'Count';
function log(current) {
console.log(`${prefix}: `, current);
}
// This tracks `count`, and `prefix` through `log`
$(log(count));
});
$ can also accept a function expression, and has the same tracking capabilities.
$(() => {
// This tracks `count`
console.log('Count:', count);
});
After its first run, $ runs again only after a write that changes a tracked variable (compared with Object.is). A write of the same value does nothing. Once queued, $ runs even if a later write puts the old value back.
Reads inside a function imported from another module are not tracked: the compiler sees one module at a time.
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
});
onMountonMount 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.
This is the beginning of the README. Read the rest at the original. Read the full README ↗