RxJS-native single-file components (.tsrx) that compile to native-DOM
clone calls over a tiny runtime. State is plain RxJS — BehaviorSubject,
Subject, .pipe(...) — and the compiler wires templates, control flow and
scoped CSS around it.
import { Subject } from 'rxjs';
import { scan, startWith } from 'rxjs/operators';
export function Counter() @{
const inc$ = new Subject<void>();
// A reactive fold: scan accumulates clicks; startWith emits the seed first.
const count$ = inc$.pipe(scan((n) => n + 1, 0), startWith(0));
<div>
<p>Count: <strong>{count$}</strong></p>
<p>Doubled: {count$ * 2}</p> <!-- auto-lifted, reactive -->
<button onClick={() => inc$.next()}>+</button>
<style>
strong { color: #2563eb; }
</style>
</div>
}
Status: alpha (0.1.x). Not a general-purpose production framework yet. Built and intended for SPAs and embedded WebViews.
- Client-only. TSRX renders to the live DOM. There is no SSR / hydration
path;
template()/createRoot()/__mount()throw a clear error if run without adocument. - Engine targets: modern Chromium (incl. Android System WebView) and
WebKit (incl. iOS WKWebView). No legacy browsers. See
browserslistinpackages/tsrx-rxjs/package.json. - Validation: unit tests run in jsdom (
bun run test). A real-browser suite (bun run test:browser) runs in both Chromium and WebKit via Playwright and covers: the runtime (event delegation/composedPath, microtask scheduling,MutationObserverauto-dispose), the compiler (scoped-CSS cascade viagetComputedStyle, SVG/MathML namespacing), controlled inputs (typed input, caret survival across the deferred write-back, an IME composition lifecycle), accessibility (axe audit, focus retained across keyed@forreorders, reactivearia-*reflection), and resource cleanup (subscriptions,MutationObservers and DOM all return to baseline across mount/dispose cycles). A separate soak (bun run test:memory, Chromium + CDP) churns the stress-grid through repeated create/clear of 10k rows and asserts heap and live-node counts don't grow, and that updates still flush while the page is backgrounded. Note Playwright's WebKit approximates but is not identical to a real iOS WKWebView device — on-device smoke testing is still recommended before an iOS release.
TSRX never builds HTML from runtime data: reactive text is written to a text
node's .data and attributes go through setAttribute / property assignment, so
attacker-controlled values can't inject markup or break out of an attribute —
there are no escape* helpers to misconfigure (the DOM does the escaping). This
is fuzz-tested with a canary suite in both Chromium and WebKit.
What remains the app's responsibility (native semantics, same as React/Solid/Svelte — TSRX does not sanitize these):
- a
javascript:URL bound tohref/src(set verbatim; only runs if the user activates it), - an explicit
innerHTMLproperty/spread (the deliberate raw-HTML escape hatch), - CSS bound via an inline
style.
Don't bind untrusted data to those sinks. Scoped <style> ships as a real CSS
module (never injected as HTML), and the dependency tree is audited
(bun audit).
- Observables are the primitive.
{count$}renders reactively;{count$ * 2}andclass={active$ ? 'on' : 'off'}are auto-lifted into derived observables.count$.valueis an explicit one-shot snapshot. - Naming: a
$suffix marks an observable (load-bearing; the type-checker enforces it). - Batching: updates are coalesced on a microtask (initial render is
synchronous). Use
flushSync()for deterministic draining (e.g. tests) andbatch(fn)to coalesce a synchronous transaction. - Lifetime: every subscription a tree owns is disposed when its mount is
disposed. Call the disposer returned by
createRooton unmount; if the host rips DOM out directly, usecreateRootWithCleanup(auto-disposes on detach).
packages/tsrx-rxjs— the compiler, runtime, and.tsrxtype-checker.packages/vite-plugin-rxjs— Vite plugin for.tsrx.
examples/counter, examples/todo, examples/pokemon (async search), examples/stress-grid
(10k-row keyed list + a derived "diamond", the perf / memory / a11y reference
target), and examples/jfb-keyed (a conformant
js-framework-benchmark
keyed implementation).
bun install
bun run dev # examples/counter dev server (can also run `bun run dev:todo`)
bun run build:examples # build all examples
bun run verify # typecheck + tests + build examples
bun run test:browser # real-browser suite (Chromium + WebKit)
bun run test:memory # CDP heap/leak soak on the stress-grid
bun run test:bench # jfb-keyed conformance + perf-budget gateMIT