Compare coding style, bundle size and performance of 33 different ways to make a Web Component.

Introduction

The same <my-counter/> Web Component has been written in 33 variants.

Source Code

HTMLElement

Homepage: https://html.spec.whatwg.org/multipage/custom-elements.html

Try in WebComponents.dev
class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;

    const style = `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;

    const html = `
      <button id="dec">-</button>
      <span>${this.count}</span>
      <button id="inc">+</button>
    `;

    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
    <style>
      ${style}
    </style>
    ${html}
    `;

    this.buttonInc = this.shadowRoot.getElementById('inc');
    this.buttonDec = this.shadowRoot.getElementById('dec');
    this.spanValue = this.shadowRoot.querySelector('span');

    this.inc = this.inc.bind(this);
    this.dec = this.dec.bind(this);
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  update() {
    this.spanValue.innerText = this.count;
  }

  connectedCallback() {
    this.buttonInc.addEventListener('click', this.inc);
    this.buttonDec.addEventListener('click', this.dec);
  }

  disconnectedCallback() {
    this.buttonInc.removeEventListener('click', this.inc);
    this.buttonDec.removeEventListener('click', this.dec);
  }
}

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

HTMLElement (w/ Lighterhtml)

The hyperHTML strength & experience without its complexity

Homepage: https://medium.com/@WebReflection/lit-html-vs-hyperhtml-vs-lighterhtml-c084abfe1285

GitHub: https://github.com/WebReflection/lighterhtml

Try in WebComponents.dev
import { html, render } from 'lighterhtml';

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;

    this.attachShadow({ mode: 'open' });

    this.update();
  }

  inc = () => {
    this.count++;
    this.update();
  };

  dec = () => {
    this.count--;
    this.update();
  };

  style() {
    return `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  template() {
    return html`
      <style>
        ${this.style()}
      </style>
      <button onclick="${this.dec}">-</button>
      <span>${this.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.shadowRoot, this.template());
  }
}

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

HTMLElement (w/ Lit-html)

Lit-Html - An efficient, expressive, extensible HTML templating library for JavaScript

Homepage: https://lit-html.polymer-project.org/

GitHub: https://github.com/polymer/lit-html

Try in WebComponents.dev
import { html, render } from 'lit-html';

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;

    this.attachShadow({ mode: 'open' });
    this.update();
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  style() {
    return `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  template() {
    return html`
      <style>
        ${this.style()}
      </style>
      <button @click="${this.dec}">-</button>
      <span>${this.count}</span>
      <button @click="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.template(), this.shadowRoot, { eventContext: this });
  }
}

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

HTMLElement (w/ uhtml)

micro html is a ~2.5K lighterhtml subset to build declarative and reactive UI via template literals tags.

Homepage: https://github.com/WebReflection/uhtml

GitHub: https://github.com/WebReflection/uhtml

Try in WebComponents.dev
import { html, render } from "uhtml";

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;

    this.attachShadow({ mode: "open" });

    this.update();
  }

  inc = () => {
    this.count++;
    this.update();
  };

  dec = () => {
    this.count--;
    this.update();
  };

  template() {
    return html`
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <button onclick="${this.dec}">-</button>
      <span>${this.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.shadowRoot, this.template());
  }
}

customElements.define("my-counter", MyCounter);

CanJS

Build CRUD apps in fewer lines of code.

Homepage: https://canjs.com/

GitHub: https://github.com/canjs/canjs

Try in WebComponents.dev
import { StacheElement } from "can";
// Extend Component to define a custom element
class MyCounter extends StacheElement {
  constructor() {
    super();
    this.viewRoot = this.attachShadow({ mode: "open" });
  }

  static view = `<style>
* {
  font-size: 200%;
}

span {
  width: 4rem;
  display: inline-block;
  text-align: center;
}

button {
  width: 64px;
  height: 64px;
  border: none;
  border-radius: 10px;
  background-color: seagreen;
  color: white;
}
</style>
<button on:click="this.decrement()">-</button>
<span>{{ this.count }}</span>
<button on:click="this.increment()">+</button>`;
  static props = {
    count: 0
  };
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}
customElements.define("my-counter", MyCounter);

HyperHTML Element

Framework agnostic, hyperHTML can be used to render any view, including Custom Elements and Web Components.

GitHub: https://github.com/WebReflection/hyperHTML-Element

Try in WebComponents.dev
import HyperHTMLElement from "hyperhtml-element";

class MyCounter extends HyperHTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  created() {
    this.count = 0;
    this.render();
  }

  inc = () => {
    this.count++;
    this.render();
  };

  dec = () => {
    this.count--;
    this.render();
  };

  render() {
    return this.html`
    <style>
      * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
    </style>
    <button onclick=${this.dec}>-</button>
    <span>${this.count}</span>
    <button onclick=${this.inc}>+</button>
    `;
  }
}

MyCounter.define("my-counter");

LitElement

Homepage: https://lit-element.polymer-project.org/

GitHub: https://github.com/polymer/lit-element

Try in WebComponents.dev
import { LitElement, html, css } from 'lit-element';

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number }
  };

  static styles = css`
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `;

  constructor() {
    super();
    this.count = 0;
  }

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

  render() {
    return html`
      <button @click="${this.dec}">-</button>
      <span>${this.count}</span>
      <button @click="${this.inc}">+</button>
    `;
  }
}

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

Neow (Alpha)

Modern, fast, and light it's the front-end framework to fall inlove with. (ALPHA)

Homepage: https://neow.dev

GitHub: (alpha)

Try in WebComponents.dev
import { ComponentMixin } from "@neow/core";

class MyComponent extends ComponentMixin(HTMLElement) {
  static template = `
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  <button onclick="{{this.dec()}}">-</button>
  <span>{{this.counter}}</span>
  <button onclick="{{this.inc()}}">+</button>
`;

  counter = 0;

  inc() {
    this.counter++;
  }

  dec() {
    this.counter--;
  }
}

customElements.define("my-counter", MyComponent);

Omi w/Class

Front End Cross-Frameworks Framework - Merge Web Components, JSX, Virtual DOM, Functional style, observe or Proxy into one framework with tiny size and high performance. Write components once, using in everywhere, such as Omi, React, Preact, Vue or Angular.

Homepage: http://omijs.org

GitHub: https://github.com/Tencent/omi

Try in WebComponents.dev
import { define, WeElement, html } from "omi";

class MyCounter extends WeElement {
  static get propTypes() {
    return {
      count: Number
    };
  }

  static get defaultProps() {
    return { count: 0 };
  }

  install() {
    this.data = { count: this.props.count };
  }

  static get css() {
    return `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  inc = () => {
    this.data.count++;
    this.update();
  };

  dec = () => {
    this.data.count--;
    this.update();
  };

  render(props) {
    return html`
      <button onclick="${this.dec}">-</button>
      <span>${this.data.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }
}

define("my-counter", MyCounter);

SkateJS (with Lit-html)

SkateJS - Effortless custom elements powered by modern view libraries.

Homepage: https://skatejs.netlify.com/

GitHub: https://github.com/skatejs/skatejs

Try in WebComponents.dev
import Element from "@skatejs/element";
import { render, html } from "lit-html";

class MyCounterElement extends Element {
  static get props() {
    return {
      count: Number
    };
  }

  inc = () => {
    this.count++;
  };

  dec = () => {
    this.count--;
  };

  render() {
    const style = `
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return html`
      <style>
        ${style}
      </style>
      <button @click="${this.dec}">
        -
      </button>
      <span>${this.count}</span>
      <button @click="${this.inc}">
        +
      </button>
    `;
  }

  renderer() {
    return render(this.render(), this.renderRoot);
  }
}

customElements.define("my-counter", MyCounterElement);

SkateJS (with Preact)

SkateJS - Effortless custom elements powered by modern view libraries.

Homepage: https://skatejs.netlify.com/

GitHub: https://github.com/skatejs/skatejs

Try in WebComponents.dev
/** @jsx h **/
import Element, { h } from "@skatejs/element-preact";

class MyCounterElement extends Element {
  static get props() {
    return {
      count: Number
    };
  }

  inc = () => {
    this.count++;
  };

  dec = () => {
    this.count--;
  };

  render() {
    const style = `host * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return (
      <host>
        <style>{style}</style>
        <button onclick={this.dec}>-</button>
        <span>{this.count}</span>
        <button onclick={this.inc}>+</button>
      </host>
    );
  }
}

customElements.define("my-counter", MyCounterElement);

SlimJS

Slim.js is a lightning fast library for development of native Web Components and Custom Elements based on modern standards. No black magic involved, no useless dependencies.

Homepage: https://slimjs.com

GitHub: https://github.com/slimjs/slim.js

Try in WebComponents.dev
import { Slim } from "slim-js/Slim.js";
import { tag, template, useShadow } from "slim-js/Decorators";

@tag("my-counter")
@useShadow(true)
@template(`
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style >
  <button click="dec">-</button>
  <span>{{parseCount(count)}}</span>
  <button click="inc">+</button>
`)
class MyCounter extends Slim {
  constructor() {
    super();
    this.count = 0;
  }

  parseCount(num) {
    return String(num);
  }

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }
}

Atomico

Atomico is a microlibrary (3.9kB) inspired by React Hooks, designed and optimized for the creation of small, powerful, declarative webcomponents and only using functions

Homepage: https://atomico.gitbook.io/doc/

GitHub: https://github.com/atomicojs/atomico

Try in WebComponents.dev
/** @jsx h */
import { h, customElement, useProp } from "atomico";

function MyCounter() {
  let [count, setCount] = useProp("count");

  const style = `
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

  return (
    <host shadowDom>
      <style>{style}</style>
      <button onclick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onclick={() => setCount(count + 1)}>+</button>
    </host>
  );
}

MyCounter.props = {
  count: {
    type: Number,
    reflect: true,
    value: 0
  }
};

customElements.define("my-counter", customElement(MyCounter));

Gallop

Use full power of web components

GitHub: https://github.com/tarnishablec/gallop

Try in WebComponents.dev
import { component, html, useState, useStyle, css } from "@gallop/gallop";

export const MyCounter = component(
  "my-counter",
  () => {
    const [state] = useState({ count: 0 });
    useStyle(
      () => css`
        * {
          font-size: 200%;
        }
        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }
        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      `
    );
    return html`
      <button @click="${() => state.count--}">
        -
      </button>
      <span>${state.count}</span>
      <button @click="${() => state.count++}">
        +
      </button>
    `;
  },
  { propList: [] }
);

Haunted

React's Hooks API implemented for web components 👻

GitHub: https://github.com/matthewp/haunted

Try in WebComponents.dev
import { html } from "lit-html";
import { component, useState } from "haunted";

function Counter() {
  const [count, setCount] = useState(0);

  return html`
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button @click=${() => setCount(count - 1)}>
      -
    </button>
    <span>${count}</span>
    <button @click=${() => setCount(count + 1)}>
      +
    </button>
  `;
}

customElements.define("my-counter", component(Counter));

Heresy w/Hook

Don't simulate the DOM. Be the DOM. React-like Custom Elements via the V1 API built-in extends.

Homepage: https://medium.com/@WebReflection/any-holy-grail-for-web-components-c3d4973f3f3f

GitHub: https://github.com/WebReflection/heresy

Try in WebComponents.dev
import { define } from "heresy";

define("MyCounter", {
  style: MyCounter => `
    ${MyCounter} * {
      font-size: 200%;
    }
    ${MyCounter} span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }
    ${MyCounter} button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `,
  render({ useState }) {
    const [count, update] = useState(0);
    this.html`
      <button onclick="${() => update(count - 1)}">-</button>
      <span>${count}</span>
      <button onclick="${() => update(count + 1)}">+</button>
    `;
  }
});

Heresy

Don't simulate the DOM. Be the DOM. React-like Custom Elements via the V1 API built-in extends.

Homepage: https://medium.com/@WebReflection/any-holy-grail-for-web-components-c3d4973f3f3f

GitHub: https://github.com/WebReflection/heresy

Try in WebComponents.dev
import { define } from "heresy";

define("MyCounter", {
  style: MyCounter => `
    ${MyCounter} * {
      font-size: 200%;
    }
    ${MyCounter} span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }
    ${MyCounter} button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `,
  oninit() {
    this.count = 0;
  },
  onclick({ currentTarget }) {
    this[currentTarget.dataset.op]();
    this.render();
  },
  inc() {
    this.count++;
  },
  dec() {
    this.count--;
  },
  render() {
    this.html`
      <button data-op="dec" onclick="${this}">-</button>
      <span>${this.count}</span>
      <button data-op="inc" onclick="${this}">+</button>
    `;
  }
});

Hybrids

Hybrids is a UI library for creating web components with strong declarative and functional approach based on plain objects and pure functions.

Homepage: https://hybrids.js.org

GitHub: https://github.com/hybridsjs/hybrids

Try in WebComponents.dev
import { html, define } from "hybrids";

function inc(host) {
  host.count++;
}

function dec(host) {
  host.count--;
}

const MyCounter = {
  count: 0,
  render: ({ count }) => html`
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button onclick="${dec}">-</button>
    <span>${count}</span>
    <button onclick="${inc}">+</button>
  `
};

define("my-counter", MyCounter);

Litedom

A very small (3kb) view library that is packed: Web Components, Custom Element, Template Literals, Reactive, Data Binding, One Way Data Flow, Two-way data binding, Event Handling, Props, Lifecycle, State Management, Computed Properties, Directives. No dependencies, no virtual dom, no build tool. Wow! ...but it's a just a view library!

Homepage: https://litedom.js.org/

GitHub: https://github.com/mardix/litedom

Try in WebComponents.dev
import Litedom from "litedom";

Litedom({
  tagName: "my-counter",
  shadowDOM: true,
  template: `
  <button @click="dec">-</button>
  <span>{this.count}</span>
  <button @click="inc">+</button>
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  `,
  data: {
    count: 0
  },
  dec() {
    this.data.count--;
  },
  inc() {
    this.data.count++;
  }
});

Ottavino

Tiny, Fast and Declarative User Interface Development. Using native custom elements API (but not only). As simple as it gets.

GitHub: https://github.com/betterthancode/ottavino

Try in WebComponents.dev
import { component } from "ottavino";

component({
  tag: "my-counter",
  shadow: true,
  template: `
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button onclick="{{this.decrease()}}">-</button>
    <span>{{this.count}}</span>
    <button onclick="{{this.increase()}}" >+</button>
    `,
  properties: {
    count: 0
  },
  this: {
    increase: function() {
      this.count++;
    },
    decrease: function() {
      this.count--;
    }
  }
});

Swiss

Functional custom elements

GitHub: https://github.com/luwes/swiss

Try in WebComponents.dev
import { define } from "swiss";
import { html, render } from "lit-html";

const Counter = (CE) => (el) => {
  el.attachShadow({ mode: "open" });

  return {
    update: () =>
      render(
        html`
          <style>
            * {
              font-size: 200%;
            }

            span {
              width: 4rem;
              display: inline-block;
              text-align: center;
            }

            button {
              width: 4rem;
              height: 4rem;
              border: none;
              border-radius: 10px;
              background-color: seagreen;
              color: white;
            }
          </style>
          <button @click="${() => el.count--}">-</button>
          <span>${el.count}</span>
          <button @click="${() => el.count++}">+</button>
        `,
        el.shadowRoot
      ),
  };
};

define("my-counter", {
  props: { count: 0 },
  setup: Counter,
});

uce

µhtml based Custom Elements.

Homepage: https://github.com/WebReflection/uce

GitHub: https://github.com/WebReflection/uce

Try in WebComponents.dev
import { define } from "uce";

define("my-counter", {
  attachShadow: { mode: "open" },
  init() {
    this.count = 0;
    this.dec = () => {
      this.count--;
      this.render();
    };
    this.inc = () => {
      this.count++;
      this.render();
    };
    this.render();
  },
  render() {
    this.html`
      <style>
      * {
        font-size: 200%;
      }
      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }
      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
      </style>
      <button onclick="${this.dec}">-</button>
      <span>${this.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }
});

Lightning Web Components

⚡️ LWC - A Blazing Fast, Enterprise-Grade Web Components Foundation

Homepage: https://lwc.dev

GitHub: https://github.com/salesforce/lwc

Try in WebComponents.dev
import { LightningElement, api, buildCustomElementConstructor } from "lwc";

export default class MyCounter extends LightningElement {
  count = 0;

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }
}

customElements.define("my-counter", buildCustomElementConstructor(MyCounter));

Lume Element

Create Custom Elements with reactivity and automatic re-rendering

GitHub: https://github.com/lume/element

Try in WebComponents.dev
import { Element, css, createSignal } from "@lume/element";

export class MyCounter extends Element {
  constructor() {
    super();
    this._count = createSignal(0);
  }

  get count() {
    return this._count[0]();
  }

  set count(val) {
    this._count[1](val);
  }

  template() {
    return (
      <>
        <button onclick={() => (this.count -= 1)}>-</button>
        <span>{this.count}</span>
        <button onclick={() => (this.count += 1)}>+</button>
      </>
    );
  }

  static get css() {
    return css`
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 4rem;
        height: 4rem;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }
}

customElements.define("my-counter", MyCounter);

Solid Element

The Deceptively Simple User Interface Library

GitHub: https://github.com/ryansolid/solid

Try in WebComponents.dev
import { createSignal } from "solid-js";
import { customElement } from "solid-element";

const style = `div * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 4rem;
          height: 4rem;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }`;

customElement("my-counter", () => {
  const [count, setCount] = createSignal(0);
  return (
    <div>
      <style>{style}</style>
      <button onClick={() => setCount(count() - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count() + 1)}>+</button>
    </div>
  );
});

Stencil

Stencil is a toolchain for building reusable, scalable Design Systems. Generate small, blazing fast, and 100% standards based Web Components that run in every browser.

Homepage: https://stenciljs.com/

GitHub: https://github.com/ionic-team/stencil

Try in WebComponents.dev
/* @jsx h */
import { h, Component, State, Host } from "@stencil/core";

@Component({
  tag: "my-counter",
  styleUrl: "index.css",
  shadow: true
})
export class MyCounter {
  @State() count: number = 0;

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

  render() {
    return (
      <Host>
        <button onClick={this.dec.bind(this)}>-</button>
        <span>{this.count}</span>
        <button onClick={this.inc.bind(this)}>+</button>
      </Host>
    );
  }
}

Angular 9 Elements (wrapped with angular-elements)

One framework. Mobile & desktop.

Homepage: https://angular.io/

GitHub: https://github.com/angular/angular

Try in WebComponents.dev
import {
  Input,
  Component,
  ViewEncapsulation,
  ChangeDetectorRef
} from "@angular/core";

@Component({
  selector: "my-counter",
  template: `
    <button (click)="dec()">-</button>
    <span>{{count}}</span>
    <button (click)="inc()">+</button>
  `,
  styles: [
    `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `
  ],
  encapsulation: ViewEncapsulation.ShadowDom
})
export default class MyCounter {
  @Input() count: number = 0;

  constructor(private cd: ChangeDetectorRef) {}

  dec() {
    this.count--;
    this.cd.detectChanges();
  }

  inc() {
    this.count++;
    this.cd.detectChanges();
  }
}

Preact w/Class (wrapped with preact-custom-element)

Fast 3kB alternative to React with the same modern API.

Homepage: https://preactjs.com/

GitHub: https://github.com/preactjs/preact

Try in WebComponents.dev
import { createCustomElement } from "@wcd/preact-custom-element";
import { Component, html } from "htm/preact";
import "preact";

class MyCounter extends Component {
  state = {
    count: 0
  };

  inc = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  dec = () => {
    this.setState(prev => ({ count: prev.count - 1 }));
  };

  render(props, state) {
    return html`
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <button onClick=${this.dec}>
        -
      </button>
      <span>${state.count}</span>
      <button onClick=${this.inc}>
        +
      </button>
    `;
  }
}

customElements.define("my-counter", createCustomElement(MyCounter, ["count"]));

React w/Class (wrapped with react-to-webcomponent)

A JavaScript library for building user interfaces

Homepage: https://reactjs.org/

GitHub: https://github.com/facebook/react/

Try in WebComponents.dev
import React from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

interface State {
  count: number;
}
interface Props {}

export default class MyCounter extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    const styles = `.my-counter * {
      font-size: 200%;
    }

    .my-counter span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    .my-counter button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return (
      <div className="my-counter">
        <style>{styles}</style>
        <button onClick={() => this.setState({ count: this.state.count - 1 })}>
          -
        </button>
        <span>{this.state.count}</span>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          +
        </button>
      </div>
    );
  }
}

customElements.define(
  "my-counter",
  reactToWebComponent(MyCounter, React, ReactDOM)
);

React w/Hook (wrapped with react-to-webcomponent)

A JavaScript library for building user interfaces

Homepage: https://reactjs.org/

GitHub: https://github.com/facebook/react/

Try in WebComponents.dev
import React, { useState } from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

export default function MyCounter() {
  const [count, setCount] = useState(0);

  const styles = `
    .my-counter * {
      font-size: 200%;
    }

    .my-counter span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    .my-counter button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

  return (
    <div className="my-counter">
      <style>{styles}</style>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

customElements.define(
  "my-counter",
  reactToWebComponent(MyCounter, React, ReactDOM)
);

Riot (wrapped with @riotjs/custom-elements)

Simple and elegant component-based UI library

Homepage: https://riot.js.org/

GitHub: https://github.com/riot/riot

Try in WebComponents.dev
<my-component>
	<style>
		* {
			font-size: 200%;
		}

		span {
			width: 4rem;
			display: inline-block;
			text-align: center;
		}

		button {
			width: 64px;
			height: 64px;
			border: none;
			border-radius: 10px;
			background-color: seagreen;
			color: white;
		}
	</style>

	<button onclick={dec}>
		-
	</button>
	<span>{state.count}</span>
	<button onclick={inc}>
		+
	</button>

	<script>
		export default {
      onBeforeMount(props, state) {
        this.state = {
					count: 0
        }
      },
      inc() {
        this.update({
          count: this.state.count+1
        })
      },
      dec() {
        this.update({
          count: this.state.count-1
        })
      },
    }
	</script>
</my-component>

Svelte (with {customElement: true})

Cybernetically enhanced web apps

Homepage: https://svelte.dev/

GitHub: https://github.com/sveltejs/svelte

Try in WebComponents.dev
<svelte:options tag="my-counter" />

<script>
	let count = 0;

	function inc() {
		count++;
	}

	function dec() {
		count--;
	}
</script>

<style>
	* {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 64px;
		height: 64px;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

<button on:click={dec}>
	-
</button>
<span>{count}</span>
<button on:click={inc}>
	+
</button>

Vue.js (wrapped with vue-custom-element)

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Homepage: https://vuejs.org/

GitHub: https://github.com/vuejs/vue

Try in WebComponents.dev
<template>
	<div>
		<button @click="this.dec">
					-
		</button>
		<span>{{count}}</span>
		<button @click="this.inc">
					+
		</button>
	</div>
</template>

<script>
	export default {
		tag: 'my-counter',
		name: 'MyCounter',
		data() {
			return { count: 0 }
		},
		methods: {
			inc: function() {
				this.count++;
			},
			dec: function() {
				this.count--;
			}
		}
	};
</script>

<style scoped>
	span,
	button {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 64px;
		height: 64px;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

Bundle Analysis

HTMLElement

No dependencies

Component size including library

Minified - Uncompressed1,293
Minified + Gzip558
Minified + Brotli418

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Lighterhtml)

Dependencies

lighterhtml : ^3.1.3

Component size including library

Minified - Uncompressed15,699
Minified + Gzip6,360
Minified + Brotli5,799

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Lit-html)

Dependencies

lit-html : ^1.2.1

Component size including library

Minified - Uncompressed9,955
Minified + Gzip3,530
Minified + Brotli3,144

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ uhtml)

Dependencies

uhtml : ^1.11.6

Component size including library

Minified - Uncompressed6,973
Minified + Gzip3,179
Minified + Brotli2,849

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

CanJS

Dependencies

can : ^6.4.0

Component size including library

Minified - Uncompressed230,634
Minified + Gzip63,154
Minified + Brotli54,620

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HyperHTML Element

Dependencies

hyperhtml-element : ^3.12.3

Component size including library

Minified - Uncompressed23,316
Minified + Gzip9,061
Minified + Brotli8,277

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

LitElement

Dependencies

lit-element : ^2.3.1

Component size including library

Minified - Uncompressed20,395
Minified + Gzip6,682
Minified + Brotli5,997

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Neow (Alpha)

Dependencies

@neow/core : 0.0.6

Component size including library

Minified - Uncompressed4,435
Minified + Gzip1,879
Minified + Brotli1,617

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Omi w/Class

Dependencies

omi : ^6.19.3

Component size including library

Minified - Uncompressed24,622
Minified + Gzip8,412
Minified + Brotli7,566

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SkateJS (with Lit-html)

Dependencies

@skatejs/element : 0.0.1
lit-html : ^1.2.1

Component size including library

Minified - Uncompressed15,580
Minified + Gzip5,248
Minified + Brotli4,662

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SkateJS (with Preact)

Dependencies

@skatejs/element-preact : 0.0.1

Component size including library

Minified - Uncompressed17,557
Minified + Gzip6,244
Minified + Brotli5,630

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SlimJS

Dependencies

slim-js : ^4.0.7

Component size including library

Minified - Uncompressed9,467
Minified + Gzip3,661
Minified + Brotli3,220

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Atomico

Dependencies

atomico : ^0.23.2

Component size including library

Minified - Uncompressed5,800
Minified + Gzip2,735
Minified + Brotli2,434

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Gallop

Dependencies

@gallop/gallop : ^0.9.9-beta.6

Component size including library

Minified - Uncompressed11,921
Minified + Gzip4,581
Minified + Brotli4,083

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Haunted

Dependencies

haunted : ^4.7.0

Component size including library

Minified - Uncompressed15,327
Minified + Gzip5,267
Minified + Brotli4,708

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Heresy w/Hook

Dependencies

heresy : ^0.28.1

Component size including library

Minified - Uncompressed26,556
Minified + Gzip9,984
Minified + Brotli9,094

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Heresy

Dependencies

heresy : ^0.28.1

Component size including library

Minified - Uncompressed26,679
Minified + Gzip10,022
Minified + Brotli9,130

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Hybrids

Dependencies

hybrids : ^4.2.1

Component size including library

Minified - Uncompressed20,811
Minified + Gzip6,941
Minified + Brotli6,257

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Litedom

Dependencies

litedom : ^0.12.1

Component size including library

Minified - Uncompressed9,392
Minified + Gzip3,835
Minified + Brotli3,412

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Ottavino

Dependencies

ottavino : ^0.2.4

Component size including library

Minified - Uncompressed4,444
Minified + Gzip1,977
Minified + Brotli1,709

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Swiss

Dependencies

swiss : ^2.0.0

Component size including library

Minified - Uncompressed12,126
Minified + Gzip4,373
Minified + Brotli3,888

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

uce

Dependencies

uce : ^0.13.1

Component size including library

Minified - Uncompressed8,563
Minified + Gzip3,829
Minified + Brotli3,438

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lightning Web Components

Dependencies

lwc : ^1.6.2

Component size including library

Minified - Uncompressed38,531
Minified + Gzip13,079
Minified + Brotli11,763

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following LWC settings:

const outputConfig: {
  format: 'esm',
  sourcemap: true,
};

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lume Element

Dependencies

@lume/element : 0.0.15

Component size including library

Minified - Uncompressed9,520
Minified + Gzip3,781
Minified + Brotli3,379

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the solid preset:

{
  "presets": [
    "solid"
  ]
}

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Solid Element

Dependencies

solid-js : ^0.18.4
solid-element : ^0.18.4

Component size including library

Minified - Uncompressed12,575
Minified + Gzip4,846
Minified + Brotli4,316

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the solid preset:

{
  "presets": [
    "solid"
  ]
}

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Stencil

Dependencies

@stencil/core : ^1.14.0

Component size including library

Minified - Uncompressed6,811
Minified + Gzip3,565
Minified + Brotli3,154

Composition

Open Bundle Visualizer

Compilation details

All Sources have been compiled with Stencil CLI and settings:

// tsconfig.js
...
"compilerOptions": {
  "allowSyntheticDefaultImports": true,
  "allowUnreachableCode": false,
  "declaration": false,
  "experimentalDecorators": true,
  "lib": ["DOM", "ES2018"],
  "moduleResolution": "node",
  "module": "esnext",
  "target": "es2017",
  "noUnusedLocals": true,
  "noUnusedParameters": true,
  "jsx": "react",
  "jsxFactory": "h",
  "types": []
}
...

//stencil.config.js
import { Config } from '@stencil/core';
export const config: Config = {
  srcDir: 'stencil/src',
  namespace: 'my-counter',
  hashFileNames: false,
  plugins: [],
  outputTargets: [
    {
      type: 'www',
      serviceWorker: null, // disable service workers
    },
  ],
  hydratedFlag: null,
  extras: {
    cssVarsShim: false,
    dynamicImportShim: false,
    safari10: false,
    scriptDataOpts: false,
    shadowDomShim: false,
  },
};

Bundling details

Bundled by Stencil CLI

Angular 9 Elements

Dependencies

@angular/core : ^9.1.9
@angular/cli : ^9.1.7
@angular/common : ^9.1.9
@angular/compiler : ^9.1.9
@angular/compiler-cli : ^9.1.9
@angular-devkit/build-angular : ^0.901.7
document-register-element : ^1.14.3
@angular/elements : ^9.1.9
@angular/platform-browser-dynamic : ^9.1.9
@angular/platform-browser : ^9.1.9

Component size including library

Minified - Uncompressed227,232
Minified + Gzip69,831
Minified + Brotli59,961

Composition

Open Bundle Visualizer

Compilation details

Compile with Angular compiler and the following angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "download",
  "projects": {
    "elements": {
      "root": "download/009-angular-9-elements",
      "sourceRoot": "download/009-angular-9-elements/src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "aot": true,
            "outputPath": "download/009-angular-9-elements/dist",
            "index": "download/009-angular-9-elements/src/index.html",
            "main": "download/009-angular-9-elements/src/index.ts",
            "tsConfig": "angular.tsconfig.json",
            "polyfills": "polyfills.ts",
            "assets": [],
            "styles": [],
            "scripts": [
              {
                "input": "node_modules/document-register-element/build/document-register-element.js"
              }
            ]
          },
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "none",
              "sourceMap": true,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": false,
              "vendorChunk": true,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  },
  "defaultProject": "elements",
  "schematics": {
    "@schematics/angular:component": {
      "prefix": "app",
      "style": "css"
    },
    "@schematics/angular:directive": {
      "prefix": "app"
    }
  }
}

And following tsconfig.json:


{
  "compileOnSave": false,
  "compilerOptions": {
    "module": "esnext",
    "outDir": "./download/009-angular-9-elements/dist/out-tsc",
    "baseUrl": "download/009-angular-9-elements/src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2015",
    "typeRoots": ["node_modules/@types"],
    "lib": ["es2016", "dom"],
    "paths": {
      "EnumUtils": ["dist/enum-utils"],
      "EnumUtils/*": ["dist/enum-utils/*"]
    }
  },
  "files": ["download/009-angular-9-elements/src/index.ts", "polyfills.ts"]
}

Bundling details

Bundled with Angular CLI

Preact w/Class

Dependencies

preact : ^10.4.4
@wcd/preact-custom-element : ^3.1.3

Component size including library

Minified - Uncompressed11,617
Minified + Gzip4,774
Minified + Brotli4,346

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:

[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

React w/Class

Dependencies

react : ^16.13.1
react-dom : ^16.13.1
react-to-webcomponent : ^1.4.0

Component size including library

Minified - Uncompressed131,430
Minified + Gzip40,901
Minified + Brotli36,008

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following TypeScript settings:
 {
  module: ts.ModuleKind.ESNext,
  experimentalDecorators: true,
  emitDecoratorMetadata: true,
  lib: ['ESNext', 'dom'],
  target: ts.ScriptTarget.ESNext
};

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

React w/Hook

Dependencies

react : ^16.13.1
react-dom : ^16.13.1
react-to-webcomponent : ^1.4.0

Component size including library

Minified - Uncompressed131,306
Minified + Gzip40,870
Minified + Brotli36,004

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following TypeScript settings:
 {
  module: ts.ModuleKind.ESNext,
  experimentalDecorators: true,
  emitDecoratorMetadata: true,
  lib: ['ESNext', 'dom'],
  target: ts.ScriptTarget.ESNext
};

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Riot

Dependencies

riot : ^4.12.4
@riotjs/custom-elements : ^4.1.1

Component size including library

Minified - Uncompressed18,809
Minified + Gzip6,906
Minified + Brotli6,240

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Riot settings:

const settings = {
  scopedCss: false,
};

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Svelte

Dependencies

svelte : ^3.23.0

Component size including library

Minified - Uncompressed3,592
Minified + Gzip1,728
Minified + Brotli1,511

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Transpiled by WebComponents.dev compiler with the following Svelte settings:

const options = {
  format: 'esm',
  customElement: true
};

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Vue.js

Dependencies

vue : ^2.6.11
vue-custom-element : ^3.2.14

Component size including library

Minified - Uncompressed76,860
Minified + Gzip27,176
Minified + Brotli24,357

Composition

Open Bundle Visualizer

Compilation details

Vue sources have been Transpiled by rollup-plugin-vue

Bundling details

Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Bundle Size Comparison

Bundle size of 1 component

If you only deliver a single <my-counter/> you have the full cost of the library for a single component. This is the total cost of a single component with the library included.

Notes

The bare HTMLElement component doesn't have any dependency and creates a unmatched tiny package.

Svelte is doing also very well with a very small library runtime cost primarily due to excellent tree-shaking. This also means that as the component use more of Svelte capabilities, more of the library will be included.

Vue and React libraries do poorly on tree-shake so you pretty much have the total runtime, no matter how complex your components are. Vue 3 is supposed to offer better tree-shake support - it would be interesting to see.

Some libraries bring significant value and are more feature rich than others. It's normal to see larger sizes in these cases.

Nevertheless, for a simple counter component, we would like to see more tree-shaking happening.

On the other hand, once you start having multiple components using a broad spectrum of the library capabilities, you end up with the entire library anyway.

Estimated Bundle size of 30 components using the same library

This is an estimated size of a bundle of 30 my-counter-like components using the same library. All components will share the library code so the estimated size is calculated with: 1 bundle-with-dependencies + 29x components-without-dependencies.

Notes

It's interesting to see that some libraries managed to be smaller than bare HTMLElement. This is achieved by sharing more runtime accross the different components. Individual components are smaller and pay for the runtime. A library is not always an overhead !

On the opposite, Svelte has a super tiny runtime but the resulting components are bigger than most solutions. Only Vue 2 and Angular components are bigger.

Performance

First Contentful Paint

The First Contentful Paint is benchmarked with Tachometer.

The benchmark page is composed of 50 <my-counter></my-counter> laid out in a single page with the library bundle code inlined.

Notes

Take into consideration that there is a margin of error of 2ms. So the first 16 libraries are moree of less equal!

Things that play a role in this performance figure are:

  • The size of the JavaScript to parse (bigger = longer).
  • The performance of the library to create the DOM nodes from scratch.

Everything runs locally so the network download time is not taken into account here.

Everything runs on a relatively powerful laptop and results could be completely different on a lower-end device like a phone or a tablet.

Final Notes

It's hard to find so much diversity in a technical solution. It tells a lot about Web Components and the low level API provided. All the 33 variants could be on the same page if we wanted to!

On bundle size, the results are already very promising but there is still room for improvement:

  • Some libraries would benefit from splitting their features, to better benefit from tree-shaking.
  • Except Stencil and Svelte, none of the libraries offer CSS minification out of the box.

Web Components are well supported in browsers now. Still, for old browsers (including IE11), polyfills are required. The bundle sizes reported here are only for modern browsers that don't need any polyfills. It was too much additional work to include polyfills in this post. Maybe later... before they become obsolete.

We will report on new libraries and updates. Make sure to follow us on Twitter.

Feedback or Questions

Please chat with us on our Spectrum or Twitter channels.

Thanks

Thanks to all those who helped us review and correct this analysis.

Changes since April release

  • Upgrade the compilation pipeline (including rollup 1 to rollup 2)
  • Upgrade all libraries
  • Add new libraries: Swiss, Gallop and @lume/element
  • Compiling Vue SFC with rollup-plugin-vue (Now templats are compiled)
  • Include Stencil and Vue in performance report

Brought to you by
Follow us