@dooboostore/dom-render
v1.0.113
Published
html view template engine
Downloads
137
Maintainers
Readme
@dooboostore/dom-render
Full Documentation: https://dooboostore-develop.github.io/@dooboostore/dom-render
A reactive and component-oriented DOM template engine for fine-grained rendering. @dooboostore/dom-render tracks state changes through proxy-based observation and updates only affected render units.
Features
- Reactive object proxy rendering (
DomRender,DomRenderProxy) - HTML template bindings (
${...}$,#...#,@this@) - Structural directives (
dr-if,dr-for,dr-for-of,dr-repeat,dr-strip,dr-appender) - Event directives (
dr-event-*,dr-window-event-*,dr-on-init,dr-on-rendered-init) - Form directives (
dr-formand validator integration) - Component system (
ComponentBase,createComponent, target elements/attrs) - Lifecycle hooks (
onCreateRender,onInitRender,onRawSetRendered, etc.) - Built-in messenger for inter-component pub/sub
- Router integration (Path/Hash) via config
Installation
pnpm add @dooboostore/dom-render
# or
npm install @dooboostore/dom-renderQuick Start
import { DomRender } from '@dooboostore/dom-render';
const app = document.querySelector('#app')!;
let state = {
title: 'Hello DomRender',
count: 0,
increment() {
this.count += 1;
}
};
app.innerHTML = `
<h1>${@[email protected]}$</h1>
<p>Count: ${@[email protected]}$</p>
<button dr-event-click="@[email protected]()">+1</button>
`;
const result = new DomRender({
rootObject: state,
target: app,
config: { window }
});
state = result.rootObject;Public API (Root Export)
@dooboostore/dom-render now exposes modules from root entry:
componentsconfigsdecoratorseventslifecyclemessengeroperatorsrawsetstypesDomRenderDomRenderProxy
This enables single-entry usage without relying on package subpath exports.
Core Concepts
1) Reactive Root Object
DomRender wraps your root object with a proxy. Mutating fields on result.rootObject triggers reactive rendering.
const result = new DomRender({
rootObject: { name: 'kim', age: 20 },
target: element,
config: { window }
});
const root = result.rootObject;
root.age = 21; // dependent template fragments update2) Template Expressions
${expr}$: evaluate JS expression in render context#it#,#nearForOfIndex#: loop context placeholders@this@: current render/component instance pointer
<div>${@[email protected]}$</div>
<li dr-for-of="@[email protected]">${#it#.title}$</li>3) Incremental Render Units (RawSet)
DOM fragments are segmented into units with dependency tracking. When path changes, only connected units are re-executed.
Structural Directives
dr-if
Conditional rendering based on expression truthiness.
<div dr-if="@[email protected]">
Welcome, ${@[email protected]}$
</div>dr-for
Classic loop-style directive.
<li dr-for="let i=0; i<@[email protected]; i++" dr-option-it="@[email protected][i]">
${destIt.name}$
</li>dr-for-of
Array/object iteration with #it# context replacement.
<ul>
<li dr-for-of="@[email protected]">
${#it#.name}$
</li>
</ul>dr-repeat
Repeat block by count/range expression.
<div dr-repeat="@[email protected]">Cell ${#it#}$</div>dr-appender
Optimized append/update/delete for list-like incremental data.
<ul>
<li dr-appender="@[email protected]">${#it#}$</li>
</ul>dr-strip / dr-option-strip
Strip wrapper element while preserving children.
<div dr-if="@[email protected]" dr-option-strip="true">#innerHTML#</div>Template Utilities
Dynamic content directives
dr-inner-html: bindinnerHTMLdr-inner-text: bindinnerTextdr-attr: bind attribute objectdr-this,dr-this-property: bind target context objectdr-before,dr-after: pre/post scripts around operator execution
<div dr-inner-text="@[email protected]"></div>
<div dr-inner-html="@[email protected]"></div>
<img dr-attr="{ src: @[email protected], alt: @[email protected] }" />Form utility (dr-form)
dr-form wires fields and optional validator metadata.
<form dr-form="@[email protected]">
<input name="email" dr-form:name="'email'" dr-form:event="change" />
</form>Appender utility class
Appender supports keyed incremental collection updates.
import { Appender } from '@dooboostore/dom-render';
const state = {
rows: new Appender('A', 'B')
};
state.rows.set('key-1', 'Updated A');
state.rows.delete('key-1');
state.rows.clear();Event System
dr-event-* directive
Bind DOM events declaratively.
<button dr-event-click="@[email protected]($event)">Submit</button>
<input dr-event-input="@[email protected]($event)" />dr-window-event-* directive
Listen to window-scoped events.
<div dr-window-event-resize="@[email protected]($event)"></div>EventManager behavior
- delegated listener strategy for common events
- direct attachment for non-delegatable events
- execution context variables:
$event,$target, and render-bound objects
Component Architecture
ComponentBase
ComponentBase provides:
- attribute binding (
@attribute) - query binding (
@query) - event binding (
@event) - child component tracking
- lifecycle integration
- decorator refresh APIs (
refreshDecorators,refreshQueryDecorators,refreshEventDecorators)
@attribute
import { ComponentBase, attribute } from '@dooboostore/dom-render';
class Card extends ComponentBase {
@attribute('title')
title = '';
@attribute({ name: 'count', converter: (v) => Number(v ?? 0) })
count = 0;
}@query
import { ComponentBase, query } from '@dooboostore/dom-render';
class Panel extends ComponentBase {
@query('.title')
titleEl?: HTMLElement;
@query({ selector: '.row', refreshRawSetRendered: true })
rows: HTMLElement[] = [];
}@event
import { ComponentBase, event } from '@dooboostore/dom-render';
class Toolbar extends ComponentBase {
@event({ query: '.save-btn', name: 'click' })
onSave() {
console.log('saved');
}
}createComponent
import { DomRender } from '@dooboostore/dom-render';
class UserCard {
name = 'Anonymous';
}
const userCard = DomRender.createComponent({
type: UserCard,
tagName: 'user-card',
template: `<div>${@[email protected]}$</div>`
});
const app = new DomRender({
rootObject: { users: [] },
target: document.querySelector('#app')!,
config: { window, targetElements: [userCard] }
});Lifecycle Hooks
Implement interfaces to receive lifecycle callbacks:
onProxyDomRender(config)onCreateRender(...args)onCreateRenderData(data)onInitRender(param, rawSet)onRawSetRendered(rawSet, otherData)onChildRawSetRendered()onDestroyRender(params)onChangeAttrRender(name, value, other)
class ViewModel {
onCreateRender() {
console.log('render create');
}
async onInitRender() {
console.log('render initialized');
}
}Router Integration
DomRender can create router by routerType:
'hash'->HashRouter'path'->PathRouter- custom router object/factory
const app = new DomRender(
{
rootObject: { page: 'home' },
target: document.querySelector('#app')!,
config: { window, routerType: 'hash' }
},
{ firstUrl: 'home' }
);
app.router.go('/about');Messenger
DefaultMessenger and Messenger provide channel-based communication across render roots/components.
Use this when direct parent-child access is not suitable.
Requested Decorator Mapping
This section is added for teams using both dom-render and simple-web-component.
@addEventListener (extended)
@addEventListener is a decorator from @dooboostore/simple-web-component, not from dom-render directly.
In dom-render, equivalent patterns are:
- Template directive:
dr-event-* - Class decorator in component:
@event({ query, name })
<button dr-event-click="@[email protected]($event)">Click</button>class MyComp extends ComponentBase {
@event({ query: '.btn', name: 'click' })
onClick(e: Event) {}
}@replaceChildren (extended)
@replaceChildren is from simple-web-component.
dom-render alternatives:
- structural replacement via directives (
dr-if,dr-for-of,dr-appender) - explicit DOM strategy in component methods (
replaceChildren) when needed
updateRows(container: HTMLElement, nodes: Node[]) {
container.replaceChildren(...nodes);
}For reactive templates, prefer declarative updates over manual replacement.
@appendChild (extended)
@appendChild is from simple-web-component.
dom-render alternatives:
dr-appender+Appenderfor incremental append/delete- direct
appendChildin imperative setup code
state.rows.set('k1', 'first row'); // reactive append through dr-appenderBest Practices
- Always mutate
result.rootObject(proxied object), not the original plain object. - Prefer declarative directives over direct DOM mutation.
- Use
dr-appenderfor large frequently changing collections. - For dynamic component trees, call
refreshDecorators()when bindings need explicit refresh. - Use
DomRenderNoProxyfor fields that should never trigger proxy traversal. - Keep heavy computations outside template expressions.
Troubleshooting
UI not updating after mutation
- Check that you are mutating
result.rootObject, not stale reference. - Verify expression paths are valid (
@[email protected]).
Event not firing
- Confirm directive name (
dr-event-click,dr-event-input, etc.). - Check script context variables (
$event,$target).
Render loop/performance drop
- Avoid expensive script evaluation in template.
- Prefer keyed
Appenderupdates instead of full array replacement.
License
This package is licensed under the MIT License.
