On Using Web Component Libraries
Tags: webcomponents, html, javascript
Published: 2021-09-20 19:13:00

We tend to think of components as things that belong to a framework. After all, React has components, Vue has components, Angular has components&hellip;it's just how we've always used them.

Because of that, people tend to refer to [Lit](https://lit.dev/) and [FAST Element](https://www.fast.design/docs/fast-element/getting-started/) as frameworks, but they’re not. They’re *libraries*, and that’s an important distinction.

If you want a React component to work, you have to use it with React. If you want a Vue component to work, you have to use it with Vue. If you want an Angular component to work&hellip;well, you get the point.

_With web components, the platform is the framework._

Naturally, a follow up question is "why do you need a library then?" The truth is that we don’t. We can create web components without a library. [Here's a counter component written in pure JavaScript](https://codepen.io/claviska/pen/abwGLPm?editors=0010).

```js
class MyCounter extends HTMLElement {
  static get observedAttributes() {
    return ['count'];
  }
  
  constructor() {
    super();
    this.state = {
      count: 0
    };
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <button type="button">
        Count:
        <span class="count">${this.state.count}</span>
      </button>
    `;
    this.handleClick = this.handleClick.bind(this);
  }
  
  connectedCallback() {
    this.shadowRoot.querySelector('button').addEventListener('click', this.handleClick);
  }

  disconnectedCallback() {
    this.shadowRoot.querySelector('button').removeEventListener('click', this.handleClick);
  }

  get count() {
    return this.state.count; 
  }
  
  set count(newCount) {
    this.state.count = newCount;
    this.update();
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'count') {
      this.state.count = Number(newValue);
      this.update();
    }
  }
  
  handleClick() {
    this.count = this.count + 1;
  }
  
  update() {
    this.shadowRoot.querySelector('.count').textContent = this.state.count;     
  }
}

customElements.define('my-counter', MyCounter);
```

We *choose* to use libraries to improve the the component authoring experience and abstract messy boilerplate into efficient, reusable modules. [Here's a functionally equivalent counter built with Lit](https://codepen.io/claviska/pen/LYLmzKL?editors=0010).

```ts
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-counter')
class MyCounter extends LitElement {
  @property({ type: Number }) count = 0;

  handleClick() {
    this.count++;
  }

  render() {
    return html`
      <button type="button" @click=${this.handleClick}>
        Count: ${this.count}
      </button>
    `;
  }
}
```

Sure, we can bake features such as declarative rendering and reactivity into each and every component, but that’s not <abbr title="Don't Repeat Yourself">DRY</abbr>. It would convolute the code and make our components larger and more difficult to maintain. That’s not what I want and it's probably not what my users want.

Alternatively, we could build those features ourselves and split them off into reusable modules — but that's just reinventing the wheel, isn't it?

When you think of it that way, using a library to build web components makes a lot of sense.

---

Aside: [It’s been said](https://twitter.com/evanplaice/status/1420922358534049804?s=20) that developer experience is the only benefit to using a library. While it’s true that benefits to the end user are marginalized with one-off components, it's worth noting that APIs such as those offered by Lit and FAST Element lead to less bugs due to reduced complexity and less code in the component itself. Consider the counter examples above. Which one is easier to maintain?