CORE

Components

There is no component API and no lifecycle object. A component is a plain function that returns a Node; props are its arguments; composition is ESM import. A scope has exactly two moments — it is created, and it dies — served by the function body and onCleanup().

A function that returns a Node

JS
// a component is a function that returns a Node; props are its arguments
function Greeting(name) {
  return html`<p>hello, ${name}</p>`;
}

// composition is import + a call inside a hole
import { Greeting } from './greeting.js';
const App = () => html`<main>${Greeting(user)}</main>`;

Props are arguments

Pass a value for something fixed, a signal for something that changes. The component does not care which — a hole handles both — and TypeScript types the parameter like any other.

JS
// a plain value: written once
Greeting('world');

// a signal: the <p> follows it
const name = signal('world');
Greeting(name);

// a derived value: pass a computed, or a function
Greeting(computed(() => first.value + ' ' + last.value));

It runs once

A component function runs when it is called and never again. After that only the hole effects are alive, each watching its own signal. There is no re-render to optimise, no memo, no dependency array.

JS
function Counter() {
  console.log('runs exactly once');          // not on every click — there is no re-render
  const count = signal(0);
  return html`<button onclick=${() => count.value++}>${count}</button>`;
}

Conditionals

A function hole that returns a different node is a conditional. The runtime swaps the nodes and disposes the branch that left.

JS
const App = () => html`<div>
  ${() => (loggedIn.value ? Dashboard(user) : LoginForm())}
</div>`;
// when loggedIn flips, the old branch's scope is disposed — its effects, cleanups, timers — and the new one is built

Lifetime

Everything a component creates — effects, computeds, child components — belongs to it and is disposed with it. onCleanup is where you release what the platform handed you; onMount is the moment its nodes are in the document.

JS
function Clock() {
  const now = signal(new Date());
  const id = setInterval(() => (now.value = new Date()), 1000);
  onCleanup(() => clearInterval(id));     // the scope dies → the interval dies
  onMount(() => console.log('in the document now'));
  return html`<time>${() => now.value.toLocaleTimeString()}</time>`;
}

Why no component API

Any registry, class or component object would be something the compiler had to understand and amo eject had to reproduce. A function is already what a person writing vanilla would write, so compiled output stays within 10% of hand-written bytes and ejected output stays readable.