A reactive and component-oriented DOM template engine for fine-grained rendering.@dooboostore/dom-rendertracks state changes through proxy-based observation and updates only affected render units.
DomRender,DomRenderProxy)${...}$,#...#,@this@)dr-if,dr-for,dr-for-of,dr-repeat,dr-strip,dr-appender)dr-event-*,dr-window-event-*,dr-on-init,dr-on-rendered-init)dr-formand validator integration)ComponentBase,createComponent, target elements/attrs)onCreateRender,onInitRender,onRawSetRendered, etc.)pnpm add @dooboostore/dom-render# ornpm install @dooboostore/dom-renderimport{DomRender}from'@dooboostore/dom-render';constapp =document.querySelector('#app')!;letstate = {title:'Hello DomRender',count:0,increment() {this.count+=1;
}
};
app.innerHTML=`
<h1>${@this@.title}$</h1>
<p>Count:${@this@.count}$</p>
<button dr-event-click="@this@.increment()">+1</button>
`;constresult =newDomRender({rootObject: state,target: app,config: {window}
});
state = result.rootObject;@dooboostore/dom-render's root entry (src/index.ts) re-exports as flat named exports — there is nocomponents/configs/etc. namespace object to import:
import{DomRender,DomRenderProxy,ComponentBase, attribute,Appender,DomRenderConfig}from'@dooboostore/dom-render';This covers: a selected set of built-in components/helpers (If,This,AppenderComponent,PromiseSwitch,Choose,CheckBox,Radio,Select,Timer,Details,Input,Route,RouterOutlet,A,ComponentSet,ComponentRouterBase,ComponentBase,attribute, and the combineddrComponentmap used to register them all at once), plus everything fromconfigs,decorators,events,lifecycle,messenger,operators(including theAppendercollection class),rawsets,types,DomRenderandDomRenderProxy.
Thequeryandeventdecorators arenotre-exported from the root; import them via the./*subpath export:
import{ query, event }from'@dooboostore/dom-render/components/ComponentBase';DomRenderwraps your root object with a proxy. Mutating fields onresult.rootObjecttriggers reactive rendering.
constresult =newDomRender({rootObject: {name:'kim',age:20},target: element,config: {window}
});constroot = result.rootObject;
root.age=21;// dependent template fragments update${expr}$: evaluate JS expression in render context#it#,#nearForOfIndex#: loop context placeholders@this@: current render/component instance pointer<div>${@this@.user.name}$</div><lidr-for-of="@this@.items">${#it#.title}$</li>RawSet)DOM fragments are segmented into units with dependency tracking. When path changes, only connected units are re-executed.
dr-ifConditional rendering based on expression truthiness.
<divdr-if="@this@.isLoggedIn">Welcome, ${@this@.user.name}$</div>dr-forClassicfor(...)loop.dr-option-itis evaluated per iteration and its value is substituted for#it#in the cloned markup.
<lidr-for="let i=0; i<@this@.items.length; i++"dr-option-it="i">${@this@.items[#it#].name}$</li>dr-for-ofArray/object iteration with#it#context replacement.
<ul><lidr-for-of="@this@.items">${#it#.name}$</li></ul>dr-repeatRepeat block by count/range expression.
<divdr-repeat="@this@.columns">Cell ${#it#}$</div>dr-appenderOptimized append/update/delete for list-like incremental data.
<ul><lidr-appender="@this@.rows">${#it#}$</li></ul>dr-strip/dr-option-stripStrip wrapper element while preserving children.
<divdr-if="@this@.ok"dr-option-strip="true">#innerHTML#</div>dr-inner-html: bindinnerHTML
dr-inner-text: bindinnerText
dr-this,dr-this-property: bind target context object
dr-option-before,dr-option-after: pre/post scripts attached alongside another directive on the same element
dr-value,dr-src,dr-checked,dr-selected,dr-readonly,dr-disabled,dr-hidden,dr-required,dr-open,dr-class,dr-style: bind the corresponding property/attribute from an expression
dr-attr="{key: expr, ...}": shorthand that expands each pair intokey="${expr}$"
Plain attributes can also be bound by putting a template expression inside the attribute value itself:
<divdr-inner-text="@this@.plainText"></div><divdr-inner-html="@this@.trustedHtml"></div><imgsrc="${@this@.imageUrl}$"alt="${@this@.alt}$"/><imgdr-attr="{src: @this@.imageUrl, alt: @this@.alt}"/>dr-form)dr-formwires every[name]child to the target object (optionally withValidator/ValidatorArrayfrom@dooboostore/core). Per-field options:dr-form:name,dr-form:event(defaultchange),dr-form:validator.
<formdr-form="@this@.formState"><inputname="email"dr-form:name="'email'"dr-form:event="change"/></form>Appendersupports keyed incremental collection updates.
import{Appender}from'@dooboostore/dom-render';conststate = {rows:newAppender('A','B')
};
state.rows.set('key-1','Updated A');
state.rows.delete('key-1');
state.rows.clear();dr-event-*directiveBind DOM events declaratively.
<buttondr-event-click="@this@.submit($event)">Submit</button><inputdr-event-input="@this@.onInput($event)"/>dr-window-event-*directiveListen to window-scoped events.
<divdr-window-event-resize="@this@.onResize($event)"></div>$event,$target, and render-bound objectsComponentBaseComponentBaseprovides:
@attribute)@query)@event)refreshDecorators,refreshQueryDecorators,refreshEventDecorators)@attributeimport{ComponentBase, attribute }from'@dooboostore/dom-render';classCardextendsComponentBase{@attribute('title')
title ='';@attribute({name:'count',converter:(v) =>Number(v ??0) })
count =0;
}@queryimport{ComponentBase, query }from'@dooboostore/dom-render';classPanelextendsComponentBase{@query('.title')titleEl?:HTMLElement;@query({selector:'.row',refreshRawSetRendered:true})rows:HTMLElement[] = [];
}@eventimport{ComponentBase, event }from'@dooboostore/dom-render';classToolbarextendsComponentBase{@event({query:'.save-btn',name:'click'})onSave() {console.log('saved');
}
}createComponentimport{DomRender}from'@dooboostore/dom-render';classUserCard{
name ='Anonymous';
}constuserCard =DomRender.createComponent({type:UserCard,tagName:'user-card',template:`<div>${@this@.name}$</div>`});// DomRender.run({...}) is a shorthand that returns the proxied rootObject directly.constapp =newDomRender({rootObject: {users: [] },target:document.querySelector('#app')!,config: {window,targetElements: [userCard] }
});Implement interfaces to receive lifecycle callbacks:
onProxyDomRender(config)onCreateRender(...args)/onCreateRendered(...args)onCreateRenderData(data)onCreatedThisChild(child, childData)onInitRender(param, rawSet)onRawSetRendered(rawSet, otherData)onChildRawSetRendered()(interface inlifecycle/OnChildRawSetRendered, not re-exported fromlifecycle/index)onChildRenderedByProperty(key, value)onDestroyRender(params)onChangeAttrRender(name, value, other)onBeforeReturnGet(name, value, fullPath)/onBeforeReturnSet(name, value, fullPath)classViewModel{onCreateRender() {console.log('render create');
}asynconInitRender() {console.log('render initialized');
}
}DomRendercan create router byrouterType:
'hash'->HashRouter'path'->PathRouterconstapp =newDomRender(
{rootObject: {page:'home'},target:document.querySelector('#app')!,config: {window,routerType:'hash'}
},
{firstUrl:'home'}
);
app.router.go('/about');DefaultMessengerandMessengerprovide channel-based communication across render roots/components.
Use this when direct parent-child access is not suitable.
This section is added for teams using bothdom-renderandsimple-web-component.
@event/@addEventListener(extended)@event(alias of@addEventListener) is a decorator from@dooboostore/simple-web-component; it is unrelated to dom-render's own@event({ query, name }).
Indom-render, equivalent patterns are:
dr-event-*@event({ query, name })<buttondr-event-click="@this@.handleClick($event)">Click</button>classMyCompextendsComponentBase{@event({query:'.btn',name:'click'})onClick(e:Event) {}
}@replaceChildren(extended)@replaceChildrenis fromsimple-web-component.
dom-renderalternatives:
dr-if,dr-for-of,dr-appender)replaceChildren) when neededupdateRows(container:HTMLElement,nodes:Node[]) {
container.replaceChildren(...nodes);
}For reactive templates, prefer declarative updates over manual replacement.
@insertBeforeEnd(extended)simple-web-componentappends via@insertBeforeEnd(there is no@appendChilddecorator).
dom-renderalternatives:
dr-appender+Appenderfor incremental append/deleteappendChildin imperative setup codestate.rows.set('k1','first row');// reactive append through dr-appenderresult.rootObject(proxied object), not the original plain object.dr-appenderfor large frequently changing collections.refreshDecorators()when bindings need explicit refresh.DomRenderNoProxyfor fields that should never trigger proxy traversal.result.rootObject, not stale reference.@this@.x.y).dr-event-click,dr-event-input, etc.).$event,$target).Appenderupdates instead of full array replacement.This package is licensed under theMIT License.