freact API
Everything you can write in freact, and nothing else. The surface is small on purpose: each
export exists because some encapp construct is its image, and anything with no image is a
TypeError at construction or a freact-check failure at build.
The one rule that outranks this page: a program is freact iff freact-check exits 0 on
it. If this page and the checker ever disagree, the checker is right.
node tools/freact-check.mjs --summary src/**/*.tsx # exit 0 ⟺ freactBuilding a view
el(tag, props?, ...kids)
An element node.
import { el, text, on } from 'freact'
el('div', { class: 'row', style: 'display:flex;gap:12px' },
el('span', null, 'Alice'),
el('button', { onClick: on('openProfile', { pub: 'ab12…' }) }, 'Open'))tagis an intrinsic tag name and must be a string. A computed tag throws: "a computed tag has no image in RView".RViewelements are constructors, not values.propsare attributes, pluskey, pluson*handlers built byon().undefined,nullandfalsevalues are skipped, socond && { … }spreads are unnecessary.kidsmay be nodes, strings, numbers, or arrays thereof;null/falsevanish.
text(v)
A text node. null/undefined become the empty string.
fragment(view)
Flattens a view — or a list of them — into the nodes it denotes. A view is allowed to be a list
of siblings: RView holds List RView, and a compose bar is a strip plus a bar with no wrapper
element between them. Both backends normalize the root through this, so the two agree on what a
list root means.
Events — the part that carries the bound
on(tag, payload?)
A serializable event binding: which effect fires, and with what payload.
el('button', { onClick: on('saveProfile') }, 'Save')
el('li', { onClick: on('openChat', { with: row.pub }) }, row.name)tag must be a non-empty string. The payload is checked for serializability at construction,
not at dispatch — pass a function or a DOM node and you get a TypeError immediately.
This is the whole reason freact is not React with lint rules. onClick={() => doThing(x)} is a
closure, and a closure has no image in Eff. on('doThing', { x }) is data: a name and a
payload, which is exactly what an Eff constructor is.
stop()
A binding that only stops the event travelling further — no effect fires.
el('div', { class: 'backdrop', onClick: on('closeDialog') },
el('div', { class: 'card', onClick: stop() }, /* … */))This exists because a modal card must swallow clicks that would otherwise reach the backdrop and
close the dialog. React wrote that as onClick={(e) => e.stopPropagation()} — an inline body
whose entire content is event routing, not app logic. Routing is a property of the binding, so
it is declared as one: still data, still serializable, still no closure.
A binding may also carry stop, prevent and key — the routing facts React expressed as
statements inside a handler body.
Styles
style(obj)
Serializes a style object to a CSS string using React's exact separator discipline —
a:1px;b:2px, no spaces, no trailing semicolon. That is measured against react-dom/server, not
assumed: the pretty form would make every node in a port differ.
Empty, null, undefined and boolean values are dropped.
The value vocabulary
freact/vocab gives constructors for the values Encapp.Style can hold. Using them is what makes
a style a value rather than a string:
| constructor | produces |
|---|---|
px(n) | "12px" |
pct(n) | "50%" |
len(v) | a length, from a number or an already-valid string |
box(v) | a box shorthand (1–4 values) |
colour(c) | a colour |
background(v) | a background — colour or gradient |
border(w, c) | "1px solid #E5E5E5" |
count(n) | a non-negative integer as a string |
align(v) · justify(v) · textAlign(v) | the flex/text enums, defaulted rather than thrown |
CONSTRUCTORS exports that list, so a tool can enumerate the vocabulary rather than hard-code it.
Rendering
mountDom(container, { onEvent })
Mounts into a real DOM node. Returns a renderer whose render(view) diffs and patches.
Events see the tree they were dispatched into. A handler may commit a new view — that is the point of a handler — and this renderer reuses DOM nodes across a commit. So a commit bumps a generation and the event sink stamps each event on first entry, ignoring it once the generation moves. Without that, a reused ancestor carrying the next page's binding could fire for an event still travelling from the previous tree.
mountHeadless(_container?, { onEvent }) and toHtml(node)
The same renderer with no DOM. toHtml serializes a node to markup — this is what the
224-fixture differential compares against renderToStaticMarkup.
renderRView(doc, node, row) · renderRViewAll(...)
Renders encapp's RView protocol shape directly. This is the bridge in the running direction:
Lean emits RView, and this interprets it with the same renderer that interprets hand-written
freact.
Host capabilities
Everything a component may not reach ambiently is declared instead.
export const CAPABILITIES = ['clock', 'intl', 'env', 'blobs', 'capture', 'ask']checkHost(host, required?) validates a host against that set and returns it, so
construction can be wrapped in the check. It throws when a declared capability is missing or
malformed — an undeclared or half-implemented world should not start — rather than failing later
at the first call.
Three hosts ship: browserHost, memoryHost, serverHost.
This is where the security argument lands in the API: a component cannot read window or call
fetch, so the set of things an app can reach is this list, and it is enumerable at build
time rather than discovered by running the program.
What has no image, and what to write instead
| you would write in React | write in freact | why |
|---|---|---|
useState | a Doc scalar, set by an effect | Doc scalars are typed Atoms; useState holds any JS value |
useEffect | a declared command on a node (cmd) | Eff has no "later, imperatively" |
onClick={() => f(x)} | onClick={on('f', { x })} | a handler must name an effect with a serializable payload |
onClick={(e) => e.stopPropagation()} | onClick={stop()} | routing is a property of the binding |
useRef + node access | a command on the node | RView is data, not a handle |
window / localStorage / fetch | a host capability | render must be a function of Doc alone |
<Tag> where Tag is computed | a conditional over fixed tags | elements are constructors, not values |
{...props} | named props | a spread hides which fields cross the boundary |
useContext | an explicit prop | a dynamic scope no first-order view can name |
| a class component | a function | lifecycle has no image in Doc/Eff |
Each row is enforced by a named rule; run the checker to see which one fires and where.