vfunc.js

Compare: Vanilla · vfunc · React/Vue

The same feature (add a to-do, show the list) written three ways shows the differences. None is always better; choose for the job.

Vanilla JS

const list = document.querySelector('#list');
const items = [];
document.querySelector('#add').addEventListener('click', () => {
  const input = document.querySelector('#text');
  items.push(input.value);
  list.innerHTML = items.map((t) => '<li>' + t + '</li>').join('');  // no escaping → XSS
  input.value = '';
});

vfunc.js

const todo = vf.vfunc({
  state: { items: [] },
  render: (s) => vf.html`
    <form data-action="add"><input data-ref="text"><button>Add</button></form>
    <ul>${s.items.map((t) => vf.html`<li>${t}</li>`)}</ul>`,
  delegates: [{ selector: '[data-action="add"]', eventType: 'submit', onEvent: (e) => {
    e.event.preventDefault();
    e.sender.setState({ items: e.sender.items.concat(e.sender.refs.text.value) });
  } }]
});
todo.mount('#app');

React

function Todo() {
  const [items, setItems] = useState([]);
  const [text, setText] = useState('');
  return (
    <>
      <form onSubmit={(e) => { e.preventDefault(); setItems([...items, text]); setText(''); }}>
        <input value={text} onChange={(e) => setText(e.target.value)} /><button>Add</button>
      </form>
      <ul>{items.map((t, i) => <li key={i}>{t}</li>)}</ul>
    </>
  );
}

What to use when

Vanillavfunc.jsReact / Vue
Buildnonenonerequired
Size (gzip)0about 9 KBabout 34–45 KB + app
Escapingby handautomatic, by positionautomatic (JSX / templates)
Behaviour on existing HTMLby handvf.attachhard
Large listsoptimize by handweaker with render (full replace), close with vf.ext.list (measured)strong
IE11by handlegacy filepractically no
Ecosystem, hiring—smalllarge

vfunc.js fits well when you add behaviour to server-rendered or published HTML, when a build pipeline is hard to set up (intranets), when you build screens quickly with AI, and when IE still has to work.