Skip to content

debjitbis08/synx

Repository files navigation

Synx

A minimal, reactive UI framework for the DOM — no virtual DOM, no compiler, no magic.

Synx is a tiny yet expressive reactive library that brings clarity, predictability, and purity to UI development.

It is built on a composable FRP core, but you can choose your own abstraction level:

  • Use just the core primitives (Event, Reactive, subscribe, etc.)
  • Add DOM helpers (div, input, bind.text, on.click, etc.)
  • Or build UIs with the full component model (defineComponent, props, outputs, child())

Synx follows a consistent dataflow:

Events → Fold → DOM → Events → ...

No runtime illusions. No reactive spaghetti. Just observable state -> UI -> new events.


🧠 Core Principles

  • Static DOM Tree All DOM structure is defined once, up front. Reactivity only applies to:

    • Attributes (bind.value, bind.checked)
    • Text content (bind.text)
    • Events (on.click, on.input, etc.)

    Dynamic child nodes (children()) are also defined as reactive bindings to content, but the container node remains fixed.

  • Real DOM, Not Virtual Synx operates directly on the DOM. No diffing, patching, or reconciliation engines. It uses efficient, minimal updates based on fine-grained reactivity.

  • Composable, Algebraic Reactivity Event<A> and Reactive<A> form functors, applicatives, and monads. You can build complex behaviors by combining tiny, testable expressions.

  • Reactive Children with Minimal DOM Mutation Synx provides a children() helper that updates child nodes with:

    • Optional key() diffing
    • create and update functions
    • Efficient DOM reuse instead of full re-renders
  • Unidirectional Dataflow Every component expresses:

    • Inputs as reactive events (props)
    • Outputs as event streams
    • DOM as a function of state, never the source of truth

🚀 Example

import { defineComponent, ref, child, bind, text, div, input, E, R } from "synx";

function createHelloInput() {
  const name = E.create<string>();

  const value = R.stepper("World", E.map(name, (e) => (e.target as HTMLInputElement).value));

  return {
    el: div({ class: "space-y-2" }, [
      input({ on: { input: name } }),
      div({}, text(R.map(value, (v) => `Hello, ${v}!`))),
    ]),
    props: {},
    outputs: {},
  };
}

export const HelloInput = defineComponent(createHelloInput);

🛠 Features

  • ✅ Push-pull FRP with explicit semantics
  • Event and Reactive primitives
  • ✅ Real DOM bindings (not virtual)
  • ✅ Static DOM tree with dynamic content
  • ✅ Reactive children() with keyed updates
  • ✅ Props as input events, not static values
  • ✅ Modular: choose low-level or DSL-level APIs
  • ✅ No compiler, no Babel, no JSX
  • ✅ Type-safe and tree-shakable

🔧 Use Synx at Your Level

Synx is layered by design. Use as much or as little as you need:

Layer What it gives you Opt-in?
@synx/frp Core FRP primitives (Event, Reactive, subscribe, fold, etc.)
@synx/dom DOM helpers: bind, on, text, children, etc.
@synx/dom/component Component system: defineComponent, child, refOutputs, props, outputs Optional
@synx/dsl (WIP) JSX-like tag functions: div(...), button(...), etc. Optional

No opinionated bundling. No black boxes. Just clean, composable building blocks.


🌀 The Cycle: Events → Fold → DOM → Events

Synx models UI as a pure dataflow cycle:

User Interaction
       ↓
    Event<A>
       ↓
Fold / stepper / reducer
       ↓
Reactive<A>
       ↓
DOM Update (text, attr, class)
       ↓
New Events triggered
       ↓
(repeat)

This makes everything traceable and debuggable. No side effects hidden in render trees or lifecycle hooks.


🧪 Running Tests

Install dependencies once with pnpm install. Then:

pnpm test
  • pnpm test:watch keeps Vitest in watch mode.
  • pnpm test:frp runs only the FRP package tests.

🏎️ Benchmarks

Microbenchmarks for long reactive chains live under packages/frp/bench. Run them with:

pnpm bench

Use this to compare different implementations before adopting them in the runtime code.

📦 Examples

The workspace includes small runnable examples that exercise the FRP core:

pnpm run examples:counter  # reactive fold example
pnpm run examples:zip      # pair two event streams

Run both sequentially with pnpm run examples.

For a browser-based demo, run pnpm dlx parcel serve examples/dom/counter/index.html --open (aliases are preconfigured for Parcel).

📜 License

MIT — handcrafted with clarity and care.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published