24
"ππ’ππ‘π‘ππ¨π©ππππ£π" : making a Web Component 29% smaller
The WebComponents.Dev site blogs about 51 ways/languages to make a
<my-counter>
Web ComponentNative code ranks first in the File Size Ranking

But... I am totally against comparison with Svelte, because the Svelte compiler optimizes code
So if I "play compiler"
That native HTMLElement code can/may be optimized
(and so can all other source versions)
(and so can all other source versions)
gzip | |
---|---|
original by WebComponents DEV | 476 B* |
Svelte | 1884 B |
refactored | 355 B - savings: 25% |
optimized | 339 B - savings: 29% |
*) The Web Components DEV site says the file is 505 Bytes,
copied to GitHub.io my
copied to GitHub.io my
<file-size>
Web Component reports it is 476 Bytes.See
JavaScript
Tab in above JSFiddle.Template literals are great, but they suck up bytes, as meaningless white space and \n
newlines are still included in the minified file.
no need for a createElement('template')
when we only want the innerHTML once
template/content should not be added in the connectedCallback (as it can run multiple times)
super()
sets and returns the this scope
attachShadow()
sets and returns this.shadowRoot
so everything can be chained:
constructor() {
super()
.attachShadow({ mode: 'open' })
.innerHTML = "<style>*{font-size:200%}...
no need for a MyCounter class definition when it is used only once
customElements.define('my-counter', class extends HTMLElement {}
Nearly all of the 51 examples use inline event handlers (notation).
render() {
return html`
<button @click="${this.dec}">-</button>
<span>${this.count}</span>
<button @click="${this.inc}">+</button>
`;
}
Then we can do that as well
- we have to add extra code to find the
inc()
anddec()
methods on the element (which libraries do for you under the hood) - the
id
references on the buttons are no longer needed
<button onclick="this.getRootNode().host.inc()">
<button onclick="this.getRootNode().host.dec()">
The Component uses shadowRoot to encapsulate styles and content. The id
on <span id="count">
is not required because we can target the only <span>
that exists in shadowDOM
All the refactored connectedCallback
does is set the span innerHTML to 0
connectedCallback() {
this.update(this.count);
}
Set the default 0 in HTML, and the connectedCallback is no longer required
"<span>0</span>"+
remove not required white space and ; from CSS
remove not required quotes from HTML attributes, because the Browser will add them
<button onclick=this.getRootNode().host.inc()>
<button onclick=this.getRootNode().host.dec()>
customElements.define("my-counter", class extends HTMLElement {
constructor() {
super()
.attachShadow({ mode: "open" })
.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 onclick=this.getRootNode().host.dec()>-</button>" +
"<span>0</span>" +
"<button onclick=this.getRootNode().host.inc()>+</button>";
this.count = 0;
}
inc() {
this.update(++this.count);
}
dec() {
this.update(--this.count);
}
update(count) {
this.shadowRoot.querySelector("span").innerHTML = count;
}
}
);
This Component can be made better and even smaller
The inc
, dec
and update
methods are not required, when count
is made a getter/setter
DRY (Don't Repeated Yourself) is great from a code maintenance Point-of-View. But from a delivery and performance PoV you do not want to be DRY; GZip loves repetitions
* {font-size:200%}
is applied to 2 elements only (button and span)
Setting font-size:200%
on both elements creates a larger file, but a smaller GZipped file!
(And the CSS parser has less work to do)
.count-- >
needs that extra space, to close the onclick
definition, or the minifier will add an -
escape code, adding 4 bytes.
<span>
can be replaced with <p>
no this.count = 0;
required because <p>0</p>
is the state
replacing seagreen
and white
with shorter #xxx
notation doesn't save extra bytes in this case, because the # doesn't exist yet in the code, thus requires extra GZip encoding bits.
customElements.define(
"my-counter",
class extends HTMLElement {
constructor() {
super().attachShadow({
mode: "open",
}).innerHTML =
"<style>" +
"p{font-size:200%;width:4rem;display:inline-block;text-align:center}" +
"button{font-size:200%;width:4rem;height:4rem;border:none;border-radius:10px;background:seagreen;color:white}" +
"</style>" +
"<button onclick=this.getRootNode().host.count-- >-</button>" +
"<p>0</p>" +
"<button onclick=this.getRootNode().host.count++>+</button>";
}
set count(p) {
this.shadowRoot.querySelector("p").innerHTML = p;
}
get count() {
return ~~this.shadowRoot.querySelector("p").innerHTML;
}
}
);
Note: You can save 6 more bytes using INline styles
but code maintainability suffers:
but code maintainability suffers:
.innerHTML =
"<button style=font-size:200%;width:4rem;height:4rem;border:none;border-radius:10px;background:seagreen;color:white onclick=this.getRootNode().host.count-- >-</button>" +
"<p style=font-size:200%;width:4rem;display:inline-block;text-align:center>0</p>" +
"<button style=font-size:200%;width:4rem;height:4rem;border:none;border-radius:10px;background:seagreen;color:white onclick=this.getRootNode().host.count++>+</button>"
Refactoring Web Components makes them smaller and better.
gzip | |
---|---|
original by WebComponents.DEV | 476 B* |
Svelte | 1884 B |
refactored | 355 B - savings: 25% |
optimized | 339 B - savings: 29% |
24