1KB Frontend Library

Today’s Challenge: Build a 1KB Frontend Library

Let’s tackle an exciting challenge today: creating a frontend library that’s just 1 kilobyte in size. I’m talking about a “disappearing framework” — not like Svelte, which only “disappears” aft…


This content originally appeared on DEV Community and was authored by Fedor

Today’s Challenge: Build a 1KB Frontend Library

Let’s tackle an exciting challenge today: creating a frontend library that’s just 1 kilobyte in size. I’m talking about a "disappearing framework" — not like Svelte, which only "disappears" after compilation. No build tools, no bloated node_modules folder hogging your SSD. Just a few lightweight JavaScript functions you can copy, paste, and use right away.

Buckle up!

Reactivity with Signals

By 2025, the frontend world has largely agreed on one thing: signals for reactivity. Almost every major framework has its own version of signals — like Vue’s ref() or Svelte’s $state rune.

If you’re new to signals, don’t worry. Just remember two key concepts:

  1. Signals: Reactive values that can be read and updated.
  2. Effects: Functions that depend on signals. When a signal changes, its dependent effects automatically re-run.

A Tiny Signals Implementation

Our compact signals implementation is inspired by Andrea Giammarchi’s excellent article on signals. If you’re curious about the nitty-gritty, I highly recommend giving it a read.

{

const effects = [Function.prototype];
const disposed = new WeakSet();

function signal(value) {
  const subs = new Set();
  return (newVal) => {
    if (newVal === undefined) {
      subs.add(effects.at(-1));
      return value;
    }
    if (newVal !== value) {
      value = newVal?.call ? newVal(value) : newVal;
      for (let eff of subs) disposed.has(eff) ? subs.delete(eff) : eff();
    }
  };
}

function effect(fn) {
  effects.push(fn);
  try {
    fn();
    return () => disposed.add(fn);
  } finally {
    effects.pop();
  }
}

}

function computed(fn) {
  const s = signal();
  s.dispose = effect(() => s(fn()));
  return s;
}

How It Works:

  • We use a block scope ({}) to keep our variables out of the global namespace. This is handy when modules aren’t an option.
  • The signal function creates a reactive value. It returns a function that acts as both a getter and setter:
    • If called without arguments, it returns the current value and subscribes the active effect to the signal.
    • If called with a new value, it updates the signal and triggers all subscribed effects (unless they’re disposed).
  • The effect function registers a callback that runs immediately and re-runs whenever any of its dependent signals change.
  • The computed function creates a derived signal — a reactive value that is recalculated every time it's dependencies change.

Example Usage:

const count = signal(0); // Create a signal with initial value 0

effect(() => {
  console.log(`Count is: ${count()}`); // Log the current value of the signal
});

count(1); // Update the signal, which triggers the effect and logs "Count is: 1"
count(2); // Update again, logs "Count is: 2"

Reactive HTML Templates

Now, let’s add some templating and rendering magic. We’ll create a tagged template function, html, that parses HTML strings and dynamically binds reactive values to the DOM.

{

function html(tpl, ...data) {
  const marker = "\ufeff";
  const t = document.createElement("template");
  t.innerHTML = tpl.join(marker);
  if (tpl.length > 1) {
    const iter = document.createNodeIterator(t.content, 1 | 4);
    let n,
      idx = 0;
    while ((n = iter.nextNode())) {
      if (n.attributes) {
        if (n.attributes.length)
          for (let attr of [...n.attributes])
            if (attr.value == marker) render(n, attr.name, data[idx++]);
      } else {
        if (n.nodeValue.includes(marker)) {
          let tmp = document.createElement("template");
          tmp.innerHTML = n.nodeValue.replaceAll(marker, "<!>");
          for (let child of tmp.content.childNodes)
            if (child.nodeType == 8) render(child, null, data[idx++]);
          n.replaceWith(tmp.content);
        }
      }
    }
  }
  return [...t.content.childNodes];
}

const render = (node, attr, value) => {
  const run = value?.call
    ? (fn) => {
        let dispose;
        dispose = effect(() =>
          dispose && !node.isConnected ? dispose() : fn(value())
        );
      }
    : (fn) => fn(value);
  if (attr) {
    node.removeAttribute(attr);
    if (attr.startsWith("on")) node[attr] = value;
    else
      run((val) => {
        if (attr == "value" || attr == "checked") node[attr] = val;
        else
          val === false
            ? node.removeAttribute(attr)
            : node.setAttribute(attr, val);
      });
  } else {
    const key = Symbol();
    run((val) => {
      const upd = Array.isArray(val)
        ? val.flat()
        : val !== undefined
        ? [document.createTextNode(val)]
        : [];
      for (let n of upd) n[key] = true;
      let a = node,
        b;
      while ((a = a.nextSibling) && a[key]) {
        b = upd.shift();
        if (a !== b) {
          if (b) a.replaceWith(b);
          else {
            b = a.previousSibling;
            a.remove();
          }
          a = b;
        }
      }
      if (upd.length) (b || node).after(...upd);
    });
  }
}

}

Key Features:

  • The html function returns an array of DOM nodes.
  • It supports dynamic attributes, text content, child nodes, and event listeners using the on* syntax.
  • If the provided value is a function (or a signal itself) it sets up an effect which is re-run to update the DOM.

Example Usage:


  // Reactive state
  const count = signal(0);

  // Render the app
  const app = html`<div>
      <h1>Counter: ${count}</h1>
      <button onclick=${() => count((val) => val + 1)}>Increment</button>
      <button onclick=${() => count((val) => val - 1)}>Decrement</button>
    </div>`;

  // Mount the app to the DOM
  document.body.append(...app);

A More Complex Example: A Todo App

Check out this interactive Todo app built with our tiny library. It’s a great example of what you can achieve with just a few lines of code.

What’s Next?

In the next installment, we’ll add efficient list re-rendering with just one function. Stay tuned! 🚀


This content originally appeared on DEV Community and was authored by Fedor


Print Share Comment Cite Upload Translate Updates
APA

Fedor | Sciencx (2025-02-08T23:19:29+00:00) 1KB Frontend Library. Retrieved from https://www.scien.cx/2025/02/08/1kb-frontend-library/

MLA
" » 1KB Frontend Library." Fedor | Sciencx - Saturday February 8, 2025, https://www.scien.cx/2025/02/08/1kb-frontend-library/
HARVARD
Fedor | Sciencx Saturday February 8, 2025 » 1KB Frontend Library., viewed ,<https://www.scien.cx/2025/02/08/1kb-frontend-library/>
VANCOUVER
Fedor | Sciencx - » 1KB Frontend Library. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/02/08/1kb-frontend-library/
CHICAGO
" » 1KB Frontend Library." Fedor | Sciencx - Accessed . https://www.scien.cx/2025/02/08/1kb-frontend-library/
IEEE
" » 1KB Frontend Library." Fedor | Sciencx [Online]. Available: https://www.scien.cx/2025/02/08/1kb-frontend-library/. [Accessed: ]
rf:citation
» 1KB Frontend Library | Fedor | Sciencx | https://www.scien.cx/2025/02/08/1kb-frontend-library/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.