DEV Community

Cover image for Create tidy front-end components for server-side rendered markup - introducing Ovee.js framework.
Jakub Przyborowski for Owls Department

Posted on • Updated on • Originally published at owlsdepartment.com

Create tidy front-end components for server-side rendered markup - introducing Ovee.js framework.

I remember, when I first discovered jQuery. It was around 2009 and I was a junior back-end developer, who loved also CSS but hated didn’t understand JS. I was amazed, that suddenly I had a tool, that let me create front-end interactions without much struggle. For the next few years, I became a full-stack dev and loved to have jQuery at my side. Yet, there was one thing I really missed from my back-end garden: the structure that MVC frameworks gave me. You know, out of larger organizations, there was no front-end architecture in most projects that were made in the late 2000s and early 2010s. Just a bunch of random event handlers put into a single functions.js file. Maybe, but just maybe, some prototype-based classes, if you were fancy.

To be honest, this style of doing JS still resonates in the works of many devs even today, if they are working on a simple website, not an enterprise-level app. I don’t wanna hate, I wanna help - but let’s continue with the story for now.

In search of a good front-end architecture for non-application projects

In search for better front-end architecture, around 2011-2013 I got fascinated by Backbone, then Angular.js. These frameworks were cool for building SPAs, but no one but some hipsters used them to build websites, as achieving SSR was a huge struggle. And you still wanted your site to be indexable by search engines.

In the meantime, when building websites, I’ve started to structure my JS into a pile of objects with simple auto-initialization magic. Still jQuery, still no build tools (other than some minification maybe). Sucked a little less, but still meh.

There’s a life beyond everything-in-js

With the rise of React and Vue, things got fancy. We now have static generators like Gatsby, Next.js, Nuxt.js, Gridsome, and dozens of others. And with them, the problem with SSR disappeared. But, have you tried to build a marketing-focused website on top of any of these? Yes, these are great tools and have many advantages, but the development cost can be 2-3 times higher, and you lose the simplicity of the “classic” sites built as templates for one of popular CMS systems.

With my team at Owls Department, we try to value picking the right tools for the job over following the lead of hype and fanciness. I really, really love Vue and evangelize it to my clients, when we pitch for application projects. Yet, when it comes to most website builds, we go “classic”. I believe there’s a place for using different approaches when seeking a successful front-end product - take a look at Signal’s Basecamp or GitLab - these are both mostly server-side rendered products and using them feels nice and smooth to me.

Look mum, I’ve built another JS framework

Over the years, I’ve searched for a good solution to keep the JS that we build for server-side rendered markup up to the same coding and architectural standards that we use when building SPAs with Vue. And didn’t find a good one, so started to DIY something for my team. The first version of our internal framework was built around the idea of a component, that hooks to matching structure in html (selected by data- parameter - inspired by good ol’ Angular v1) and encapsulates the JS magic. It still used jQuery here and there. But it freakin’ worked. We were able to build quite complex sites, while keeping the code maintainable. We were able to reuse components, so the work was done faster.

In late 2019 I had a chat with some team members, that it would be good to finally ditch the jQuery and also switch from our proprietary pjax clone to Barba for page transitions. When doing my research, I found Basecamp’s Stimulus (https://stimulus.hotwired.dev/) - now part of Hotwire suite. I love the work of these guys, but I don’t like how much JS-related stuff (e.g. binding events) is done in the server-side rendered markup. There’s also Strudel.js (https://strudel.js.org/), which comes from a similar background to ours. When I started to modernize our framework, I’ve found a lot of inspiration in Strudel’s design and API (kudos to the team behind this pastry-flavored framework).

By the mid of 2020, we had the new framework ready to use internally. We’ve decided to publish it as open-source under MIT license, and named it Ovee.js. It’s fully written in TypeScript (huge contribution from @F0rsaken), has good unit test coverage, and is here to help teams and individuals, who struggle with problems similar to ours. Now it’s ready for you to discover it!

Show me the code

Let’s take a quick journey, so you could feel how the framework tastes.

Installation is nothing special:

yarn add ovee.js
Enter fullscreen mode Exit fullscreen mode

A component is a building block of your website or an application. In Ovee.js, it is represented by a class and corresponding markup. The framework detects html tag matching the component by either tag name or a data parameter. Each instance of matched tag gets its own instance of the component class.

Let's take a look at an example:

<incremental-counter class="incremental-counter">
    <p class="incremental-counter__value"></p>
    <button class="incremental-counter__button">increment!</button>
</incremental-counter>
Enter fullscreen mode Exit fullscreen mode
import {
    Component,
    bind,
    el,
    reactive,
    register,
    watch
} from 'ovee.js';

@register('incremental-counter')
export default class extends Component {
    @reactive()
    counter = 0;

    @el('.incremental-counter__value')
    valueElement;

    @bind('click', '.incremental-counter__button')
    increment() {
        this.counter++;
    }

    @watch('counter', { immediate: true })
    update() {
        if (this.valueElement) {
            this.valueElement.innerHTML = `Current value: ${this.counter}`;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As we can see, within the component class we can reference children elements that are contained within its corresponding DOM node. The framework gives us convenient mechanisms to bind events, DOM elements and react to data changes.

The framework is reactive if you want it to be reactive. It uses the power of MutationObserver, so you don’t need to manually initialize or destroy components when you modify the DOM (e.g. by changing views using Barba).

The initialization is pretty straightforward, and if you ever used any modern framework, you’ll see the similarities.

import { App } from 'ovee';

import OveeBarba from '@ovee.js/barba';

import IncrementalCounter from './components/IncrementalCounter';

const root = document.getElementById('app');

const app = new App({
    components: [
        IncrementalCounter
    ],
    modules: [
        OveeBarba
    ]
});

app.run(root);
Enter fullscreen mode Exit fullscreen mode

Oh, and you remember when I told you that it’s meant to work only with server-side generated markup? Oh, I kinda lied. You see, that’s the core use case. But sometimes a project that in 90% fits the use case for rendering markup on the back-end, this one pretty dynamic part. And when you think about how to approach it, this part shouts “duude, React or Vue would serve me well”. For such scenarios, we’ve extended the default Component’s design with the power of Polymer’s lit-html. So, some of your components can be client-side rendered, if you want.

import {
    TemplateComponent,
    bind,
    reactive,
    register
} from 'ovee.js';

@register('incremental-counter')
export default class extends TemplateComponent {
    @reactive()
    counter = 0;

    @bind('click', '.incremental-counter__button')
    increment() {
        this.counter++;
    }

    template() {
        return this.html`
            <p class="incremental-counter__value">Current value: ${this.counter}</p>
            <button class="incremental-counter__button">increment!</button>
        `
    }
}
Enter fullscreen mode Exit fullscreen mode

Neat, right? This way it’s your decision, how you build your stuff. Not the framework’s.

What’s next

Our team at Owls Department uses the thing daily. We collect the team’s feature requests and have plans for the future development of the framework. The biggest change we have in mind is adapting Vue 3’s reactivity in place of the solution we have in place. With this change, we are looking forward to performance gains, especially when it comes to TemplateComponent. If you have any ideas or want to contribute, give us a heads up!

Further reads

I hope you’ll find the project interesting and I’ve convinced you to try Ovee.js.

In the future, I’ll cover Ovee’s features in more in-depth articles. Please, follow us on Twitter (@owlsdepartment), Dev.to (@owlsdepartment) and Instagram (@owlsdepartment), so you don’t miss any future publications.

The full documentation can be found here: https://owlsdepartment.github.io/ovee/

As the library is still fresh, the community is still to come. But, what is important - our team is using Ovee.js on daily basis, so we are commited to maintain and improve it in future. If you have any questions or ideas, don’t hesitate to catch us via Twitter (@owlsdepartment) or through GitHub Issues.

Cheers!

Discussion (4)

Collapse
arsprogramma profile image
ArsProgramma

I'm absolutly unsure whether i love the idea or i hate it by heart.
Guess i have to get my hands dirty to decide :D

Collapse
przyb profile image
Jakub Przyborowski Author

By "the idea" you mean the idea of a framework for server-side rendered markup? Or the design of its API? I'm really curious to hear your feedback :)

Collapse
arsprogramma profile image
ArsProgramma

I like as well the idea as the annotation-style-API.

However I feel a little uncomfortable in adopting that;
why: I cannot really point my finger on it, might be the stringified-coupling by attributes or some worries about how good this will scale on a complex application...
but basically it's just a feeling and i guess i'll fiddle around with it for a while to get an actual opinion out of it for i like the basic premise of the framework.

Thread Thread
przyb profile image
Jakub Przyborowski Author • Edited on

Thanks for clarifying. Your concerns sound reasonable. However, using annotations is (almost) optional. Some of devs on my team prefer to bind events with this.$on() notation instead, and work with vanilla querySelectors rather than binding elements to the instance with @el. We could probably introduce a more flexible notation in form of something simmilar to Vue's composition API. However, the library was originally meant to be used by less experienced devs (or less JS-focused creative devs). So I wanted the API to enforce keeping the component's code tidy. By being a little more verbose, also motivates devs to better understand how the whole thing works (wants something to be reactive? declare it!).

When it comes to scaling up on a larger application - it probably won't. And it was never meant to. When my team builds complex apps, we use Vue (and love it very much). We built Ovee to help us write tidy and modern JS when working on designer websites, not large scale apps (like the typical stuff you find on Awwwards). In such scenario, your codebase is much more heavy with CSS, GSAP and WebGL animation code than with business logic. So you manage probably around 20-40 components, and for this scale it shines.