DEV Community

Cover image for My learning project became a JavaScript framework: Brio.js v5
Jhonathan Reis for Brio.js

Posted on

My learning project became a JavaScript framework: Brio.js v5

On February 12, 2024, I committed a file with the message:

A not so good scope architecture

Five days later, the next commit said:

New scoping system

That's the most honest summary of how Brio.js was built. I wrote something, I learned why it was wrong, and I wrote it again. I did that a lot, for fun and to learn how the frameworks I used every day actually work.

At some point the "learning project" quietly started running in one small production project of mine. Then it got tests. Then docs. Then a playground, a starter CLI, and a website with a small creature on it (we'll get to the creature).

This week I released Brio.js v5 and made the whole thing public. This post is the "hello, here's what I've been doing in my free time" post.

What Brio is

Brio adds reactivity to HTML with b-* attributes. It works on the real DOM, in place. No virtual DOM, no JSX, no required compiler, and no runtime dependencies.

Here's the smallest useful Brio app. You can save it as a file and open it from any static server:

<div id="counter" b-cloak>
  <button b-on-click="$data.count++">
    Count: <span b-text="$data.count"></span>
  </button>
</div>

<script src="https://cdn.jsdelivr.net/npm/@jnreis/briojs@5.1.1/dist/brio.min.js"></script>
<script>
  brio.createApp({ state: () => ({ count: 0 }) }).mount('#counter');
</script>
Enter fullscreen mode Exit fullscreen mode

(Add <style>[b-cloak] { display: none; }</style> so the button doesn't flash before Brio starts.)

That's it: one script tag, one mount, and a button that counts. If you want a build setup instead, npm create brio@latest gives you a starter.

Meet the creature

Every framework website needs a demo. Most use a todo list. I made a small creature with feelings.

A creature changes from happy blue Blip to sleepy purple Bean to mischievous pink Mochi as its controls change.

You pick a mood, a color, a size, and a name, and the creature updates as you go. There's also a 🎲 Surprise me button, because it's fun.

Here's a trimmed version of the real markup from the homepage. The horns only exist while it's being mischievous:

<div role="img" b-bind-aria-label="$data.greeting"
  b-bind-class="['creature', 'is-' + $data.mood]"
  b-bind-style="{ '--body': $data.color, '--size': $data.size / 100 }">
  <span class="creature-zzz" b-if="$data.mood === 'sleepy'">z z z</span>
  <span class="creature-horns" b-if="$data.mood === 'mischievous'"></span>
  <span class="creature-eyes"><i></i><i></i></span>
</div>

<label><input type="radio" name="mood" value="sleepy" b-model="$data.mood"> 😴 Sleepy</label>
<input type="range" min="70" max="130" b-model.number="$data.size">
Enter fullscreen mode Exit fullscreen mode

And the greeting is just a getter on the state:

get greeting() {
  const name = this.name.trim() || 'This creature';
  if (this.mood === 'sleepy') return `${name} needs a nap.`;
  if (this.mood === 'mischievous') return `${name} is up to something.`;
  return `${name} is having a great day.`;
},
Enter fullscreen mode Exit fullscreen mode

The best part: the playground opens with this exact creature. You can edit its HTML, CSS, and JavaScript and see the changes right away. Give it a fourth mood. Give it a hat. I won't stop you.

Three things I'm excited about

1. Every value says where it came from

In Brio templates you write $data.count, $props.label, or $for.item. Not count, label, or item.

Yes, it's a little more typing. I went back and forth on this for a long time. It became a deliberate breaking change in 4.0, because once lists and components were nested inside each other, "where does item come from?" stopped being a fun puzzle. Now you can read any expression and know which drawer to open. (There's a whole post coming on this decision.)

2. It only owns what you mount

You can mount Brio around one form in a Rails view, one menu in a WordPress theme, or one calculator in an old PHP page. It doesn't care what's around it, and it won't touch anything outside the element you gave it. When the widget is removed, it cleans up after itself.

That's the use case Brio was born from, and the one I care about most: adding interactivity to pages that already exist, without asking the rest of the page to change.

3. It grows with you

It starts with a single counter, but it doesn't stop there:

  • Five starters from npm create brio: widget, multi-page, SPA, Astro islands, and plugin.
  • Optional add-ons for routing, HTTP, persistence, extra inputs, and observers. You load only what you use.
  • A public plugin API for your own directives and helpers. I moved some of Brio's own features out of the core into add-ons built on that same API, partly to prove it works.
  • Parts: copy-paste dialog, disclosure, menu, and tabs components you own and can change.
  • Ten example apps, from a counter to a TodoMVC, a 1,000-row table, a Kanban board, and a collaborative editor.
  • Integration guides for Rails, Laravel, Django, WordPress, and plain HTML with Express.

There are 36 directives in total, and 1,258 tests that are much stricter with me than I am with myself.

What it's not good at (yet)

I'd rather you hear this from me than find out an hour into trying it:

  • Strict CSP needs a separate build. The standard build uses new Function, which a policy that forbids unsafe-eval blocks. Since 5.1 there's a CSP build (@jnreis/briojs/csp) that runs without eval. It hydrates only components defined in JavaScript, so plain page markup stays inert.
  • Server-side rendering. Brio enhances HTML your server (or Astro) already rendered. It doesn't render on the server itself.
  • Old browsers. It targets modern evergreen browsers.
  • It's young, and it's one person. I'm the only maintainer. The ecosystem is the docs, the examples, and whatever you and I build next. If you need a big community and long-term guarantees today, that's a fair reason to wait.

Why share it now?

Because I've reached the point where I can't see its flaws anymore. I know where everything is, so everything feels obvious. That's exactly when you need people who don't know where everything is.

So here's my ask, and it's a small one:

  1. Open the playground, break the creature, and tell me what confused you.
  2. Answer this in the comments: if you had to add interactivity to one part of a page you already have, what would it be? A filter? A form that shows and hides fields? Something weirder?

Every answer helps me decide what to write about and build next.

Links:

Thanks for reading. Bean says hi. Bean is also asleep.

Top comments (0)