Introducing Gremlin.js: web components for websites, not apps.

Summary: How to use Gremlin.js with HTML Custom Elements and BEM CSS to build modular server-side driven websites, not web apps.

Fluffy thing in a wooden box

We build websites. The ones where you click a link and the page gets redirected. Where the bigger part of HTML comes up generated server-side, not client-side. While we like to build web apps, most of the time we have to deal with some CMS or shopping system that does not allow us to develop anything app-like in a reasonable amount of time.

We love our modern frontend though. ES6, CSS pre post processors, highly complex build systems with Gulp and whatnot. I can’t live without that anymore. And I don’t want to write my JavaScript like 5 years ago, when we assembled some single main.js full of jQuery plugin spaghetti code. I want to profit from all the recent additions to the browsers toolbelt!

Modularize your website!

Websites contain a lot of modules, aka components. Big ones like an image gallery or a slider, smaller pieces like a button. Components will be nested, of course, and they should not depend on anything but themselves.

If you build self-contained components, things will be isolated, they will work everywhere. You take and throw them into another (modularized) project and you’re done. Furthermore, the code will be much more manageable if you work in a team. Everybody knows where to look at, because the component tells you by its name. And if you use a package manager like bower or npm you may even want to put some of your components into separate packages. You can reuse them in other projects easily this way.

Having said this: everything gets better if you build a website with modular, reusable and small pieces.

But while the paradigm of modular code is quite obvious to follow when writing JavaScript, PHP, Ruby or stuff, it’s not that evident how to build modular HTML and CSS. With HTML and CSS there is no scope. Your HTML may collide with existing structure and your CSS may blow up the whole thing if you mess around with inheritance and specificity.

(Remember humans aren’t good in measuring inheritance and spotting conflicts. However, browsers are pretty tough. They know code.)

Well. How to write modular HTML and CSS?

Use Web Components.

I would love to, but sadly this is not an option yet. Web Components will be the way to go for me once they are supported by all the browsers I have to consider. But until then, we need to find a proper workaround.

Panda oh no

Source: giphy


Step 1: use BEM for your CSS

When you write CSS, there is always context and you’re about to touch it!
‘Why BEM?’ in a nutshell

As long as CSS does not provide scope, we think BEM is the best workaround we have. With BEM you avoid styling based on element type selectors (like p or li) or generic classes (like .title or .active). You apply styles by unique (as in unique by type) CSS classes instead and thereby archive some sort of pseudo scope. It’s no real scope at all, but you don’t interfere with other element styles and avoid inheritance issues — it feels like scope.

Short version: With BEM you write today’s most modular CSS.

Step 2: add your very own HTML tags

One of the things Web Components use in the background are Custom Elements. Custom Elements allow the developer to create their very own HTML elements, describing the elements functionality with JavaScript. The native browser support is pretty poor today, but there are multiple libraries providing polyfills for the old browsers we all love so much.

A Custom Element is created by calling document.registerElement():

var Gallery = document.registerElement('photo-gallery');

You then use it in your HTML:

<photo-gallery>
  <!-- photo gallery html !-->
</photo-gallery>

or create new elements with JavaScript:

document.body.appendChild(new Gallery());

(This post is not meant to be a tutorial on custom elements, though. Plenty of articles about custom elements can be found, for example at htm5rocks.)

Using the native API is a little bit cumbersome, of course. It’s one of the things I don’t want to deal with in my daily work.

That’s why I built Gremlin.js.


Introducing Gremlin.js

Fluffy thing in a wooden box

Gremlin.js is a library for

dead simple web components

My problem with existing libraries was (and still is) that they tend to be highly complex, have a huge footprint, and — most importantly — don’t like the server-side rendering approach too much.

I needed something that would allow me to build components with Custom Elements for normal websites and started to build the first version of Gremlin.js some years ago.

Basically I wanted it to:

  • observe the DOM, looking for new elements. No matter if it’s available on page load or added later dynamically (see watched.js).
  • instantiate a Gremlin class for every component once it’s found in the DOM.
  • make it easily extensible via plugins.
  • use server-side rendered HTML for components.

This worked pretty good and I used it in a lot of projects. Sadly, the markup wasn’t that great as I had to rely on extra attributes to find and create the components:

<div data-gremlin-name="HelloWorld"></div>

All this changed when I taught Gremlin.js to use Custom Elements.

Custom element gremlins!

You can use any HTML you like for new elements, as long as it fulfills the browser’s constraints for custom elements.

<hello-world>
  <button>tap me please!</button>
</hello-world>

You can render the components content on the server, or, if you like, use a JS template engine to do so when the component is instantiated.

To make the component alive, we create a new gremlin with gremlins.create. It uses the native document.registerElement in the background and registers the new element in the current document.

const gremlins = require('gremlins');

gremlins.create('hello-world', {
  initialize: function(){
    let button = this.el.querySelector('button');
    button.addEventListener('click', () => alert('hello world!'));
  }
});

Gremlin.js offers the most important parts to build a component:

Spec:

  • initialize
    constructor called for every instance of this element found in and added to the DOM
  • destroy
    destructor called if an element is removed from the DOM
  • attributeDidChange
    callback if an attribute of the DOM element changed

Component instance:

  • this.el
    a reference to the actual DOM element that can be used to add event listeners, find other elements inside or modify the element itself.

Use mixins to share behavior between components

Gremlin.js does not offer too much beside the very basics. That’s intentional, as it is small and lightweigt this way.

To extend and to share code across your components you can use mixins. Gremlin.js supports inheritance as well, but for me, mixins are the preferred way to go.

A (currently small) list of mixins available can be found here: http://grml.in/modules/. I do use them all the time, as they take care of those most important always repeating tasks you have to deal with when developing fancy JavaScript elements for a website.

The hello world example from a above gets even easier with the jQuery mixin:

const gremlins = require('gremlins');
const gremlinsJquery = require('gremlins-jquery');

gremlins.create('hello-world', {
  mixins: [gremlinsJquery],
  events: {
      'click button': 'onClick'
  },
  onClick(){
    alert('hello world!');
  }
});

There is also gremlins-data to read and parse (data) attributes, and gremlins-dispatcher for communication between components.


Remember: it’s not for web apps

Gremlin.js is not a web app framework. It does not give you fancy data binding, history pushstate routing, models and all the other shiny things you need for an app. There are a lot of options out there that do an awesome job at building apps (I prefer React).

Gremlin.js is build to help developers organize their jQuery JavaScript code on server-side driven websites. And it works. Write custom elements, add the corresponding CSS in BEM-style modules and you can reuse all your components with ease in other projects.

Dancing

Source: giphy