CSR UI Dom runtime
npm install @papack/csrExperimental 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.
---
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.
---
- Fully synchronous rendering
- No Virtual DOM, no reconciliation
- Minimal reactivity via signal / effect
- Explicit lifecycle (mount, unmount)
- Structural primitives (Show, For)
---
Signals are sources, not values.
``ts
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
`ts`
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 structural, not conceptual.
`ts
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
---
`ts
import { render, signal, for } from "@papack/csr";
const [items, setItems] = signal([
{ uuid: "a", name: "A" },
{ uuid: "b", name: "B" },
]);
render(
function App() {
return (
Structural Rendering
$3
Controls existence, not visibility.
`ts
`- when
false, the subtree is destroyed
- when true, it is rendered fresh
- lifecycle runs correctly on both transitions$3
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`ts
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 keysWhat
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
`ts
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 orderRouting
Routing is just application state.
`ts
const [route, setRoute] = signal("home");
`Structure is derived explicitly:
`ts
effect(route, (r) => {
setIsHome(() => r === "home");
setIsSettings(() => r === "settings");
});
``tsx
``