docs: function updateDOM(source,target,scope,actions,view=target,template=source) {
  		Object.defineProperty(target,"view",{enumerable:false,configurable:true,writable:true,value:view});
  		Object.assign(scope,{"$source":source,
  												"$target":target,
  												"$template":template.children ? (template.children.length> 1 ? template.children : template.children[0]) : template,
  												"$view":view.children.length> 1 ? view.children : view.children[0]});
  		// if source and target are text
  		if(typeof(source)==="string") {
  			const value = (resolve(source,scope,actions)+""),
  					doc = new DOMParser().parseFromString(value, "text/html"),
  		  		children = slice(doc.body.childNodes);
  		  if(children.some(node => node.nodeType === 1)) {
  		  	updateDOM(toVDOM(doc.body),doc.body,scope,actions,view,template);
  		  	let parent;
  		  	if(target.nodeType===3) {
  			  	parent = target.parentElement;
  			  	parent.removeChild(target);
  		  	} else if(target.nodeType===1) {
  		  		parent = target;
  		  		while(parent.lastChild) parent.removeChild(parent.lastChild);
  		  	}
  		  	slice(doc.body.childNodes).forEach(child => parent.appendChild(child));
  		  } else if(target.data!==value) {
  		  	target.data = value;
  		  }
  		  return view;
  		}
  		target.view.template = target.view.innerHTML;
  		// if tags aren't the same
  		if(source.tagName!==target.tagName) {
  			// replace the target with the source
  			if(target.parentNode) {
  				const el = document.createElement(source.tagName);
  				target.parentNode.replaceChild(el,target);
  				target = el;
  			}
  		}
  		// loop through target attributes, remove where does not exist in source
  			slice(target.attributes).forEach(attribute => source.attributes[attribute.name]!==null || target.removeAttribute(attribute.name));
  			// loop through source attributes
  			let directed;
  			Object.keys(source.attributes).forEach(aname => {
  				let value = source.attributes[aname],
  				 type = typeof(value),
  				 currentview = CURRENTVIEW;
  				CURRENTVIEW = target;
  				const dname = resolve(aname.split(":")[0],scope,actions),
  					directive = directives[dname]||tlx.directives[dname];
  				value = resolve(value,scope,actions);
  				CURRENTVIEW = currentview;
  				type = typeof(value);
  				// replace different
  				if(value==null) {
  					target.removeAttribute(aname);
  					delete target[aname];
  				} else if(["boolean","number","string"].includes(type)) {
  					if(value!==target.getAttribute(aname)) {
  						try {
  							target.setAttribute(aname,value);
  							if(aname==="value") target.value = value;
  						} catch(e) {
  							; // just ignore, may be an attribute with an unresolved argument, e.g. t-bind:${aname}
  						}
  					}
  				} else {
  						const pname = toPropertyName(aname);
  						if(value!==target[pname]) target[pname] = value;
  				}
  				if(directive) { 
  					while(target.lastChild) target.removeChild(target.lastChild); 
  					const render = (scope,actions) => {
  						source.children.forEach(child => {
  							if(typeof(child)==="string") target.appendChild(new Text(child));
  							else target.appendChild(document.createElement(child.tagName));
  							updateDOM(child,target.lastChild,scope,actions,view,template);
  						});
  						return target;
  					};
  					const result = directive(value,scope,actions,render,{element:target,raw:aname,resolved:aname.indexOf("${")>=0 ? resolve(aname,scope,actions) : aname}),
  						rtype = typeof(result);
  					if(result==null) {
  						target.parentElement.removeChild(target);
  					} else if(rtype==="string") {
  							directed = true;
  							target.innerHTML = result;
  					} else if(rtype==="object" && result instanceof HTMLElement) {
  						directed = true;
  						if(result!==target) target.parentElement.replaceChild(result,target);
  					}
  				}
  			});
  			if(!directed) {
  				// remove extra children
  				let remove = target.childNodes.length-source.children.length;
  				while(remove-->0) !target.lastChild || target.removeChild(target.lastChild);
  				// handle the child nodes
  				let targets = slice(target.childNodes,0,source.children.length);
  				if(targets.length===0) {
  					const shadow = target.shadowRoot;
  					if(shadow) targets = slice(shadow.childNodes,0,source.children.length);
  				}
  				source.children.forEach((child,i) => {
  					// update if in range
  					if(i<targets.length) {
  						updateDOM(child,targets[i],scope,actions,view,template);
  						return;
  					}
  					if(typeof(child)==="string") target.appendChild(new Text());
  					else target.appendChild(document.createElement(child.tagName));
  					updateDOM(child,target.lastChild,scope,actions,view,template);
  				});
  			}
  		
  		return view;
  	}