Skip to main content

Rendering

Rendering is the process of converting your model data into DOM elements using DSL templates. Barocss uses an efficient reconciliation algorithm similar to React.

Rendering Pipeline

Barocss supports two rendering targets from the same DSL templates:

The DOM path builds a virtual node tree and reconciles it against the previous tree. The React path produces React elements directly — no VNode intermediate.

1. Template Lookup

When rendering a model node, the renderer looks up the corresponding template from the registry:

const model = { stype: 'paragraph', sid: 'p1', text: 'Hello' };
const template = registry.get(model.stype); // Gets 'paragraph' template

2. VNode Building

The template and model data are combined to create a VNode (Virtual DOM node):

const vnode = vnodeBuilder.build(template, model);
// Result: { tag: 'p', attrs: {...}, children: [...] }

3. Reconciliation

The VNode is reconciled with the previous VNode to determine what DOM changes are needed:

domReconcile.reconcile(prevVNode, nextVNode, container);

DOM Reconciliation

Reconciliation is the process of efficiently updating the DOM to match the new VNode tree.

How It Works

  1. Create WIP Tree: Build a work-in-progress Fiber tree from the new VNode
  2. Detect Changes: Compare with previous Fiber to find differences
  3. Assign Priority: Prioritize changes (attributes, children, etc.)
  4. Apply Updates: Make minimal DOM changes

Change Detection

The renderer detects three types of changes:

  • Node Changes: Tag name or key changed → Replace node
  • Attribute Changes: Attributes changed → Update attributes
  • Child Changes: Children added/removed/reordered → Reconcile children

Efficient Updates

Only changed parts of the DOM are updated:

// If only text changed, only the text node is updated
// Parent elements are not recreated

Component Rendering

Components are rendered with access to both props and internal state:

// Component receives:
// - props: Model data
// - context: Component state and utilities
const elementTemplate = component.template(props, context);

Component State

Components can maintain internal state that persists across renders:

define('counter', component((props, context) => {
const count = context.state?.count || 0;
return element('div', {}, [
element('button', { onClick: () => setState({ count: count + 1 }) }, ['+']),
element('span', {}, [String(count)])
]);
}));

Decorators

Decorators are temporary UI elements that don't affect the model:

const decorators = [{
target: { nodeId: 'p1' },
type: 'highlight',
attrs: { class: 'highlight' }
}];

// Decorators are applied during rendering
// but don't change the underlying model

Rendering Performance

Optimizations

  1. Minimal DOM Updates: Only changed nodes are updated
  2. Priority-based Processing: Important changes are processed first
  3. Batch Updates: Multiple changes are batched together
  4. Key-based Reconciliation: Efficient child reconciliation using keys

Best Practices

  • Use stable keys for list items
  • Minimize template complexity
  • Avoid unnecessary re-renders
  • Use decorators for temporary UI instead of model changes

Example: Complete Rendering Flow

Here's a complete example showing the rendering process:

import { DOMRenderer } from '@barocss/renderer-dom';
import { getGlobalRegistry } from '@barocss/dsl';
import { define, element, data, slot } from '@barocss/dsl';

// 1. Register templates
const registry = getGlobalRegistry();
define('paragraph', element('p', { className: 'paragraph' }, [slot('content')]));
define('inline-text', element('span', { className: 'text' }, [data('text', '')]));

// 2. Create renderer
const renderer = new DOMRenderer(registry);

// 3. Model data
const model = {
stype: 'paragraph',
sid: 'p1',
content: [
{
stype: 'inline-text',
sid: 'text-1',
text: 'Hello, World!'
}
]
};

// 4. Render to DOM
const container = document.getElementById('editor');
const vnode = renderer.build(model, []);
renderer.render(container, vnode);

// 5. Update model (only changed parts are updated)
const updatedModel = {
...model,
content: [{
stype: 'inline-text',
sid: 'text-1',
text: 'Updated text' // Only this changed
}]
};

const newVnode = renderer.build(updatedModel, []);
renderer.render(container, newVnode); // Only text node is updated, not the paragraph

React Rendering

Barocss also supports React-native rendering via @barocss/renderer-react:

Model → Registry → Template → buildToReact → ReactNode

Unlike the DOM renderer (which uses VNode → DOMReconcile), the React renderer interprets the same DSL templates and produces React elements directly. This means:

  • Same templates: define() and defineMark() work identically
  • React output: Output is ReactNode instead of DOM nodes
  • Mark components: defineMark('type', external(ReactComponent)) renders marks as React components with markType, attributes, text, and children props
  • Decorator support: Inline, block, and layer decorators are rendered in the same React tree

Use @barocss/editor-view-react to integrate the React renderer with the editor.

See Renderer React Architecture and Editor View React Architecture for details.

Why This Approach?

  1. Performance: Minimal DOM updates for better performance
  2. Predictability: Clear rendering pipeline
  3. Testability: VNode tree can be tested independently
  4. Flexibility: Easy to add new rendering targets (not just DOM)

Next Steps