Skip to content

papack/csr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@papack/csr

Experimental synchronous UI DOM runtime. Designed for simplicity, explicitness, and predictability.

No feature is the feature.

No Virtual DOM. No diffing. No scheduler. No hidden async.

Just real DOM, real mutation, and deterministic structure with zero dependencies.


Philosophy

This library is simple by design, not “easy”.

  • There is one way to do things
  • Everything is explicit

It is optimized for clarity and long-term maintenance.


Features

  • Fully synchronous rendering
  • No Virtual DOM, no reconciliation
  • Minimal reactivity via signal / effect
  • Explicit lifecycle (mount, unmount)
  • Structural primitives (Show, For)

Core Ideas

Signals are active state

Signals are sources, not values.

const [count, setCount] = signal(0);

setCount((v) => v + 1);
setCount(() => 42);
  • no equality checks
  • no dependency tracking
  • mutation is allowed
  • every write notifies every subscriber

Effects are explicit reactions to signals

effect(count, (value) => {
  console.log(value);
});

Characteristics:

  • NOT runs immediately on mount
  • only runs on every write to the signal
  • only one singal, no dependency arrays
  • async callbacks are allowed (fire-and-forget)

Effects are not awaited. Concurrency is explicit and intentional.

Lifecycle is bound to real DOM

Lifecycle is structural, not conceptual.

mount((parent) => {
  // runs once, when the root DOM element exists
});

unmount(() => {
  // guaranteed cleanup before removal
});
  • lifecycle is attached to actual DOM nodes
  • children unmount before parents
  • removal means real destroy

Example

import { render, signal, for } from "@papack/csr";

const [items, setItems] = signal([
  { uuid: "a", name: "A" },
  { uuid: "b", name: "B" },
]);

render(<App />, { parent: document.body });

function App() {
  return (
    <ul>
      <For each={items}>{(item) => <li>{item.name}</li>}</For>
    </ul>
  );
}

Structural Rendering

Show

Controls existence, not visibility.

<Show when={visible}>
  <User />
</Show>
  • when false, the subtree is destroyed
  • when true, it is rendered fresh
  • lifecycle runs correctly on both transitions

For (intentionally restricted)

For is a keyed structural renderer, not a generic iterator.

Rules:

  • each must be an array
  • each item must be an object
  • each item must have a stable key (uuid)
  • no fallbacks, no heuristics
type Item = {
  uuid: string;
  [key: string]: any;
};

What For does:

  • detects:

    • additions
    • removals
    • order changes
  • performs:

    • DOM moves (insertBefore)
    • rendering only for new keys
    • destroy() for removed keys

What For does not do:

  • no content diffing
  • no re-rendering existing items
  • no prop patching

If an item’s content changes, the item itself must be reactive.


Mutation is allowed

setItems((prev) => {
  prev.push({ uuid: "c", name: "C" });
  return prev;
});

This is correct.

Why:

  • signals are active
  • updates are not reference-based
  • For only cares about keys and order

Routing

Routing is just application state.

const [route, setRoute] = signal("home");

Structure is derived explicitly:

effect(route, (r) => {
  setIsHome(() => r === "home");
  setIsSettings(() => r === "settings");
});
<Show when={isHome}>
  <Home />
</Show>

<Show when={isSettings}>
  <Settings />
</Show>

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors