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
DSL-First Philosophy
In Barocss, everything is defined using DSL:
- Templates:
define('paragraph', element('p', {}, [slot('content')])) - Marks:
defineMark('bold', element('strong', {}, [data('text')]))ordefineMark('bold', external(BoldComponent)) - 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.
Dual Rendering Pipeline
The same DSL templates power two rendering targets:
Both paths share the same template definitions — you define once and render to either target.
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 templateexternal()- Wrap React components or DOM mount/unmount objects
All builders are pure functions.
Input: Builder parameters Output: Template
2a. 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)
Input: Template × Model data Output: VNode tree
2b. buildToReact (packages/renderer-react)
Role: Convert DSL templates directly to ReactNode
- Same template lookup from Registry
- Same data binding and component resolution
- Supports
external(ReactComponent)for marks and blocks - No VNode intermediate — outputs ReactNode directly
Input: Template × Model data Output: ReactNode
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)
Package Map
packages/
├─ schema/ # Document structure and validation
├─ datastore/ # Transactional node store (overlay/COW)
├─ model/ # Operations, transactions, undo/redo
├─ dsl/ # Declarative template builders ⭐
├─ renderer-dom/ # VNode reconciliation → DOM
├─ renderer-react/ # Direct → ReactNode (no VNode)
├─ editor-core/ # Commands, selection, extensions, history
├─ editor-view-dom/ # DOM input handling, selection sync
├─ editor-view-react/ # React view layer with hooks
├─ extensions/ # 30+ built-in extensions
├─ collaboration/ # CRDT/OT base adapter
├─ collaboration-yjs/ # Yjs adapter
├─ collaboration-liveblocks/ # Liveblocks adapter
├─ converter/ # HTML/Markdown/LaTeX/PDF conversion
├─ shared/ # Platform, keybinding, i18n utilities
├─ text-analyzer/ # Smart text diff (LCP/LCS)
├─ dom-observer/ # DOM mutation observer
└─ devtool/ # Dev tools (tree view, event log)
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
- Core Concepts: Rendering - Understand the rendering pipeline
- Renderer DOM - DOM rendering with VNode reconciliation
- Renderer React - React rendering (no VNode)
- Editor View DOM - DOM view layer
- Editor View React - React view layer
- Practical Examples - Real-world usage examples