@adaas/are-html
v0.0.2
Published
A-Concept Rendering Engine (ARE) is a powerful rendering engine designed to work seamlessly with the A-Concept framework. This library provides an HTML engine implementation of ARE, enabling developers to create dynamic and interactive user interfaces for
Maintainers
Readme
ARE HTML Rendering Engine
HTML Rendering Engine for the ARE (A-Concept Rendering Engine) framework.
@adaas/are-html bridges the ARE reactive scene graph to the browser DOM. It provides a full HTML compilation, interpreting, and lifecycle pipeline — turning ARE component templates written in standard HTML syntax into live, reactive DOM trees.
Table of Contents
- Overview
- Installation
- Quick Start
- Template Syntax
- Component Authoring
- Engine Architecture
- Nodes
- Instructions
- Directives
- Signals & Routing
- Watcher
- API Reference
- License
Overview
@adaas/are-html
│
├── AreHTMLEngine ← registers compiler, interpreter, tokenizer, lifecycle, transformer
├── AreHTMLCompiler ← converts AreNodes + attributes into SceneInstructions
├── AreHTMLInterpreter ← applies SceneInstructions to the real DOM
├── AreHTMLLifecycle ← initialises nodes, subscribes to signals
├── AreHTMLTokenizer ← parses HTML markup into AreHTMLNode attribute objects
├── AreHTMLTransformer ← runs directive transform phase (tree restructuring)
│
├── nodes/ ← AreHTMLNode, AreComponentNode, AreRootNode, AreText, AreInterpolation, AreComment
├── attributes/ ← AreStaticAttribute, AreBindingAttribute, AreEventAttribute, AreDirectiveAttribute
├── directives/ ← AreDirectiveIf ($if), AreDirectiveFor ($for)
├── instructions/ ← AddElement, AddText, AddAttribute, AddStyle, AddListener, AddInterpolation, AddComment
└── signals/ ← AreRoute (client-side routing signal)The engine follows a strict pipeline per node:
- Tokenize — parse raw HTML markup, extract attributes
- Transform — run directive transforms that may restructure the node tree
- Compile — emit
SceneInstructionobjects describing DOM mutations - Mount — apply instructions through the interpreter, writing to the real DOM
- Update — re-compile reactive instructions when store values change
- Unmount — revert instructions and clean up the DOM
Installation
npm install @adaas/are-htmlPeer dependencies (installed automatically as transitive deps):
| Package | Purpose |
|---|---|
| @adaas/are | Core ARE scene graph, store, compiler, interpreter |
| @adaas/a-concept | Dependency injection, component model |
| @adaas/a-frame | Component metadata decorators |
| @adaas/a-utils | Logger, signal bus, execution context |
Quick Start
1. Write a component
import { Are, AreNode, AreStore, AreEvent } from '@adaas/are';
import { A_Caller, A_Inject } from '@adaas/a-concept';
export class CounterComponent extends Are {
@Are.Template
async template(@A_Inject(A_Caller) node: AreNode) {
node.setContent(`
<div class="counter">
<p>Count: {{count}}</p>
<button @click="increment">+</button>
</div>
`);
}
@Are.Data
async data(@A_Inject(AreStore) store: AreStore) {
store.set({ count: 0 });
}
@Are.EventHandler
async increment(
@A_Inject(A_Caller) node: AreNode,
@A_Inject(AreStore) store: AreStore,
) {
store.set('count', store.get('count') + 1);
await node.update();
}
}2. Bootstrap with AreHTMLEngine
import { A_Concept, A_Context } from '@adaas/a-concept';
import { A_Logger } from '@adaas/a-utils/a-logger';
import { A_Service, A_ServiceFeatures } from '@adaas/a-utils/a-service';
import { A_SignalBus } from '@adaas/a-utils/a-signal';
import { AreInit, AreRoute } from '@adaas/are';
import { AreHTMLEngine } from '@adaas/are-html';
import { AreRoot } from '@adaas/are-html';
import { AreHTMLEngineContext } from '@adaas/are-html';
import { AreDirectiveIf, AreDirectiveFor } from '@adaas/are-html';
import { CounterComponent } from './counter.component';
// Your app container (extends A_Service or any A-Concept service)
class AppContainer extends A_Service {
// ... build/load/start lifecycle hooks
}
const container = new AppContainer({
name: 'my-app',
components: [
CounterComponent,
AreDirectiveIf,
AreDirectiveFor,
AreRoot,
AreHTMLEngine,
A_SignalBus,
A_Logger,
],
fragments: [
new AreHTMLEngineContext({ container: document }),
],
entities: [AreInit, AreRoute],
});
const concept = new A_Concept({
name: 'my-concept',
containers: [container],
components: [A_Logger],
});
await concept.load();
await concept.start();3. index.html
<!DOCTYPE html>
<html>
<body>
<!-- The engine mounts onto existing DOM elements by their id -->
<are-root id="app" default="counter-component"></are-root>
</body>
</html>Template Syntax
Templates are plain HTML strings written inside node.setContent(...) inside an @Are.Template method.
Interpolations {{ }}
Double-curly-brace syntax renders a reactive value from the component store:
<p>Hello, {{username}}!</p>
<span class="badge">{{count}}</span>The value is re-evaluated and the text node is updated on every node.update() call.
Static Attributes
Regular HTML attributes are treated as static and written directly to the DOM element:
<button class="btn btn-primary" type="button">Click me</button>
<img src="/logo.png" alt="Logo" />Dynamic Bindings :attr
Prefix an attribute name with : to evaluate its value as a store expression:
<!-- binds the "class" attribute to an expression -->
<li :class="active === item.name ? 'menu-item-active' : ''" class="menu-item">...</li>
<!-- binds href dynamically -->
<a :href="user.profileUrl">Profile</a>
<!-- binds a boolean attribute -->
<input :disabled="isLoading" />The expression has access to all values in the component store.
Event Listeners @event
Prefix an attribute name with @ to attach a DOM event listener that fires an ARE event routed to an @Are.EventHandler method on the component:
<button @click="handleClick">Submit</button>
<input @input="onInput" />
<form @submit="onSubmit">...</form>Pass arguments to the handler:
<li @click="$select(item.id)">{{item.name}}</li>
<button @click="$remove(item)">Delete</button>Inside the handler use event.get('args') to retrieve the passed arguments.
Conditional Rendering $if
The $if directive conditionally renders an element based on a store expression. When the expression is falsy the element is replaced by a comment placeholder and removed from the DOM:
<!-- simple boolean -->
<div $if="isVisible">Visible content</div>
<!-- expression -->
<span $if="user.role === 'admin'">Admin panel</span>
<!-- combined with $for -->
<li $for="item in items" $if="item.badge > 0" class="menu-item">
{{item.name}} <span class="badge">{{item.badge}}</span>
</li>List Rendering $for
The $for directive renders a template element for each item in a store array. It supports key, optional index, and function-call expressions:
<!-- basic -->
<li $for="item in items">{{item.name}}</li>
<!-- with index -->
<tr $for="row, idx in rows">
<td>{{idx}}</td><td>{{row.value}}</td>
</tr>
<!-- expression with parentheses -->
<li $for="(item, i) in items">{{i}}: {{item.label}}</li>
<!-- filter function defined in the store -->
<li $for="item in filter(items)">{{item.name}}</li>Each generated node receives its own isolated AreDirectiveContext holding the item-scoped variables, so bindings like {{item.name}} resolve correctly per clone.
HTML Comments
HTML comments are first-class nodes — they pass through to the DOM as Comment nodes and are also used internally by the $if and $for directives as placeholder anchors:
<!-- This is a static comment -->Component Authoring
ARE components extend the Are base class from @adaas/are.
@Are.Template
Defines the HTML structure. Called once during the compile phase.
@Are.Template
async template(@A_Inject(A_Caller) node: AreNode) {
node.setContent(`<div class="card">{{title}}</div>`);
}@Are.Data
Initialises the component store. Called once before compilation.
@Are.Data
async data(@A_Inject(AreStore) store: AreStore) {
store.set({ title: 'Hello World', count: 0 });
}@Are.Styles
Attaches scoped CSS to the component node.
@Are.Styles
async styles(@A_Inject(A_Caller) node: AreNode) {
node.setStyles(`
.card { padding: 16px; border-radius: 8px; }
`);
}@Are.EventHandler
Declares a DOM event handler. The method is resolved by name from the template's @event="handlerName" binding.
@Are.EventHandler
async handleClick(
@A_Inject(A_Caller) node: AreNode,
@A_Inject(AreStore) store: AreStore,
@A_Inject(AreEvent) event: AreEvent,
) {
store.set('count', store.get('count') + 1);
await node.update();
}Access DOM event arguments:
const [arg1] = event.get('args') ?? [];
const domElement = event.get('element');props
Define typed, default-valued props that parent components can pass via :propName="expression" bindings:
export class CardComponent extends Are {
props: Record<string, ArePropDefinition> = {
title: { type: 'string', default: '' },
disabled: { type: 'boolean', default: false },
count: { type: 'number', default: 0 },
};
}Usage in a parent template:
<card-component :title="selectedItem.name" :disabled="isLoading"></card-component>Engine Architecture
AreHTMLEngine
The top-level engine class registered as a component in the container. On load it packages the entire pipeline:
import { AreHTMLEngine } from '@adaas/are-html';Registers: AreHTMLCompiler, AreHTMLInterpreter, AreHTMLTokenizer, AreHTMLLifecycle, AreHTMLTransformer, and the AreHTMLEngineContext context.
AreHTMLCompiler
Extends AreCompiler. Converts visited AreNode / AreAttribute instances into AreInstruction objects planned onto a AreScene:
| Visitor target | Emits |
|---|---|
| AreInterpolation | AddTextInstruction (evaluated) |
| AreText | AddTextInstruction (static) |
| AreStaticAttribute | AddAttributeInstruction |
| AreEventAttribute | AddListenerInstruction |
| AreBindingAttribute | AddAttributeInstruction (evaluated) or prop injection |
| Directive attributes | delegated to the directive's compile method |
AreHTMLInterpreter
Extends AreInterpreter. Applies and reverts instructions against the real DOM:
| Instruction | Apply | Revert |
|---|---|---|
| AddElement | createElement + appendChild / replaceChild | removeChild |
| AddAttribute | setAttribute (with cache-diff for reactive updates) | removeAttribute |
| AddListener | addEventListener | removeEventListener |
| AddText | createTextNode + appendChild | removeChild |
| AddComment | createComment + appendChild | removeChild |
AreHTMLLifecycle
Extends AreLifecycle. Handles per-node initialisation hooks:
AreComponentNode— standard init + scope registrationAreRootNode— subscribes the node to theAreSignalsContextAreText/AreInterpolation— creates a lightweightAreSceneper text node
Also manages the directive attribute update pipeline, chaining updates when store values change.
AreHTMLTokenizer
Extends AreTokenizer. Parses the raw markup string on AreComponentNode and AreRootNode, extracting attributes and registering them into the node scope:
| Prefix | Attribute class |
|---|---|
| $ | AreDirectiveAttribute |
| : | AreBindingAttribute |
| @ | AreEventAttribute |
| (none) | AreStaticAttribute |
AreHTMLTransformer
Extends AreTransformer. Runs before compilation and delegates to a directive component's Transform phase for AreDirectiveAttribute instances. Transformations may restructure the node tree (e.g. $if and $for move original nodes into template clones before the compile cycle starts).
AreHTMLEngineContext
The shared context object that maps AreNodes and instructions to their DOM nodes:
import { AreHTMLEngineContext } from '@adaas/are-html';
// provide as a fragment during container setup
fragments: [
new AreHTMLEngineContext({ container: document }),
]Maintains internal indices for: nodeToHostElements, instructionToElement, elementListeners.
Nodes
| Class | Description |
|---|---|
| AreHTMLNode | Base node class. Exposes staticAttributes, bindings, directives, events, interpolations, styles. |
| AreComponentNode | Generic HTML element node. Resolves a matching Are component instance from the scope. |
| AreRootNode | Special root element node (<are-root>). Fixed tag = 'div'. |
| AreText | Plain text node. |
| AreInterpolation | {{ expression }} node. |
| AreComment | HTML comment node. |
Instructions
Instructions are immutable descriptions of DOM mutations that can be applied (mounted) or reverted (unmounted):
| Class | Type | Description |
|---|---|---|
| AddElementInstruction | AreDeclaration | Creates one DOM element. |
| AddTextInstruction | AreDeclaration | Creates one text node (static or evaluated). |
| AddCommentInstruction | AreDeclaration | Creates one comment node. |
| AddAttributeInstruction | AreMutation | Sets an attribute on the parent element. Supports reactive diff updates. |
| AddStyleInstruction | AreMutation | Sets an inline CSS property on the parent element. |
| AddListenerInstruction | AreMutation | Attaches a DOM event listener. |
| AddInterpolationInstruction | AreMutation | Reactive text node resolved from the store. |
Directives
Directives are prefixed with $ in templates and processed in priority order (higher number runs first):
Built-in: $if
Priority: 2
Conditionally shows or hides an element. During the transform phase the original node is wrapped in a group + comment anchor. During compile/update the inner template's scene is activated or deactivated.
<div $if="isAuthenticated">Welcome back, {{username}}!</div>Built-in: $for
Priority: 1
Renders a list by cloning the template node for each array item. Each clone has an isolated AreDirectiveContext with item-scoped variables. Supports incremental updates — unchanged items are kept, new items are appended, removed items are unmounted.
<li $for="product in products">
{{product.name}} — ${{product.price}}
</li>Custom Directives
Extend AreDirective and give it a priority, then implement Transform, Compile, and/or Update phases:
import { AreDirective } from '@adaas/are-html';
import { AreDirectiveAttribute, AreDirectiveContext } from '@adaas/are-html';
import { A_Inject, A_Caller } from '@adaas/a-concept';
import { AreStore, AreScene } from '@adaas/are';
@AreDirective.Priority(3)
export class AreDirectivePermission extends AreDirective {
@AreDirective.Compile
compile(
@A_Inject(A_Caller) attribute: AreDirectiveAttribute,
@A_Inject(AreStore) store: AreStore,
@A_Inject(AreScene) scene: AreScene,
) {
const allowed = store.get(attribute.content);
if (!allowed) scene.deactivate();
}
}Register the directive in your container's components array and name it AreDirective<PascalCaseName> — the tokenizer resolves components by name automatically.
Signals & Routing
AreRoute
AreRoute wraps an A_Route (path or regex) as an ARE signal. It is used to match the current document.location.pathname against registered route conditions:
import { AreRoute } from '@adaas/are-html';
// Matches /dashboard or any sub-path
new AreRoute('/dashboard');
// Regex route
new AreRoute(/^\/user\/\d+/);AreRoute.default() returns an AreRoute for the current pathname.
AreRoot component
AreRoot is a built-in Are component that powers <are-root> elements. It resolves which child component to render based on incoming signals or a default attribute, then performs a full lifecycle cycle (tokenize → compile → mount) on the new child:
<!-- render 'dashboard-component' by default -->
<are-root id="main" default="dashboard-component"></are-root>Route-driven rendering uses AreSignalsContext / AreSignalsMeta to select the component; when a new signal arrives the old child is unmounted, destroyed, and replaced.
Watcher
AreWatcher observes browser navigation events (pushState, replaceState, popstate, hashchange) and notifies handlers on URL change:
import { AreWatcher } from '@adaas/are-html';
const watcher = new AreWatcher();
const unsubscribe = watcher.onChange((url: URL) => {
console.log('Navigated to:', url.pathname);
});
// later
unsubscribe();
watcher.destroy();API Reference
| Export | Description |
|---|---|
| AreHTMLEngine | Main engine — register as a component |
| AreHTMLEngineContext | Context fragment — register as a fragment |
| AreHTMLNode | Base HTML node class |
| AreHTMLAttribute | Base attribute class |
| AreHTMLCompiler | Compiler (extendable) |
| AreHTMLInterpreter | DOM interpreter (extendable) |
| AreHTMLLifecycle | Node lifecycle hooks (extendable) |
| AreHTMLTokenizer | Markup attribute parser (extendable) |
| AreHTMLTransformer | Directive transform runner (extendable) |
| AreComponentNode | Generic element node |
| AreRootNode | <are-root> node |
| AreText | Text node |
| AreInterpolation | {{ }} interpolation node |
| AreComment | Comment node |
| AreStaticAttribute | Plain HTML attribute |
| AreBindingAttribute | :prop dynamic binding |
| AreEventAttribute | @event listener binding |
| AreDirectiveAttribute | $directive attribute |
| AreDirectiveIf | Built-in $if directive |
| AreDirectiveFor | Built-in $for directive |
| AreDirective | Base class for custom directives |
| AreDirectiveContext | Per-item scope context for directives |
| AreRoot | <are-root> component |
| AreWatcher | Browser navigation observer |
| AreRoute | Client-side routing signal |
| AddElementInstruction | DOM element creation instruction |
| AddTextInstruction | Text node instruction |
| AddCommentInstruction | Comment node instruction |
| AddAttributeInstruction | Attribute mutation instruction |
| AddStyleInstruction | Inline style mutation instruction |
| AddListenerInstruction | Event listener mutation instruction |
| AddInterpolationInstruction | Reactive interpolation instruction |
| AreHTMLInstructions | Instruction name constants |
License
ISC © ADAAS
