All the Ways to Make a Web Component - June 2020 Update
source link: https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
All the Ways to Make a Web Component - October 2020 Update
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.devconst 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);
Bundle Analysis
HTMLElement
No dependencies
Component size including library
Minified - Uncompressed | 993 |
Minified + Gzip | 505 |
Minified + Brotli | 363 |
Composition
Open Bundle VisualizerCompilation 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 } }),
...
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.
- @Uppercod creator of Atomico
- @adamdbradley creator of Stencil
- @matthewcp creator of Haunted
- @eavichay creator of SlimJs, Ottavino and Neow (in alpha)
- @WebReflection creator of Heresy, HyperHTML, lighterhtml, uhtml, uce and uce-template
- @smalluban creator of Hybrids
- @_developit creator of Preact
- @KevinJHill @diervo @pmdartus of the LWC team
- @Rich_Harris representing Svelte
- @justinfagnani from lit-html and LitElement team
- @RyanCarniato creator of Solid
- @trusktr creator of @lume/element
- @luwes creator of Swiss
- @Tarnishablec creator of Gallop
- @EisenbergEffect on team FAST
- @dee_bloo creator of Joist
- Tabs from @lion/tabs - Thanks @dakmor and ING
- Charts library by Charts.js
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 byFollow us
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK