Architecture Overview
Barocss Editor uses a model-first, DSL-first architecture. All operations work on a model, and everything (templates, marks, decorators, operations) is defined using DSL.
Core Structure
Model → DSL → VNodeBuilder → VNode → DOMReconcile → DOM
↑
element, data, when, component, slot, portal
DSL-First Philosophy
In Barocss, everything is defined using DSL:
- Templates:
define('paragraph', element('p', {}, [slot('content')])) - Marks:
defineMark('bold', element('strong', {}, [data('text')])) - Decorators:
defineDecorator('highlight', element('span', {}, [])) - Operations:
defineOperationDSL('insertText', (p) => insertText({ text: p.text }))
This unified approach provides consistency, type safety, and composability across all definitions.
Complete Pipeline
1. DSL Layer (packages/dsl)
└─ element('p', {...}, [data('text')]) → Template
2. VNode Builder (packages/renderer-dom)
└─ Template × Data → VNode
3. DOM Reconcile (packages/renderer-dom)
└─ VNode × VNode → DOM
Role of Each Layer
1. DSL Layer (packages/dsl)
Role: Functional template definition
element()- HTML element templatedata()- Data bindingwhen()- Conditional renderingcomponent()- Component templateslot()- Slot templateportal()- Portal template
All builders are pure functions (Pure Functions)
Input: Builder parameters Output: Template
2. VNodeBuilder (packages/renderer-dom)
Role: Convert DSL templates to VNode
- Template lookup from Registry
- Data binding (data(), className, style)
- Component resolution
- Conditional rendering (build time)
when()conditions are evaluated at build time and converted to regular VNode
Input: Template × Model data Output: VNode tree
3. DOMReconcile (packages/renderer-dom)
Role: Convert VNode differences to DOM changes
- WIP tree creation and management
- Change detection (tag, attrs, children)
- Priority-based processing
- Minimal DOM changes applied
- Children reconcile (React-style)
Input: prevVNode, nextVNode Output: DOM updates
4 Steps of Reconcile
reconcile(prevVNode, nextVNode, container, context) {
// 1. Create WIP Tree
const wipTree = createWorkInProgressTree(nextVNode, prevVNode);
// 2. Detect changes and assign priority
detectChangesAndAssignPriority(prevVNode, nextVNode);
// 3. Process by priority
processByPriority(context, processWorkInProgress);
// 4. Execute DOM updates
executeDOMUpdates(container, finalizeDOMUpdate);
}
Children Reconcile Core
// Reconciliation that directly manipulates DOM
reconcileChildren(wip, prevChildren, nextChildren) {
while (prevIndex < prevChildren.length || nextIndex < nextChildren.length) {
if (!prevChild) {
// Add new child
const newNode = createNewDOMNode(nextChild);
domNode.insertBefore(newNode, referenceNode);
// Important: Set child WIP's domNode
wip.children[nextIndex].domNode = newNode;
} else if (isSameNode(prevChild, nextChild)) {
// Same node - skip
} else {
// Replace node
const newNode = createNewDOMNode(nextChild);
domNode.replaceChild(newNode, oldNode);
wip.children[nextIndex].domNode = newNode;
}
}
}
Core Rule: DOM nodes created/replaced in reconcile must be set in child WIP's domNode to prevent duplicate append
Key Classes
| Class/Function | Layer | Role |
|---|---|---|
element, data, when, component | DSL | Template builder (pure functions) |
VNodeBuilder | VNode | DSL template → VNode conversion |
DOMReconcile | Renderer | VNode → DOM reconcile orchestration |
WorkInProgressManager | Renderer | WIP tree creation and management |
ChangeDetection | Renderer | Change detection |
DOMProcessor | Renderer | DOM manipulation (insert/update/remove) |
ComponentManager | Renderer | Component lifecycle |
PortalManager | Renderer | Portal rendering |
DOMOperations | Renderer | DOM creation/modification utilities |
Core Rules
- DSL builders define templates as pure functions
- VNodeBuilder only converts templates and data to VNode
- DOMReconcile converts VNode differences to DOM changes
- WIP pattern batches actual changes
- children reconcile must set created DOM nodes in child WIP's domNode
- finalizeDOMUpdate prevents duplicate append (
isAlreadyInDOMcheck)
File Structure
packages/
├─ schema/ # Schema definition
├─ dsl/ # DSL Layer ⭐
│ ├─ template-builders.ts # element, data, when, component
│ ├─ types.ts # Template types
│ └─ registry.ts # Template registry
├─ vnode/ # VNodeBuilder
│ └─ factory.ts # DSL Template → VNode conversion
├─ model/ # Model data
├─ renderer-dom/
│ ├─ dom-renderer.ts # High-level wrapper
│ ├─ dom-reconcile.ts # Main reconcile
│ ├─ work-in-progress.ts # WIP interfaces
│ ├─ work-in-progress-manager.ts
│ ├─ change-detection.ts # Change detection
│ ├─ dom-processor.ts # DOM manipulation
│ ├─ component-manager.ts # Component lifecycle
│ ├─ portal-manager.ts # Portal rendering
│ └─ dom-operations.ts # DOM utilities
└─ datastore/ # Data management
Usage Examples
For detailed examples, see Practical Examples
Basic Render
// Define DSL template
define('paragraph', element('p', {}, [data('text')]));
// Render
const renderer = new DOMRenderer();
const model = { stype: 'paragraph', text: 'Hello' };
renderer.render(container, model);
// Result: <p>Hello</p>
Update (Automatic Reconcile)
model.text = 'New';
renderer.render(container, model);
// Result: <p>New</p> (only text changed without full regeneration)
Complex Template
define('article', element('article',
{ className: data('className') },
[
when(
(d) => d('published') === true,
element('span', {}, ['Published'])
),
element('h1', {}, [data('title')]),
component('author', { name: data('author') })
]
));
Related Documents
- Package Structure - Learn about each package's role and how to extend the editor
- Core Concepts: Rendering - Understand the rendering pipeline
- Practical Examples - Real-world usage examples