Why I coded a micro library for Web Components
Tags: webcomponents, javascript, typescript, webdev
Published: 2019-07-15 19:09:35

I know it seems like everyone is building micro this, micro that. 

Micro services, micro frontends and now micro libraries?! 

There are already excellent solutions out there for developing Web Components.

- [Stencil](https://stenciljs.com/docs/introduction)
- [lit-element](https://github.com/Polymer/lit-element)
- [Hybrids](https://hybrids.js.org)
- [slim.js](http://slimjs.com/#/getting-started)
- [SkateJS](http://skatejs.netlify.com)

Some of the major JavaScript frameworks like Svelte and Angular even compile down to Custom Elements. This can be a little overkill though considering the amount of tooling that goes into compiling a modern JavaScript framework down to Web Components.

So why did I code another library?

![](https://media.giphy.com/media/4oexCrqXVKAnu/giphy.gif)

# Challenge myself

to build a framework that is modern, but has zero dependencies. I wanted a solution that uses only API found in the browser. This means some features require a polyfill, but that's OK. It turns out several APIs exist in the browser that allow you to build a micro library for UI that enables data binding, advanced event handling, animations and more!

- customElements
- createTreeWalker
- Proxy
- CustomEvent
- BroadcastChannel
- Web Animations

# Taking the pain away

from developing Web Components is another goal of the project. There is a lot of boilerplate involved with coding custom elements that can be reduced. It can be difficult to switch between custom elements that allow `ShadowDOM` and others that don't. Autonomous custom elements are treated differently than customized built-in elements. Event handling is only as good as typical DOM, requiring calls to `addEventListener` and `dispatchEvent` and even then you're stuck with how events typically bubble up. There's also the problem of updating a custom element's template, requiring selecting DOM and updating attributes and inner content. This opens up the opportunity for engineers to make not so performant choices. What if a library could just handle all of this?

# Full control

is what I was after. If I want to change the way the library behaves, I can. Readymade can build it out to support SVG out of the box (it does), but it could also render GL objects if I wanted to support that. All that would need to happen is to swap out the state engine and boom, WebGL support. I experiment all the time with different UI and need something malleable.

# Distribution 

is a key aspect of another project I've been working on for quite some time. I wanted a way to distribute a library of UI components without any framework dependencies. The goal of that project is to provide a UI library < 20Kb. Readymade itself is ~3Kb with all the bells and whistles imported. Components built with Readymade can be used like any other DOM element in a project built with any JavaScript framework, provided the framework supports custom elements.


# Decorators

are something I take for granted in Angular and I wanted to learn how these high order functions work. The micro library I built is highly dependent on this future spec, but that's OK too. Building the library from scratch with TypeScript also provides the additional benefits of type checking, IntelliSense, and gives me access to the excellent TypeScript compiler.

![](https://thepracticaldev.s3.amazonaws.com/i/yyavfza6i1tb8dxc4e46.png)

# Enter Readymade

Readymade is a micro library for handling common tasks for developing Web Components. The API resembles Angular or Stencil, but the internals are different. Readymade uses the browser APIs listed above to give you a rich developer experience. 

- 🎰 Declare metadata for CSS and HTML ShadowDOM template
- ☕️ Single interface for 'autonomous custom' and 'customized built-in' elements
- 🏋️‍ Weighing in ~1Kb for 'Hello World' (gzipped)
- 1️⃣ One-way data binding
- 🎤 Event Emitter pattern
- 🌲 Treeshakable

# An example

The below example of a button demonstrates some of the strengths of Readymade.

```typescript

import { ButtonComponent, Component, Emitter, Listen } from '@readymade/core';

@Component({
    template:`
    <span>{{buttonCopy}}</span>
    `,
    style:`
        :host {
            background: rgba(24, 24, 24, 1);
            cursor: pointer;
            color: white;
            font-weight: 400;
        }
    `,
})
class MyButtonComponent extends ButtonComponent {
    constructor() {
        super();
    }
    @State() 
    getState() {
      return {
        buttonCopy: 'Click'
      }
    } 
    @Emitter('bang')
    @Listen('click')
    public onClick(event) {
        this.emitter.broadcast('bang');
    }
    @Listen('keyup')
    public onKeyUp(event) {
        if (event.key === 'Enter') {
            this.emitter.broadcast('bang');
        }
    }
}

customElements.define('my-button', MyButtonComponent, { extends: 'button'});

```

- `ButtonComponent` is a predefined ES2015 class that extends `HTMLButtonElement` and links up some functions needed to support the `template` and `style` defined in the `Component` decorator and calls any methods added to the prototype of this class by other decorators. The interesting part here is `ButtonComponent` is composable. Below is a the definition.

```typescript
export class ButtonComponent extends HTMLButtonElement {
  public emitter: EventDispatcher;
  public elementMeta: ElementMeta;
  constructor() {
    super();
    attachDOM(this);
    attachStyle(this);
    if (this.bindEmitters) { this.bindEmitters(); }
    if (this.bindListeners) { this.bindListeners(); }
    if (this.onInit) { this.onInit(); }
  }
  public onInit?(): void;
  public bindEmitters?(): void;
  public bindListeners?(): void; public bindState?(): void;
  public setState?(property: string, model: any): void;
  public onDestroy?(): void;
}
```

- `State` allows you to define local state for an instance of your button and any properties defined in state can be bound to a template. Under the hood Readymade uses `document.createTreeWalker` and `Proxy` to watch for changes and update `attributes` and `textContent` discretely.

- `Emitter` defines an EventEmitter pattern that can use `BroadcastChannel API` so events are no longer relegated to just bubbling up, they can even be emitted across browser contexts.

- `Listen` is a decorator that wires up `addEventListener` for you, because who wants to type that all the time?

# Readymade is now v1

so go and check it out on [GitHub](https://github.com/readymade-ui/readymade). The documentation portal is built with Readymade and available on [Github Pages](https://readymade-ui.github.io/readymade/).


![](https://media.giphy.com/media/BaSHs78BU2ZYQ/giphy.gif)