DEV Community

Numbnut
Numbnut

Posted on

YoffeeJS: Yet another Javascript framework. But this one's good, I think

As I strive to refine my stack, one consideration becomes increasingly important - Simplicity.

I want my main tool - the UI library - to do just one thing: help me create reactive components. While doing so, it has to be unopinionated, as close to the standards as possible, without imposing anything except for the data reactivity bit.

I didn't find anything that felt good enough, so I made one. Meet YoffeeJS.

GitHub logo lefetmeofefet / yoffee

Minimal HTML one-way binding library

Yoffee doesn't invent a new syntax. It doesn't impose a cumbersome API. No build steps. No Virtual DOM to slowly depress your soul. All you need to know is HTML and Javascript, and you're set to go.

Hello World

<script type="module">
    import {html, createYoffeeElement} from "https://unpkg.com/yoffee@latest/dist/yoffee.min.js"
    createYoffeeElement("hello-world", () => html()`<div>Hello World!</div>`)
</script>
<hello-world></hello-world>
Enter fullscreen mode Exit fullscreen mode

Try it on JSFiddle

In this example we used the two API functions of Yoffee: createYoffeeElement and html.

The output is a legit web component - a brand new HTML tag.

What about data?

Yoffee's features one-way data binding, much like React. Consider the counter button example:

<script type="module">
    import {html, createYoffeeElement} from "https://unpkg.com/yoffee@latest/dist/yoffee.min.js"

    createYoffeeElement("counter-button", () => {
        const state = {
            clicks: 0
        }

        return html(state)`
            <button onclick=${() => state.clicks += 1}>
                I've been clicked ${() => state.clicks} times
            </button>
        `
    })
</script>
<counter-button></counter-button>
Enter fullscreen mode Exit fullscreen mode

Try it on JSFiddle

When state.clicks changes, Yoffee knows which expressions need to be rerun, and in turn which DOM Nodes need to be updated. No unnecessary expression evaluations, no DOM Diffing. You can read more about the mechanism and how it compares to other libraries in the official docs.

What about everything else?

Yoffee is just a thin wrapper for web components and reactive html.
Things that usually require learning in other frameworks are elegant and obvious in Yoffee, like CSS, shared state, listening to events, passing data from component to component, calling callbacks passed from parent element, reacting to property changes, and more.

I use it and so should everybody

Joking aside, some code style decisions were made, and I don't claim that they're objectively better. Some people may like the useState syntax better.
For me, this is the perfect framework. I use it in my projects.

My wildest hope is that other people will like it, and use it, and maybe even contribute to it.

In the future I plan to fill gaps that'll make it more accessible to everybody, like a components library and better documentation.

Feel free to contact me, or add issues on Github or magically dive into the code and open a PR.

I'd love to collaborate!

Discussion (12)

Collapse
artydev profile image
artydev • Edited on

Hy Numbut

What do you think of this, a not styled tiny counter :

import {button, idiv, div, unselectBase, selectBase} from "./dml"; 
function Counter () {
    let count = 0;
    let counter = selectBase(div(''))
    let value = idiv("0", {style:"padding-right: 10px"});
    let binc = button("inc");
    let bdec = button("dec");
    binc.onclick = () => value.innerText = ++count;
    bdec.onclick = () => value.innerText = --count;
    unselectBase()
    return counter 
}

/* This is displayed directly on the page */
Counter()

Enter fullscreen mode Exit fullscreen mode

You can test it here : DevToTinyCounter

Regards

Collapse
daweet profile image
Numbnut Author

Hi :)
I like the self-encapsulation of your components. In the second (styled) example, you could move the style inside the Counter function.
What I don't like is that in order to write html hierarchies, you have to declare selectBase and unselectBase each time you enter / exit a DOM node. Your code formatting will be ruined if you run a basic linter.
Instead, I would allow a third parameter to the function div (and any other HTML Element) that would receive it's children, and it would look like:

div("I am a div", {style: ...}, [
    span("I am a child"),
    div("I am another child")
])
Enter fullscreen mode Exit fullscreen mode

But that's just my opinion - you should do whatever you like.
Can you send me your github / npm so I can play with it? I couldn't find it.

Also, did you look at hyperapp? It looks close to DML in its concept.
Have a good day :)

Collapse
artydev profile image
artydev • Edited on

Hy Numbut,
Thank you your advises.
In fact, I am not the creator of DML
Here is the official link : DML
Github : DMLGit

Enjoy :-)

Regards

Thread Thread
daweet profile image
Numbnut Author

Thanks :)

Collapse
artydev profile image
artydev

Hyperapp is nice.
But DML has this very nice thing to refence directly the newly created element.
Read the official site, to see what does that permit.

Regards

Thread Thread
daweet profile image
Numbnut Author

Yep, that's actually a neat feature of the selectBase code style.

Collapse
dannyengelman profile image
Danny Engelman • Edited on

Why does everyone want to be framed?
And forget Vanilla JavaScript is the most powerful Framework.

<script>
  customElements.define(`counter-button`, class extends HTMLElement {
    connectedCallback() {
      this.clicks = this.getAttribute(`clicks`) || 0
      this.clicked = _ => `I've been clicked ${this.clicks++} times`
      this.innerHTML = `<button onclick=this.innerHTML=this.parentNode.clicked()>${this.clicked()}</button>`
    }
  });
</script>
<counter-button clicks=10></counter-button>
Enter fullscreen mode Exit fullscreen mode

jsfiddle.net/WebComponents/5q4hk3gL/

Collapse
daweet profile image
Numbnut Author

It's the most powerful in terms of being lower-level. I like your example, it's simple and it's elegant with vanillajs. But what about when you wanna update some other stuff that's not the button you just clicked? Do you really wanna write querySelectors all day long? And what about lists? Constructing and replacing the HTML as a string is extremely slow, so to match Yoffee's performance you will have to keep references to DOM Nodes that you'd like to update at some point. What about multiple UI elements that depend on a single property? What about a UI element that is dependent on multiple properties?
All is possible with vanilla JS, but your code would be much longer and much less self explanatory, I'm afraid.

Collapse
dannyengelman profile image
Danny Engelman

Use Github Copilot to write code for you

Collapse
adam_cyclones profile image
Adam Crockett

Reminds me a lot of lit-element (I like that framework)

Collapse
daweet profile image
Numbnut Author

Thanks :)
You're spot-on, lit was my inspiration to make yoffee, along with lighterhtml!

Collapse
adam_cyclones profile image
Adam Crockett

You have good taste 🙂