JSFiddle - React, Tailwind, and code Playground

HTML

<div id="root"></div>
<script>
let name;
const container = document.getElementById('root');
const redraw = () => render(container, createFragment());

function createFragment() {
	return {
    tag: 'div',
    attrs: {class: "app"},
    children: [
      {tag: "h1", children: `Hi, ${name || '%username%'}!`},
      {tag: "button", attrs: {
      	class: "button",
        name: name ? "exit" : "enter",
        onClick: handleClick,
      }, children: name ? "Exit" : "Enter"},
    ],
  };
}

function handleClick(evt) {
	name = evt.currentTarget.name === "enter" && prompt("Name?");
  redraw();
}

redraw();
</script>

CSS

.app {
  text-align: center;
  marging: 30px;
}

.button {
  font-size: 30px;
}

JavaScript

//
// API
//
const TEXT_NODE = '#';
const R_IS_EVENT = /^on[A-Z]/;
const R_IS_TEXT = /string|number|boolean/;
const CLASS_ATTR = 'class';
const VALUE_ATTR = 'value';

function normalize(node) {
  if (node == null) {
    return {tag: TEXT_NODE, children: ''};
  } else if (node.tag == null) {
  	return {tag: TEXT_NODE, children: node};
  } else {
  	return node;
  }
}

function create(/* Object */node, /* HTMLElement */parent) {
  node = normalize(node); // "str" -> {tag: "#", children: "..."}
  let el;

  if (TEXT_NODE === node.tag) {
    el = document.createTextNode(node.children);
  } else {
    el = document.createElement(node.tag);
    updateAttrs(el, node.attrs); // добавляем аттрибуты
    createChildren(node, el); // создаеём детишек
  }

  node.el = el; // запоминаем DOM
  (parent !== null) && parent.appendChild(el);

  return node;
}

function updateAttrs(/* HTMLElement */el, attrs, oldAttrs) {
  let name;
  let value;
  for (name in attrs) {
    value = attrs[name];
    if (oldAttrs == null || oldAttrs[name] !== value) {
      if (CLASS_ATTR === name) { // className
        el.className = value;
      }
      else if (VALUE_ATTR === name) { // value
        (el[name] !== value)  && (el[name] = value);
      }
      else if (R_IS_EVENT.test(name)) { // события
        el.addEventListener(name.substr(2).toLowerCase(), value, false);
      }
      else { // все остальные
        el.setAttribute(name, value);
      }
    }
  }
  
  if (oldAttrs != null) {
    for (name in oldAttrs) {
      value = oldAttrs[name];

      if (attrs[name] == null && attrs[name] !== value) {
        if (R_IS_EVENT.test(name)) {
          el.removeEventListener(name.substr(2).toLowerCase(), value, false);
        } else {
          el.removeAttribute(name);
        }
      }
    }
  }
}

function createChildren(/* Object */node, /* HTMLElement */parent) {
  const children = node.children;
  const length = children.length;
 
  if (length == null || R_IS_TEXT.test(typeof children))...