Skip to content

fernandosalves/micron-js

Repository files navigation

micron

A next-generation web component framework. Not an evolution of what exists — a rethink from first principles.

npm version npm downloads


Installation

Quick Start

Install the core framework:

npm install @micronjs/core

Individual Packages

# Core framework
npm install @micronjs/core

# Optional packages
npm install @micronjs/css
npm install @micronjs/router
npm install @micronjs/ai
npm install @micronjs/worker
npm install @micronjs/wasm
npm install @micronjs/islands

Usage Example

import { $, $computed, component, el, text, each, when, on, bind } from '@micronjs/core';
import { cssSignal } from '@micronjs/css';

export default component('x-todo', {
  props: { title: String },
  
  setup({ title }) {
    const items = $<string[]>([]);
    const input = $('');
    const filter = $<'all' | 'done'>('all');

    const add = () => {
      if (!input().trim()) return;
      items.update(list => [...list, input()]);
      input.set('');
    };

    const filtered = $computed(() => 
      filter() === 'done' ? items().filter(i => i.startsWith('✓')) : items()
    );

    // CSS Custom Property binding
    cssSignal(document.documentElement, {
      '--todo-accent': $computed(() => items().length > 0 ? '#4f46e5' : '#94a3b8'),
    });

    return el('div', { class: 'todo' }, [
      el('h1', text(title)),
      el('div', { class: 'input-row' }, [
        el('input', { ...bind(input), placeholder: 'Add item…' }),
        el('button', { ...on('click', add) }, text('Add')),
      ]),
      el('ul', each(filtered, (item, i) => 
        el('li', { key: i, class: 'item' }, text(item))
      )),
      when(
        $computed(() => items().length === 0),
        el('p', { class: 'empty' }, text('Nothing yet.'))
      ),
    ]);
  },
});

The Four Bets

Bet What exists today What X does
WASM reactive core JS signal graphs (GC, JIT variance) Rust compiled to WASM — linear memory, zero GC, predictable sub-μs propagation
SharedArrayBuffer signals postMessage to share state between threads Signals live in SAB — atomic reads/writes, zero serialization cost
Off-main-thread rendering All frameworks run on the main thread RenderWorker owns the reactive runtime; main thread only applies DOM patches
Binary resumability JSON state serialization + constructor re-run WASM memory snapshot → client restores and is immediately interactive

Package Map

@micronjs/core    — Signals ($, $computed), scheduler, el() hyperscript, component(), context
@micronjs/worker  — RenderWorker, DomPatcher, binary PatchRingBuffer protocol
@micronjs/wasm    — Rust reactive core (lib.rs) + TS fallback + auto-loader
@micronjs/css     — cssSignal(), cssVar(), defineTheme(), adoptedStyleSheet()
@micronjs/router  — XRouter, defineRoute<TParams, TData>(), typed navigation
@micronjs/ai      — stream(), infer(), reactiveStream() — AI-native streaming primitives
@micronjs/islands — captureSnapshot(), injectSnapshot(), resumeFromDOM() — binary resumability

Quick Start

import { $, $computed, component, el, text, each, when, on, bind } from '@micronjs/core';
import { cssSignal } from '@micronjs/css';

export default component('x-todo', {
  props: { title: String },

  setup({ title }) {
    const items  = $<string[]>([]);
    const input  = $('');
    const filter = $<'all' | 'done'>('all');

    const add = () => {
      if (!input().trim()) return;
      items.update(list => [...list, input()]);
      input.set('');
    };

    const filtered = $computed(() =>
      filter() === 'done'
        ? items().filter(i => i.startsWith('✓'))
        : items()
    );

    // CSS Custom Property binding — zero per-element JS
    cssSignal(document.documentElement, {
      '--todo-accent': $computed(() => items().length > 0 ? '#4f46e5' : '#94a3b8'),
    });

    return el('div', { class: 'todo' }, [
      el('h1', text(title)),
      el('div', { class: 'input-row' }, [
        el('input', { ...bind(input), placeholder: 'Add item…' }),
        el('button', { ...on('click', add) }, text('Add')),
      ]),
      el('ul', each(filtered, (item, i) =>
        el('li', { key: i, class: 'item' }, text(item))
      )),
      when(
        $computed(() => items().length === 0),
        el('p', { class: 'empty' }, text('Nothing yet.')),
      ),
    ]);
  },
});

Signals — SharedArrayBuffer mode

When Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers are set (required for SharedArrayBuffer), signals store scalar values directly in shared memory. A signal write is one Atomics.store. Workers can read signal values without postMessage.

import { $, sabAvailable, signalVersion } from '@micron/core';

const count = $(0);

console.log(sabAvailable()); // true if COOP/COEP set

// In a compute worker — reads count without postMessage:
// const val = Atomics.load(sharedBuffer(), signalSlot(count));

count.set(5);         // Atomics.store into SAB slot
count.update(n => n + 1); // read-modify-write
count.peek();         // untracked read
count.map(n => n * 2); // derived ReadonlyXSignal<number>

Falls back to plain JS closures transparently when SAB is unavailable.


Off-Main-Thread Rendering

import { RenderWorker, DomPatcher, registerNode } from '@micron/worker';

// Main thread — only applies DOM patches
const worker  = new RenderWorker('/x-render-worker.js');
const patcher = new DomPatcher(worker.ringBuffer);
registerNode(0, document.getElementById('app')!);
patcher.start();

await worker.mount('x-todo', 'app', { title: 'My Todos' });

// Everything else (signal graph, effects, reconciliation) runs in the worker.
// The main thread budget is ~0% framework, ~100% paint.

Runtime modes

  • Synchronous (dev / fallback)component() mounts and updates the DOM directly on the main thread. This path works everywhere (no COOP/COEP headers). Perfect for Storybook, quick demos, and environments where SharedArrayBuffer is unavailable.
  • Worker (production) — the RenderWorker evaluates the EdNode tree, diffs keyed lists, and writes DOM patches into the ring buffer; the main thread’s DomPatcher simply applies them. The component API is identical: the only difference is how you bootstrap the app.

Switching between the two modes is a build-level decision. Local development can rely on the synchronous path, while production bundles wire up the worker entry point and load COOP/COEP headers for SAB-backed signals.

Worker quickstart checklist

  1. Main thread bootstrap

    import { RenderWorker, DomPatcher, registerNode } from '@micron/worker';
    
    const worker  = new RenderWorker(new URL('/x-render-worker.js', import.meta.url));
    const patcher = new DomPatcher(worker.ringBuffer);
    registerNode(0, document.getElementById('app')!);
    patcher.start();
    
    // Optional: wire low-latency notifications
    worker.notifyPort.onmessage = () => patcher.drainSync?.();
    
    await worker.mount('x-app', 'app', { initialCount: 5 });
  2. Worker entry (x-render-worker.ts)

    import { RenderWorkerRuntime } from '@micron/worker';
    import '../dist/components/x-app.js'; // ensure components are registered
    
    RenderWorkerRuntime.init();
  3. Headers + hosting — serve the bundle with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp so SAB-backed signals and the patch ring buffer are shared between threads.

  4. Testing parity — run Storybook (synchronous path) for ergonomics, then run E2E smoke tests that boot the worker build to ensure parity before shipping.


AI Streaming

import { stream, infer } from '@micron/ai';
import { el, text, when, component, $ } from '@micron/core';

component('x-chat-reply', {
  props: { prompt: String },
  setup({ prompt }) {
    const reply = stream(
      fetch('/api/chat', {
        method: 'POST',
        body: JSON.stringify({ prompt: prompt() }),
      })
    );

    return el('div', { class: 'reply' }, [
      text(reply.text),                // updates per-token, zero re-render
      when(reply.loading, el('span', { class: 'cursor' }, text('▌'))),
      when($computed(() => reply.error() !== null),
        el('p', { class: 'error' }, text(reply.error as any))
      ),
    ]);
  },
});

Works with OpenAI SSE, WebSocket, ReadableStream, EventSource, and any AsyncIterable<string>.


Typed Router

import { defineRoute, XRouter, buildPath } from '@micron/router';
import { z } from 'zod'; // optional schema

const userRoute = defineRoute({
  path:      '/users/:id',
  schema:    z.object({ id: z.string().min(1) }),
  loader:    ({ id }) => fetchUser(id),
  component: ({ params, data, loading }) =>
    when(loading, el('span', text('Loading…')), userCard(params, data)),
});

const router = new XRouter({ routes: [userRoute.def] });
await userRoute.go(router, { id: '42' }); // TypeScript error if params wrong

Binary Resumability (SSR)

// Server
import { renderIsland, captureSnapshot, injectSnapshot } from '@micron/islands';
import { createReactiveCore } from '@micron/wasm';

const core = createReactiveCore();
// ... run SSR render ...
const snap = captureSnapshot(core);
const page = injectSnapshot(ssrHtml, snap);

// Client — zero re-render, zero re-parse
import { resumeFromDOM } from '@micron/islands';
const { core, signalValues } = await resumeFromDOM();
// App is interactive immediately

Reactive CSS

import { cssSignal, cssVar, defineTheme } from '@micron/css';
import { $ } from '@micron/core';

const $theme = $<'light' | 'dark'>('light');

// All CSS rules using these vars update in one browser-native pass
cssSignal(document.documentElement, {
  '--bg':    $theme.map(t => t === 'dark' ? '#0f172a' : '#fff'),
  '--fg':    $theme.map(t => t === 'dark' ? '#f1f5f9' : '#111'),
  '--accent': $('4f46e5'),
});

// In CSS:
// body { background: var(--bg); color: var(--fg); }

$theme.set('dark'); // instant, zero JS per element

WASM Core (Rust)

The reactive graph is implemented in Rust (packages/wasm/src/lib.rs) and compiled to WASM via wasm-pack. The TypeScript fallback (packages/wasm/src/fallback.ts) provides identical semantics when WASM is unavailable.

cd packages/wasm
cargo install wasm-pack
wasm-pack build --target web --out-dir ./pkg

Comparison

Feature React Solid Svelte Qwik X
Reactive primitive Hook (re-render) Signal (fine-grained) Rune (compiled) Signal Signal (WASM+SAB)
Cross-thread state postMessage postMessage N/A postMessage SharedArrayBuffer
Main thread work High Medium Low Low Near zero
Hydration cost Full re-render Full re-render Full re-render Resumable (JS) WASM binary restore
CSS updates class/inline class/inline class/inline class/inline CSS Custom Properties
AI streaming Addon Addon Addon Addon Core primitive
Component format JSX JSX SFC JSX Typed TS (no transform)

Package roadmap

Package Scope Status
@micron/core Signals, scheduler, hyperscript, component + context APIs ✅ Implemented (TypeScript)
@micron/worker RenderWorker, DomPatcher, patch protocol, SAB transport 🚧 Prototype ready — needs browser integration + tests
@micron/wasm Rust reactive core + WASM bindings ✅ Compiles via wasm-pack; integration work pending
@micron/css cssSignal, cssVar, theme helpers 🚧 API skeleton — polish + docs required
@micron/router Typed routes, navigation helpers 🧪 Spec draft — implementation in progress
@micron/ai stream, infer, reactive streaming primitives 🧪 Experimental — SSE/WebSocket adapters prototyped
@micron/islands Binary snapshot capture/resume tooling 🧭 Design + API sketches; implementation upcoming

Status

This is a proof-of-concept / research prototype. The architecture is fully designed and the TypeScript layer is implemented. The WASM reactive core (lib.rs) is production-ready Rust and compiles with wasm-pack. The off-main-thread rendering system needs integration testing in a real browser environment with COOP/COEP headers.

The goal: demonstrate that these four bets are technically viable together, not just in isolation.

About

Next-generation web component framework — WASM reactive core, SharedArrayBuffer signals, off-main-thread rendering, binary resumability

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors