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

Introduction

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

Table of Content

Source Code

HTMLElement

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

Try in WebComponents.dev
const template = document.createElement('template');
template.innerHTML = `
  <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 id="dec">-</button>
  <span id="count"></span>
  <button id="inc">+</button>`;

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.shadowRoot.getElementById('inc').onclick = () => this.inc();
    this.shadowRoot.getElementById('dec').onclick = () => this.dec();
    this.update(this.count);
  }

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

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

  update(count) {
    this.shadowRoot.getElementById('count').innerHTML = count;
  }
}

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

HTMLElement (w/ Alpine JS)

A rugged, minimal framework for composing JavaScript behavior in your markup.

Homepage: https://github.com/alpinejs/alpine

GitHub: https://github.com/alpinejs/alpine

Try in WebComponents.dev
import "alpinejs";

const template = document.createElement("template");
template.innerHTML = `
  <style>
    span, button {
      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>
  <div x-data="$el.parentElement.data()">
    <button @click="dec()">-</button>
    <span x-text="count"></span>
    <button @click="inc()">+</button>
  </div>
`;

export class MyCounter extends HTMLElement {
  connectedCallback() {
    this.append(template.content.cloneNode(true));
  }

  data() {
    return {
      count: 0,
      inc() {
        this.count++;
      },
      dec() {
        this.count--;
      },
    };
  }
}

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' });
  }

  connectedCallback() {
    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' });
  }

  connectedCallback() {
    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/ Mithril)

A JavaScript Framework for Building Brilliant Applications

Homepage: https://mithril.js.org/

GitHub: https://github.com/MithrilJS/mithril.js

Try in WebComponents.dev
/* @jsx m */
import m from "mithril";

export class MyCounter extends HTMLElement {
  count: number = 0;

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

  get styles() {
    return (
      <style>{`
    	span,button {
    		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>
    );
  }

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

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

  connectedCallback() {
    this.update();
  }

  update() {
    m.render(
      this.shadowRoot as any,
      <div>
        {this.styles}
        <button onclick={this.dec.bind(this)}>-</button>
        <span>{this.count}</span>
        <button onclick={this.inc.bind(this)}>+</button>
      </div>
    );
  }
}

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" });
  }

  connectedCallback() {
    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);

ElemX

Library for connecting MobX to native Web Components with a Vue-like template binding syntax

Homepage: https://github.com/agquick/elemx.js

GitHub: https://github.com/agquick/elemx.js

Try in WebComponents.dev
import { observable } from "mobx"; // needs mobx@5.15.7
import { ReactiveElement } from "elemx";

class MyCounterElement extends ReactiveElement {
  @observable counter = 0;

  templateHTML() {
    return `
    <button @on-click="this.dec">-</button>
    {{this.counter}}
    <button @on-click="this.inc">+</button>
    `;
  }

  inc() {
    this.counter++;
  }

  dec() {
    this.counter--;
  }

  templateCSS() {
    return `
  * {
		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", MyCounterElement);

FAST

The adaptive interface system for modern web experiences

Homepage: https://www.fast.design/

GitHub: https://github.com/microsoft/fast

Try in WebComponents.dev
import { html, css, FASTElement } from "@microsoft/fast-element";

class MyCounter extends FASTElement {
  static definition = {
    name: "my-counter",
    template: html<MyCounter>`
      <button @click="${(x) => x.count--}">-</button>
      <span>${(x) => x.count}</span>
      <button @click="${(x) => x.count++}">+</button>
    `,
    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;
      }
    `,
    attributes: ["count"],
  };

  count: number = 0;
}

FASTElement.define(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");

Joist

A small (~2kb) library to help with the creation of web components and web component based applications

Homepage: https://github.com/joist-framework/joist

GitHub: https://github.com/joist-framework/joist

Try in WebComponents.dev
// By Danny Blue GitHub:deebloo
import { component, JoistElement, handle, get, State } from "@joist/component";

@component({
  tagName: "my-counter",
  shadowDom: "open",
  styles: [
    `* {
      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;
    }`,
  ],
  state: 0,
  render: ({ state, host, run }) => {
    const self = host.shadowRoot;
    if (!self.innerHTML) {
      self.innerHTML = `
        <button id="dec">-</button>
        <span id="count"></span>
        <button id="inc">+</button>`;
      self.getElementById("inc").addEventListener("click", run("add", 1));
      self.getElementById("dec").addEventListener("click", run("add", -1));
    }
    self.getElementById("count").innerHTML = state.toString();
  },
})
export class CounterElement extends JoistElement {
  @get(State) private state!: State<number>;

  @handle("add") add(_: Event, value: number) {
    this.state.setValue(this.state.value + value);
  }
}

Joist (with lit-html)

A small (~2kb) library to help with the creation of web components and web component based applications

Homepage: https://github.com/joist-framework/joist

GitHub: https://github.com/joist-framework/joist

Try in WebComponents.dev
// By Danny Blue GitHub:deebloo

import { component, JoistElement, handle, get, State } from "@joist/component";
import { template } from "@joist/component/lit-html";
import { html } from "lit-html";

@component({
  tagName: "my-counter",
  shadowDom: "open",
  state: 0,
  styles: [
    `* {
      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;
    }`,
  ],
  render: template(({ state, run }) => {
    return html`
      <button @click="${run("update", -1)}">-</button>
      <span>${state}</span>
      <button @click="${run("update", 1)}">+</button>
    `;
  }),
})
export class CounterElement extends JoistElement {
  @get(State) private state!: State<number>;

  @handle("update")
  updateCount(_: Event, num: number) {
    return this.state.setValue(this.state.value + num);
  }
}

LitElement v2

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);

LitElement v3 (next-major)

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@next-major";

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);

ReadyMade

JavaScript microlibrary for developing Web Components with Decorators

Homepage: https://github.com/readymade-ui/readymade

GitHub: https://github.com/readymade-ui/readymade

Try in WebComponents.dev
import { Component, State, CustomElement } from "@readymade/core";

@Component({
  selector: "my-counter",
  template: `
    <button id="dec">-</button>
    <span>{{c}}</span>
    <button id="inc">+</button>
  `,
  style: `
	span,
	button {
		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;
	}
	`,
})
export class MyCounter extends CustomElement {
  private c: string = "0";
  connectedCallback() {
    this.shadowRoot
      .querySelector("#inc")
      .addEventListener("click", this.inc.bind(this));
    this.shadowRoot
      .querySelector("#dec")
      .addEventListener("click", this.dec.bind(this));
  }

  get count() {
    return Number.parseInt(this.c);
  }

  inc() {
    this.setState("c", this.count + 1);
  }

  dec() {
    this.setState("c", this.count - 1);
  }

  @State()
  public getState() {
    return { c: this.c || "0" };
  }
}

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--;
  }
}

uce-template

A tiny toolless library with tools included

Homepage: https://medium.com/@WebReflection/a-new-web-components-wonder-f9e042785a91

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

Try in WebComponents.dev
import "uce-template";

const template = `
<my-counter>
	<button onclick="{{dec}}">
    -
  </button>
	<span>{{state.count}}</span>
	<button onclick="{{inc}}">
    +
  </button>
</my-counter>

<script type="module">
	import reactive from "@uce/reactive";
  export default {
    setup(element) {
      const state = reactive({ count: 0 });
      const inc = () => {
        state.count++;
      };
      const dec = () => {
        state.count--;
      };
      return { state, inc, dec };
    },
  };
</script>

<style scoped>
	* {
		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>`;

customElements
  .whenDefined("uce-template")
  .then((Template) =>
    document.body.insertAdjacentElement("beforeend", Template.from(template))
  );

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, c, 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: 4rem;
          height: 4rem;
          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", c(MyCounter));

Gallop

Use full power of web components

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

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

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>
  `;
});

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 "uce";

const MyCounter = ({ define }) => {
  define("my-counter", {
    attachShadow: { mode: "open" },
    props: { count: 0 },
    bound: ["inc", "dec"],
    inc() {
      this.count++;
      this.render();
    },
    dec() {
      this.count--;
      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>
      `;
    },
  });
};

customElements
  .whenDefined("uce-lib")
  .then(() => MyCounter(customElements.get("uce-lib")));

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, reactify } from "@lume/element";
import { html } from "@lume/element/dist/html";

export class MyCounter extends Element {
  count = 0;

  constructor() {
    super();
    reactify(this, ["count"]);
  }

  inc = () => (this.count += 1);
  dec = () => (this.count -= 1);

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

  css = /*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;
			outline: none;
			cursor: pointer;
		}
	`;
}

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 10 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>

Vue2 (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>

Vue3 `One Piece` (wrapped within a minimum Web Component layer)

🖖 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
import MyCounter from "./index.vue.js";
import { createApp } from "vue@next";
import styles from "./index.style0.css";

class wrapper extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.removeAttribute = this.removeAttribute.bind(this);
    this.shadowRoot.setAttribute = this.setAttribute.bind(this);
  }

  connectedCallback() {
    if (styles) this.shadowRoot.adoptedStyleSheets = [styles];
    createApp(MyCounter).mount(this.shadowRoot);
  }
}

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

Bundle Analysis

HTMLElement

No dependencies

Component size including library

Minified - Uncompressed 993
Minified + Gzip 505
Minified + Brotli 363

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/ Alpine JS)

Dependencies

alpine :

Component size including library

Minified - Uncompressed 27,184
Minified + Gzip 8,729
Minified + Brotli 7,814

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 : ^4.1.1

Component size including library

Minified - Uncompressed 12,033
Minified + Gzip 5,204
Minified + Brotli 4,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 } }),

...

HTMLElement (w/ Lit-html)

Dependencies

lit-html : ^1.3.0

Component size including library

Minified - Uncompressed 10,206
Minified + Gzip 3,628
Minified + Brotli 3,234

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/ Mithril)

Dependencies

mithril : ^2.0.4

Component size including library

Minified - Uncompressed 28,503
Minified + Gzip 10,248
Minified + Brotli 9,186

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 : ^2.1.2

Component size including library

Minified - Uncompressed 7,035
Minified + Gzip 3,217
Minified + Brotli 2,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 } }),

...

CanJS

Dependencies

can : ^6.6.0

Component size including library

Minified - Uncompressed 237,850
Minified + Gzip 64,497
Minified + Brotli 55,336

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 } }),

...

ElemX

Dependencies

elemx : ^0.2.2
mobx : ^5.15.7

Component size including library

Minified - Uncompressed 52,893
Minified + Gzip 15,731
Minified + Brotli 14,038

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 } }),

...

FAST

Dependencies

@microsoft/fast-element : ^0.19.0

Component size including library

Minified - Uncompressed 22,702
Minified + Gzip 7,108
Minified + Brotli 6,350

Composition

Open Bundle Visualizer

Compilation details

All Sources have been Compiled and/or Transpiled by WebComponents.dev compiler.

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 - Uncompressed 22,600
Minified + Gzip 9,063
Minified + Brotli 8,309

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 } }),

...

Joist

Dependencies

@joist/component : ^1.6.0

Component size including library

Minified - Uncompressed 5,340
Minified + Gzip 2,011
Minified + Brotli 1,758

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 } }),

...

Joist (with lit-html)

Dependencies

@joist/component : ^1.6.0
lit-html : ^1.3.0

Component size including library

Minified - Uncompressed 14,724
Minified + Gzip 5,127
Minified + Brotli 4,586

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 v2

Dependencies

lit-element : ^2.4.0

Component size including library

Minified - Uncompressed 20,953
Minified + Gzip 6,858
Minified + Brotli 6,175

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 v3 (next-major)

Dependencies

lit-element@next-major :

Component size including library

Minified - Uncompressed 13,339
Minified + Gzip 5,036
Minified + Brotli 4,509

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.11

Component size including library

Minified - Uncompressed 6,403
Minified + Gzip 2,629
Minified + Brotli 2,297

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 - Uncompressed 24,622
Minified + Gzip 8,412
Minified + Brotli 7,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 } }),

...

ReadyMade

Dependencies

@readymade/core : ^1.1.2

Component size including library

Minified - Uncompressed 6,199
Minified + Gzip 2,438
Minified + Brotli 2,108

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.3.0

Component size including library

Minified - Uncompressed 15,831
Minified + Gzip 5,353
Minified + Brotli 4,765

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 - Uncompressed 18,116
Minified + Gzip 6,416
Minified + Brotli 5,779

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 - Uncompressed 9,517
Minified + Gzip 3,678
Minified + Brotli 3,234

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-template

Dependencies

uce-template : ^0.2.4

Component size including library

Minified - Uncompressed 23,906
Minified + Gzip 9,873
Minified + Brotli 8,944

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 : ^1.4.2

Component size including library

Minified - Uncompressed 5,755
Minified + Gzip 2,761
Minified + Brotli 2,471

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.11.1

Component size including library

Minified - Uncompressed 10,989
Minified + Gzip 4,219
Minified + Brotli 3,771

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 - Uncompressed 15,597
Minified + Gzip 5,367
Minified + Brotli 4,794

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 : ^1.0.2

Component size including library

Minified - Uncompressed 21,379
Minified + Gzip 8,741
Minified + Brotli 7,936

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 : ^1.0.2

Component size including library

Minified - Uncompressed 21,502
Minified + Gzip 8,778
Minified + Brotli 7,964

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.3.1

Component size including library

Minified - Uncompressed 22,159
Minified + Gzip 7,224
Minified + Brotli 6,521

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 - Uncompressed 9,476
Minified + Gzip 3,855
Minified + Brotli 3,421

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 - Uncompressed 4,474
Minified + Gzip 1,984
Minified + Brotli 1,719

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.1

Component size including library

Minified - Uncompressed 12,441
Minified + Gzip 4,505
Minified + Brotli 4,026

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 : ^1.11.6

Component size including library

Minified - Uncompressed 9,909
Minified + Gzip 4,430
Minified + Brotli 4,006

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.8.2

Component size including library

Minified - Uncompressed 47,293
Minified + Gzip 14,938
Minified + Brotli 13,333

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.2.2

Component size including library

Minified - Uncompressed 19,782
Minified + Gzip 7,419
Minified + Brotli 6,726

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.20.2
solid-element : ^0.20.2

Component size including library

Minified - Uncompressed 12,720
Minified + Gzip 4,962
Minified + Brotli 4,415

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 : ^2.0.3

Component size including library

Minified - Uncompressed 12,824
Minified + Gzip 5,381
Minified + Brotli 4,858

Composition

Open Bundle Visualizer

Compilation details

All Sources have been compiled with Stencil CLI and settings:

// stencil.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',
  tsconfig: 'stencil.tsconfig.json',
  hashFileNames: false,
  plugins: [],
  outputTargets: [
    {
      type: 'dist-custom-elements-bundle',
    },
  ],
  hydratedFlag: null,
  extras: {
    cssVarsShim: false,
    dynamicImportShim: false,
    safari10: false,
    scriptDataOpts: false,
    shadowDomShim: false,
  },
};

Bundling details

Bundled by Stencil CLI

Angular 10 Elements

Dependencies

@angular/core : ^10.1.4
@angular/cli : ^10.1.4
@angular/common : ^10.1.4
@angular/compiler : ^10.1.4
@angular/compiler-cli : ^10.1.4
@angular-devkit/build-angular : ^0.1001.4
document-register-element : ^1.14.6
@angular/elements : ^10.1.4
@angular/platform-browser-dynamic : ^10.1.4
@angular/platform-browser : ^10.1.4

Component size including library

Minified - Uncompressed 254,287
Minified + Gzip 71,092
Minified + Brotli 61,062

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/011-angular-elements",
      "sourceRoot": "download/011-angular-elements/src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "aot": true,
            "outputPath": "download/011-angular-elements/dist",
            "index": "download/011-angular-elements/src/index.html",
            "main": "download/011-angular-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",
    "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/011-angular-elements/src/index.ts",
    "polyfills.ts"
  ]
}

Bundling details

Bundled with Angular CLI

Preact w/Class

Dependencies

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

Component size including library

Minified - Uncompressed 12,196
Minified + Gzip 4,958
Minified + Brotli 4,499

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 - Uncompressed 131,430
Minified + Gzip 40,920
Minified + Brotli 36,010

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,
  importHelpers: 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 - Uncompressed 131,306
Minified + Gzip 40,890
Minified + Brotli 35,955

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,
  importHelpers: 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.14.0
@riotjs/custom-elements : ^4.1.1

Component size including library

Minified - Uncompressed 18,557
Minified + Gzip 6,892
Minified + Brotli 6,224

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.29.0

Component size including library

Minified - Uncompressed 3,749
Minified + Gzip 1,795
Minified + Brotli 1,573

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 } }),

...

Vue2

Dependencies

vue : ^2.6.12
vue-custom-element : ^3.2.14

Component size including library

Minified - Uncompressed 76,856
Minified + Gzip 27,151
Minified + Brotli 24,307

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 } }),

...

Vue3 `One Piece`

Dependencies

vue@next :

Component size including library

Minified - Uncompressed 42,875
Minified + Gzip 17,005
Minified + Brotli 15,479

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.

Angular 10, CanJS, React and Vue2 libraries go above the 20KB mark.

Vue3 "One Piece" is delivering on promises with 30% improvement versus version 2.

ElemX comes with MobX, it's powerful but it's not light.

The incoming LitElement v3 (branch next-major) 30% improvements, on par with preact

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 !

Again here, Vue3 "One Piece" is delivering smaller compiled components and smaller runtime than Vue2

Performance

Performance of Parsing JavaScript + Create DOM tree

Benchmarked with Tachometer on Chrome Version 86.0.4240.75.

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.

Things that play a role in this performance figure are:

  • The size of the JavaScript to parse (more JavaScript = 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 43 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 Discrod or Twitter channels.

Thanks

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

Changes since June release

  • Full upgrade of the build
  • Upgrade of all libraries including Angular 10 and Stencil 2
  • Improve the vanilla HTMLElement implementation
  • Added FAST element
  • Added Joist framework
  • Added ElemX
  • Added Mithril.js
  • Added Readymade
  • Added uce-template
  • Added AlpineJS
  • Added lit-html v2 (next-major branch)
  • Added lit-element v3 (next-major branch)
  • Added Vue 3 "One Piece" with a Minimum Viable Web Component wrapper
  • Stencil compiler target to Type: dist-custom-elements-bundle

Brought to you by Twitter Discord