On This Page
Rendering Engines
An engine is a plain object with three keys, registered under a name. The framework calls into it at two points: once when a component is defined, and once when an instance initializes.
| Key | Type | Called by |
|---|---|---|
factory |
function | defineComponent, to build the custom element class |
renderer |
class | Template.initialize(), to render on the client |
serverRenderer |
class | Template.initialize(), to render to a string. Optional |
Without a serverRenderer an engine falls back to its client renderer on the server, which is why lit omits one.
Registration
registerEngine
registerEngine(name, engine);Adds an engine to the registry.
Parameters
| Name | Type | Description |
|---|---|---|
| name | string | The value renderingEngine matches against |
| engine | object | An object with factory, renderer, and optionally serverRenderer |
Usage
import { registerEngine, Renderer } from '@semantic-ui/renderer';import { createComponent } from './factory.js';
registerEngine('native', { factory: createComponent, renderer: Renderer,});Registration is a side effect of importing the module that calls it. @semantic-ui/component registers native on import, and @semantic-ui/component/server adds the engine’s serverRenderer when it loads, so a browser bundle carries no server renderer. The Lit engine registers only when you import it:
import '@semantic-ui/component/engines/lit/register.js';getEngine
getEngine(name);Returns the registered engine, or undefined.
Selecting an Engine
defineComponent({ tagName: 'my-card', template, renderingEngine: 'lit',});The name resolves once, at definition time. renderingEngine also accepts an engine object, which skips the registry. An unregistered name is fatal rather than silent, since the alternative is a component that renders nothing.
Two components on the same page can use different engines. Every stage before rendering is shared.
Adapter Touchpoints
factory
Called once per component definition. It receives the shared configuration and returns a class for customElements.define.
factory({ prototypeTemplate, // the Template every instance clones from resolvedProperties, // property definitions, for observedAttributes css, delegatesFocus, componentSpec, defaultSettings, plural, onAttributeChanged, renderingEngine,});The native factory returns a class extending HTMLElement. The Lit factory returns one extending LitElement. What the class extends is the engine’s decision, so an engine targeting something other than the DOM defines what its “element” means.
renderer
Constructed once per instance, during Template.initialize().
new RendererClass({ ast, // compiled template, shared across instances data, // the flat data context, with settings overlaid as signals template, // the owning Template subTemplates, helpers, receivesData, // true when the template is a subtemplate});The instance must expose render(). The native renderer returns a DocumentFragment, the Lit renderer a TemplateResult. Template stores whatever comes back and does not inspect it.
Shared Modules
An engine writes the walk and the binding. Everything below is exported from @semantic-ui/renderer so it does not have to write these too.
Expressions
ExpressionEvaluator resolves every expression form the template syntax accepts, so an engine does not reimplement the lookup cascade or the Lisp-style argument walk.
import { ExpressionEvaluator } from '@semantic-ui/renderer';HTML assembly
buildHTMLString turns an AST into one HTML string with markers at every dynamic position, paired with an entries array describing each marker. analyzePosition classifies a marker as attribute or text, boolean or quoted, property or event.
Both are pure and shared between the client and server renderers. An engine that produces a string benefits most; one that builds nodes directly may skip them.
Keyed list identity
{#each} is keyed. It reconciles by an item’s key rather than its index, so a row keeps its DOM node, its focus, and its per-item state across a reorder or a splice. The key is inferred from the item rather than declared in the template.
These resolve identity the same way the shipped engines do, so an engine reconciles a list identically without reimplementing the rules.
| Export | Purpose |
|---|---|
getItemId |
Resolves an item’s key, falling back through id, _id, hash, key, then index |
getCollectionType |
Classifies the iterated value as array, object, or primitive list |
getEachData |
Builds the per-item data context, including index and alias |
encodeItemKey / decodeItemKey |
Round-trips a key through a DOM-safe string |
lisIndices |
Longest increasing subsequence, for the minimum set of moves in a reorder |
Error handling
setRecovery and setTracing control what happens when an expression throws. isRecovery and isTracing read the current state, which an engine checks before deciding whether to swallow an error or let it escape.
Writing an Engine
The two shipped engines are worked examples of the same contract. Native assembles an HTML string, parses it once, then binds markers. Lit accumulates static strings and directive instances, then hands both to lit-html.