Where to begin building Web Components? - Class-based Libraries

alangdm

Alan Dávalos

Posted on June 29, 2020

Where to begin building Web Components? - Class-based Libraries

Introduction

Welcome back to this series where I try to help you find which way of building Web Components best suits you!

In the previous post, we covered the Web Components Standards and why you probably will want to use a library to help you build your Web Components at this point of time.

In this article we'll be covering the first pattern commonly used by Web Components libraries, the class-based pattern.

Just before we begin, I have one quick note regarding the "bundle" size data.

I'll be using the fantastic BundlePhobia as the source for the minified + gzip bundle size data of each library.

However, depending on the approach each library follows, how much the library's bundle size affects your application's bundle when using multiple components based on that library may vary greatly.

The folks at WebComponents.dev did an amazing breakdown on that kind of data so if you're interested in that kind of data go check them out. (You can also test out all the libraries covered in this article in their Web IDE.)

Now without further ado.

What's the Class-based Pattern About?

In the first article of this series, we mentioned that in order to create a Web Component you need to create a class that extends HTMLElement and then register that class in the CustomElementRegistry.

And of course, extending a class that extends HTMLElement also counts.

So what this type of libraries do is exactly that, they create a generic class that extends HTMLElement and add a bunch of utility code that helps out make creating components easier.

For example, the SuperAwesomeElement class defined below could help making updating an element after one of it's attributes changed a lot easier than when manually extending HTMLElement.

You can see the code in action here

export class SuperAwesomeElement extends HTMLElement {
  constructor() {
    super();
    this.state = {};
  }

  static get attributes() {
    return {};
  }

  static get observedAttributes() {
    return Object.keys(this.attributes);
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) {
      return;
    }
    // it basically will just parse the attribute depending on the
    // type you define and set it to the components state property
    const type = this.attributes[name].type;
    if (/array|object/i.test(type)) {
      this.state[name] = JSON.parse(newValue);
    } else if (/number/i.test(type)) {
      this.state[name] = parseFloat(newValue);
    } else {
      this.state[name] = newValue;
    }
    this.update();
  }
}
Enter fullscreen mode Exit fullscreen mode

And creating an actual component based on it would look like this:

import { SuperAwesomeElement } from "super-awesome-element";

const template = document.createElement("template");
template.innerHTML = `
  <p>Text: <span class="text"></span></p>
  <p>Number: <span class="int"></span></p>
  <p>Object: <span class="obj"></span></p>
  <p>Array: <span class="arr"></span></p>
`;

export class MyComponent extends SuperAwesomeElement {
  constructor() {
    super();
    this.state = { text: "", int: 0, obj: {}, arr: [] };

    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    this._textNode = this.shadowRoot.querySelector(".text");
    this._intNode = this.shadowRoot.querySelector(".int");
    this._objNode = this.shadowRoot.querySelector(".obj");
    this._arrNode = this.shadowRoot.querySelector(".arr");
  }

  static get attributes() {
    return {
      text: { type: "string" },
      int: { type: "number" },
      obj: { type: "object" },
      arr: { type: "array" },
    };
  }

  update() {
    this._textNode.textContent = this.state.text;
    this._intNode.textContent = this.state.int;
    this._objNode.textContent = JSON.stringify(this.state.obj);
    this._arrNode.textContent = JSON.stringify(this.state.arr);
  }
}

customElements.define("my-component", MyComponent);
Enter fullscreen mode Exit fullscreen mode

Naturally, this is just a super simple example that's nowhere near production ready and actual class-based libraries do a lot more things for you.

But even an example as simple as this reduces the amount of code you need to create a component quite a bit.

Now imagine what a full-featured library can do for you. 💪

Pros and Cons

Components written using this kind of libraries are by definition a lot closer to the standard, which in itself has some pros and cons:

Pros

  • Easy migration: if you ever need to migrate your components to vanilla or another class-based library the migration will be smoother than if you were using one of the other patterns.
  • Extensibility: If you need extra common features for your components you can use mixins to add them to your components, and these mixins might actually work no matter which class-based library you end up using as they all extend HTMLElement.
  • You learn to use the standards: learning how to use one of this libraries will help you understand the standards better.

Cons

  • More boilerplate code: while extending a class-based library does drastically reduce the amount of code you need to write, classes in JS just generally require you to write slightly more boilerplate code than other approaches.
    • This is mostly evident while doing things like having side effects on property changes.
    • Note that this doesn't mean the build size will be bigger, it's just about the code you actually write.

Libraries that Follow this Pattern

Here's a list of some of the libraries that follow this pattern in alphabetical order:

Stars/Version/Size data was updated at the time of publishing.

CanJS

Website | Github

Stars License Latest Version TS Support Bundle Size Templating
1.8k+ MIT 1.1.2 (June 2020) Couldn't find 66kB can-stache (mustache-like syntax)

Fun Facts

CanJS's size is actually quite big compared to pretty much every other library introduced throughout this series.

But that's mostly because unlike the other libraries CanJS is more of a framework based on Web Components than a library to create Web Components.

So if you build your whole app just around it, it might be worth it for you, but if you're just building a reusable components you're probably better off using other libraries.

HyperHTML Element

Github

Stars License Latest Version TS Support Bundle Size Templating
0.1k+ ISC 3.12.3 (March 2020) Yes 8.7kB hyperHTML (JS Tagged Template Literals)

Fun Facts

This library is mostly meant to be a helper to create Web Components rendered with hyperHTML.

As a side node, hyperHTML might be one of the best rendering libraries in terms of performance. ⚡️

LitElement

Website | Github

Stars License Latest Version TS Support Bundle Size Templating
3.5k+ BSD 3-Clause 2.3.1 (March 2020) Yes, includes decorators 7.1kB lit-html (JS Tagged Template Literals)

Fun Facts

LitElement being made by the Polymer Project team confuses many people since Polymer v3 still "exists".

To put it simple LitElement is Polymer v4, except that since the approach to creating the components changed quite drastically (and got improved drastically too) they changed the name.

So if you want to use a "Polymer" library just use LitElement. 😉

LitElement's first production-ready release was actually v2.0.0 because the lit-element package was previously owned by other people and already had a v1.0.0 release.

It's sister library, lit-html, has a lot of parallels with a library that was mentioned before, hyperHTML, including the part about being one of the best performing rendering libraries. ⚡️

Omi

Website | Github

Stars License Latest Version TS Support Bundle Size Templating
11.1k+ MIT 6.19.3 (May 2020) Yes, includes decorators 8.3kB JSX (Preact)

Fun Facts

Omi is probably the only class-based library whose docs are on multiple languages by default.

They seem to all have versions in English and Chinese and some even in Korean. 🇬🇧🇨🇳🇰🇷

SkateJS

Website | Github

Stars License Latest Version TS Support Bundle Size Templating
3.1k+ MIT 0.0.1 (December 2018) Couldn't find 1.8kB + render library hyperHTML/lit-html (JS Tagged Template Literals), JSX

Fun Facts

SkateJS is actually a pretty unique library as it doesn't provide an "official" way of writing templates.

Instead, it's designed to be used together with either a tagged template literal-based engine like hyperHTML or lit-html or a JSX engine like Preact or React.

The only bad thing is that it seems like the SkateJS team is mostly focused on improving their SSR functionality right now so there haven't been any updates on the Web Component library itself for a while.

SlimJS

Website | Github

Stars License Latest Version TS Support Bundle Size Templating
0.7k+ MIT 4.0.7 (April 2019) Yes, includes decorators 3.3kB own library (mustache-like)

Fun Facts

As the name suggests, SlimJS is super slim, it's the smallest library on this article's list and it's one of the smallest I will cover throughout the series in general.

Just one thing you might want to consider is that the project doesn't seem to have any updates for the past year or so. ☹️

What's Next?

Now we have covered the class-based approach, how it works, why it can be good for you, and some of the libraries that use this approach.

And maybe you already liked something here so much that you want to test it out ASAP, if you did, that's amazing! 🎉

But don't worry if you haven't, we still have other patterns to cover with many more libraries representing them so stay tuned for the next entry on this series.

Feel free to leave a comment with any corrections, questions, or suggestions you have for the rest of the series. Especially regarding the libraries and the data I showed on them as I'm not an expert on every single one of them so some of it might be slightly off.

💖 💪 🙅 🚩
alangdm
Alan Dávalos

Posted on June 29, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related