The shape of a compiled render program, and the vocabulary the compiler and the runtime agree on.
Why a program at all
The previous rendering contract between the compiler and the runtime was a
single string: the compiler handed over a template with {{ }} still in it,
and the runtime worked out what that meant on every update. Working it out
meant interpolating the whole template into HTML, handing that HTML to
DOMParser, and diffing the resulting tree against the live DOM — so the
cost of changing one value was proportional to the size of the template, and
the runtime had to carry a template engine to pay it.
None of that work depends on runtime values. Which text nodes are dynamic, which attributes are bound, which elements carry an event handler, and which subtrees can never change are all properties of the template, and the compiler already has the template. A render program is the compiler writing those answers down once, so the runtime can stop rediscovering them.
The two halves
A program is a skeleton, a list of ops, and a list of blocks.
The skeleton is the template with every dynamic part removed: interpolations become comment markers, bound attributes are dropped. It is valid HTML that is parsed exactly once per component class and then cloned per instance, so a 2000-node template is parsed once for the life of the page rather than once per update.
An op says what to do and where. { k: 'text', t: 3, x: 'count' } reads
"evaluate count, write it to text marker 3". Each op becomes one reactive
effect at mount, so a write to count wakes that op and nothing else.
Addressing, and why markers rather than paths
The compiler cannot number nodes by walking its own AST and assume the
browser will produce the same tree: HTML parsing inserts implied elements
(<tbody>), relocates misplaced content, and closes tags the author left
open. A path computed from the compiler's tree can therefore address a
different node than the one the compiler meant.
So the compiler emits markers — data-axb="n" on a dynamic element,
<!--axt:n--> where dynamic text goes — and the runtime resolves them
against the tree the browser actually built. That resolution happens once per
component class (module:lib/core/renderer/program/CompiledTemplate),
is cached as index paths, and every later instance resolves by path with no
search.
What the compiler refuses to compile
A program is emitted only when every construct in the template is one the program runtime implements. Anything else and the component keeps the string renderer, with the reason recorded. Partial compilation is not offered: a template that is half compiled and half diffed has two sources of truth for the same DOM, and they will disagree.
That fallback is visible rather than silent — avenx build reports which
components did not compile and why, which is the same house rule Atlas
follows when its analysis is incomplete.
The refusal reasons themselves live with the layer that decides them, in module:lib/compiler/ir/nodes. They used to live here, next to a second template compiler that read already-rewritten markup; that compiler has been replaced by the IR and its lowering pass, so this module is now the format and the vocabulary and nothing else.
- Source:
Members
(static, constant) OpKind :string
Op kinds a program may contain.
Kept as short string constants rather than numbers: a program is JSON in a
bundle a developer may well end up reading, and 'text' costs three bytes
more than 0 while costing nothing to understand.
Type:
- string
- Source:
(static, constant) PROGRAM_VERSION :number
The program format version.
A bundle carries programs produced by the compiler that built it, and the runtime is concatenated into that same bundle, so the two can never be mismatched in a deployed application. The version exists for the case that can happen: a stored or transported program (a trace fixture, a cached build) read back by a newer runtime. A runtime that does not recognise the version falls back to the string renderer rather than guessing.
Type:
- number
- Source:
(static, constant) RANGE_OPS :Set:.<string:>
Op kinds that own one or more blocks and therefore a DOM range.
Everything else writes to a single node the skeleton already contains. A range op inserts and removes nodes, so it needs an anchor and a teardown path, and the runtime dispatches on this rather than re-listing the kinds.
Type:
- Source:
Methods
(static) isProgram(value) → {boolean}
Whether a value looks like a render program this runtime can execute.
Parameters:
| Name | Type | Description |
|---|---|---|
value |
any | The candidate. |
- Source:
Returns:
True when it is a program of a known version.
- Type
- boolean
(static) programBlocks(program) → {Array:.<object:>}
Returns a program's blocks, tolerating a program that declares none.
A template with no control flow lowers to a root block and nothing else, and omitting the empty array keeps that program byte-identical to what the previous format produced for the same template.
Parameters:
| Name | Type | Description |
|---|---|---|
program |
object | A render program. |
- Source:
Returns:
The blocks.