Dom Dom Factory ๐Ÿญ

PoV: You donโ€™t want to use a frontend framework because you think itโ€™s overkill. You want to use Vanilla JS to manipulate the DOM, but then you realize your code gets messy, filled with so many document.createElement, element.setAttribute, & element.appendChild. Youโ€™re considering a minimal abstraction with an extremely small bundle size (1.7ย KB).

Comparison

10
129
react + react-dom
85
jQuery
dom-dom-factory

Render

Combine the declarative React-like syntax with the flexibility of vanilla JS. ๐Ÿ˜‡

<div id="example-render"></div>

<script>
  const myElement = document.querySelector('#example-render');

  $(myElement, { className: 'bg-yellow-300 rounded my-3 p-3' }, [
    $('div', {}, 'Hello world!'),
    $('ul', { className: 'list-disc pl-5' }, [
      $('li', {}, 'Foo'),
      $('li', {}, ['Bar ', $('b', { className: 'font-bold' }, 'Baz')]),
    ]),
  ]);
</script>

State

By default, there is no re-rendering at all.
You control which HTML elements should be re-rendered.

<div id="example-state"></div>

<script>
  const MyComponent = (initialCounter = 1) => {
    const counter = $.createState(initialCounter);

    return $('div', { className: 'bg-orange-300 p-2 rounded flex gap-2' }, [
      $('div',
        { ref: counter.ref }, // ๐Ÿ‘ˆ put ref in any element you want to re-render
        ['Count: ', counter.get]),
      $('div',
        // ๐Ÿ‘‡ Use function for dynamic props
        () => ({
          ref: counter.ref,
          className: ['size-3 rounded-full', counter.current % 2 ? 'bg-white' : 'bg-black'],
        })
      ),
      $('div',
        { className: 'ml-auto' },
        'No re-rendered here'),
      $('button',
        { onClick: () => counter.set((prev) => prev + 1) },
        'Increment'
      ),
    ]);
  };

  $(
    document.querySelector('#example-state'),
    { className: 'bg-yellow-300 rounded my-3 p-3 space-y-2' },
    [
      MyComponent,     // A function (a.k.a component)
      MyComponent(),   // An element
      MyComponent(30), // An element
    ],
  );
</script>

Global State & Local State

Here is an example of a simple to-do app that covers both global & local state.

Installation

TypeScript Project:

You can copy the code here:
https://github.com/afiiif/dom-dom-factory/blob/main/lib/dom.ts

JavaScript Project:

Add this minified script ๐Ÿ‘‡ Only 1.7 KB

{let v=(e,p={},u=[])=>{let y=e instanceof HTMLElement?e:document.createElement(e),m=(e="all")=>{var t="function"==typeof p?p():p;let r=new Map;if("all"===e||"props"===e)for(var[n,a]of Object.entries(t))if(void 0!==a)if("ref"===n){var i,o=(e="all")=>{if("all"===e||"props"===e){for(;0<y.attributes.length;)y.removeAttribute(y.attributes[0].name);r.forEach((e,t)=>{y.removeEventListener(t,e)})}"all"!==e&&"children"!==e||(y.innerHTML=""),m(e)};for(i of Array.isArray(a)?a:[a])"function"==typeof i?i({element:y,render:o}):i instanceof Map?i.set(y,o):(i.element=y,i.render=o)}else if(n.startsWith("on")){var s=n.substring(2).toLowerCase();y.addEventListener(s,a),r.set(s,a)}else if("className"===n)a&&(s=Array.isArray(a)?a:[a],y.className=s.filter(Boolean).join(" "));else if("style"===n)for(var[f,l]of Object.entries(a))void 0!==l&&(y.style[f]=l);else if("data"===n)for(var[c,d]of Object.entries(a))void 0!==d&&(y.dataset[c]=d);else n.startsWith("aria")?y.setAttribute(n,String(a)):y[n]=a;"all"!==e&&"children"!==e||v.append(y,u)};return m(),y};v.append=(e,t)=>{var r,n;for(r of Array.isArray(t)?t:[t])r instanceof HTMLElement?e.appendChild(r):"string"==typeof r||"number"==typeof r?e.appendChild(document.createTextNode(String(r))):"function"==typeof r&&((n=r())instanceof HTMLElement?e.appendChild(n):"string"==typeof n||"number"==typeof n?e.appendChild(document.createTextNode(String(n))):Array.isArray(n)&&v.append(e,n));return e},v.createRef=()=>{let e=new Map;return Object.assign(e,{render:t=>{e.forEach(e=>e(t))}})},v.createState=e=>{let t=v.createRef(),r={current:e,ref:t,get:()=>r.current,set:e=>{r.current="function"==typeof e?e(r.current):e,t.render()}};return r},window.$=v}