Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 5 min read

Wrium: A Tiny Reactive JavaScript Library With No Build Step

Have you ever wanted a button that updates some text on a page, and ended up setting up a bundler, a compiler, and a folder full of config files? I felt that way too. So I built Wrium, a small JavaScript library for mak

Have you ever wanted a button that updates some text on a page, and ended up setting up a bundler, a compiler, and a folder full of config files?

I felt that way too. So I built Wrium, a small JavaScript library for making reactive user interfaces. You write normal HTML, add a few special attributes, and the page updates itself when your data changes.

What is "reactive" anyway?

"Reactive" means one simple thing: when your data changes, the screen updates automatically.

Without reactivity, you write code like this:

count++;
document.querySelector('#count').textContent = count;

You have to change the data and update the page by hand. With a reactive library, you only change the data. The library updates the page for you.

Meet Wrium

Wrium is inspired by Vue. If you know Vue's ref, computed, v-if, or v-for, you will feel at home. But Wrium is much smaller and simpler:

  • Zero dependencies
  • No build step. Add a script tag and go.
  • About 10.9 KB for the whole core (minified)
  • TypeScript declarations included
  • MIT license

It is not trying to replace big frameworks. It is for the times when a big framework is too much.

Your first app in 20 lines

Here is a working counter. Save it as an HTML file and open it in a browser (use a local server, because ES modules need one).

<div id="app">
  <h1>{{ title }}</h1>
  <p>Current count: {{ count }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</div>

<script type="module">
  import { createApp, ref } from '@wrium/wrium';

  createApp(() => {
    const title = ref('Counter App');
    const count = ref(0);

    const increment = () => count.value++;
    const decrement = () => count.value--;

    return { title, count, increment, decrement };
  }).mount('#app');
</script>

Here is what happens:

  1. ref(0) creates a reactive box that holds the number 0.
  2. {{ count }} in the HTML shows the value of that box.
  3. When you click a button, count.value changes and the text on the page updates by itself.

There was no compiler and no bundler. It is just HTML and JavaScript.

The four building blocks

Wrium's reactivity is built on four functions.

ref holds a single value, such as a number, a string, or even an object.

const name = ref('John');
name.value = 'Jane'; // the page updates

reactive makes a whole object or array reactive, including nested properties.

const state = reactive({ user: { name: 'John' }, todos: [] });
state.user.name = 'Jane';   // reactive
state.todos.push('Learn');  // also reactive

computed creates a value that is calculated from other values. It only recalculates when something it depends on changes.

const first = ref('John');
const last = ref('Doe');
const fullName = computed(() => `${first.value} ${last.value}`);

watchEffect runs a function right away, then runs it again whenever the reactive values it uses change. You never have to list dependencies by hand.

watchEffect(() => {
  console.log('Count is:', count.value);
});

Templates: HTML that reacts

Wrium reads your HTML directly and understands the directives you already know from Vue:

<!-- Show or hide -->
<p v-if="score >= 90">Excellent!</p>
<p v-else>Keep trying!</p>

<!-- Repeat -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

<!-- Two-way binding for inputs -->
<input v-model="username" />

<!-- Events, with modifiers -->
<form @submit.prevent="save">...</form>
<input @keyup.enter="submit" />

<!-- Bind attributes and classes -->
<button :disabled="isLoading" :class="{ active: isActive }">Save</button>

Supported directives include v-if, v-else-if, v-else, v-for, v-model, v-show, v-text, v-bind (:), and v-on (@).

A real example: a Todo list

Every framework needs a Todo list example, so here is Wrium's:

<div id="app">
  <input v-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo" />
  <button @click="addTodo">Add</button>

  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo.id)">Γ—</button>
    </li>
  </ul>

  <p>{{ remaining }} of {{ todos.length }} remaining</p>
</div>

<script type="module">
  import { createApp, ref, reactive, computed } from '@wrium/wrium';

  createApp(() => {
    const newTodo = ref('');
    const todos = reactive([]);
    const remaining = computed(() => todos.filter(t => !t.done).length);

    function addTodo() {
      if (!newTodo.value.trim()) return;
      todos.push({ id: Date.now(), text: newTodo.value, done: false });
      newTodo.value = '';
    }

    function removeTodo(id) {
      const i = todos.findIndex(t => t.id === id);
      if (i > -1) todos.splice(i, 1);
    }

    return { newTodo, todos, remaining, addTodo, removeTodo };
  }).mount('#app');
</script>

State, list rendering, a computed counter, and event handling, all in one small file.

Components

You can reuse markup with app.component():

app.component('todo-item', {
  template: '<li :class="{ done }">{{ text }}</li>'
});
<todo-item v-for="t in todos" :key="t.id" :text="t.text" :done="t.done"></todo-item>

Every attribute on the tag becomes a prop, and props stay in sync with the parent when you bind them with :.

Plugins: add your own directives

Wrium's built-in directives are registered the same way plugins register theirs. Built-ins get no special treatment. Here is a tiny plugin that adds a v-focus directive:

const FocusPlugin = {
  install(api) {
    api.directive('focus', (el) => {
      setTimeout(() => el.focus(), 0);
    });
  }
};

createApp(() => ({})).use(FocusPlugin).mount('#app');

Some features ship as optional plugins instead of core. For example, v-html is opt-in because inserting raw HTML can cause XSS problems if you use untrusted content. Wrium makes you choose it on purpose. There are also plugins for password strength checking and for draggable elements.

Honest limitations

Wrium is young, and I would rather tell you now than surprise you later:

  • Directives, components, and hooks are global, not scoped per app instance.
  • There are no slots and no dedicated emit. A function passed as a prop does that job.
  • reactive() does not handle Map and Set specially yet.
  • It needs Proxy support, so there is no Internet Explorer.

When should you use Wrium?

A good fit:

  • Small interactive widgets on an existing page
  • Prototypes and demos
  • Learning how reactivity works, since the library is small enough to read
  • Projects where you really do not want a build step

Probably not the best fit:

  • Large apps that need routing, state management, SSR, and a big ecosystem. Use Vue, React, or Svelte there.

Try it

npm install @wrium/wrium

Or read the docs at wrium.dev.

Wrium started as an experiment called Zog.js, and it just reached v1.0.0. If you try it, I would love to hear your feedback. Open an issue, send a PR, or drop a star on GitHub. And tell me in the comments: what is the smallest thing you would build with it?

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.