Why Azox

Every framework decides how to get a change in your data onto the screen. That single decision shapes everything else. Here is the one Azox made, and what it costs.

The problem every framework solves

You change a number. Somewhere on screen, that number is displayed. The framework's job is to make the screen agree with the data.

Sounds trivial. It is not, because the framework does not know where on screen that number appears — you wrote markup, not instructions. So it has to work it out. The three main answers are genuinely different, and each has a cost.

Answer one: re-run and compare

This is React's approach, and the most widely used. When state changes, the framework re-runs your component function, gets a fresh description of what the UI should look like, and compares it against the previous description to find the differences.

the cycle
state changes
      ↓
component function runs again
      ↓
returns a new tree of elements
      ↓
framework compares old tree vs new tree
      ↓
applies the differences to the real DOM

It works, and it is easy to reason about: your UI is a function of your state. But look at the middle of that list. To update one number, the framework rebuilt a description of everything around it and then did work to discover that only the number changed.

This is where useMemo, useCallback, React.memo and dependency arrays come from. They are not features so much as tools for telling the framework to skip work it would otherwise do. That is a real cognitive cost.

Answer two: compile it away entirely

Svelte's original insight: a compiler can read your markup at build time and see exactly which piece of the DOM depends on which variable. Then it can generate direct instructions and skip the runtime comparison completely.

This is much faster. The trade-off is that the compiler has to statically understand your code, which gets harder the more dynamic your code is.

Answer three: let the value carry its own subscribers

This is what Azox does, and what Solid and Svelte 5 also converged on. Instead of the framework working out what depends on what, the value itself keeps track.

the mechanism
const count = signal(0);

// Reading count() inside an effect registers
// that effect as a subscriber. The signal now
// knows exactly who cares about it.

effect(() => {
  textNode.data = String(count());
});

// So on a write, there is nothing to work out.
// The signal already holds the list.

count.set(1);   // → runs that one effect

There is no tree, no comparison, and no re-run of anything but the effects that actually read the value. The cost of an update is proportional to how many things display that value — not to the size of your component or your page.

What this looks like in your code

re-run and compare
function Profile({ user }) {
  const [count, setCount] = useState(0);

  // Recreated on every render, so it is
  // wrapped to keep child components from
  // re-rendering unnecessarily.
  const onClick = useCallback(
    () => setCount(c => c + 1),
    []
  );

  // Recomputed on every render unless
  // memoised.
  const label = useMemo(
    () => `${user.name}: ${count}`,
    [user.name, count]
  );

  return <button onClick={onClick}>{label}</button>;
}
signals
<script>
  import { signal, computed } from 'azox/reactivity';

  const count = signal(0);

  // Runs when count changes. Not on
  // render, because there is no render.
  const label = computed(
    () => user.name + ': ' + count()
  );
</script>

<!-- The handler is created once, when
     this node is created. -->
<button on:click={() => count.set(count() + 1)}>
  {label()}
</button>

The right-hand version has no memoisation because there is nothing to memoise. Nothing re-runs, so nothing needs to be prevented from re-running.

Where components go

If components do not re-render, what are they? In Azox they are a build-time unit of reuse and nothing more. A component is inlined into whatever uses it, and by the time your code reaches the browser there is no trace that it was ever a separate file.

before and after
components/Card.azox     →  inlined into the page
pages/index.azox        →  one flat sequence of DOM calls

// No component instance. No mount or unmount.
// No props object allocated at runtime.
// The output is what you would have written by hand.

What Azox gives up

A page that claims only advantages is selling something. Here is the other side.

You give upBecause
Lifecycle hooksA component's script runs once; nothing fires when it appears or goes away
Automatic list identityRows survive a change only when you give them a key; without one the list rebuilds
A large ecosystemThe API is stable, but it is new: no plugins, no component libraries, no Stack Overflow answers
State that outlives a pageNavigation starts each page fresh; nothing is carried across
Battle-tested edge casesReact has had a decade of production use finding them. Azox has months

Use Azox if you want to understand your whole framework, you value a small dependency-free build, and you are comfortable on early-stage software.

Do not use Azox if you need an ecosystem today, or you are shipping something where an unfixed framework bug would be expensive.

Why build it at all

Because the interesting part of a framework is the reactivity model, and it is small enough to fit in your head. Azox's runtime is about fifty lines. You can read all of it in a few minutes and know exactly what happens when your data changes.

That is worth something on its own, whether or not you ship anything with it.