"𝙎𝙢𝙖𝙡𝙡𝙚𝙨𝙩𝙚𝙙𝙞𝙣𝙜" : making a Web Component 29% smaller
Danny Engelman
Posted on July 11, 2021
The WebComponents.Dev site blogs about 51 ways/languages to make a <my-counter>
Web Component
Native 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)
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 <file-size>
Web Component reports it is 476 Bytes.
Original <my-counter> = 476 Bytes:
Refactor code
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 oncetemplate/content should not be added in the connectedCallback (as it can run multiple times)
super()
sets and returns the this scopeattachShadow()
sets and returnsthis.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()">
- we have to add extra code to find the
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()>
Refactored code = 355 Bytes:
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;
}
}
);
Optimized code = 339 Bytes:
This Component can be made better and even smaller
The
inc
,dec
andupdate
methods are not required, whencount
is made a getter/setterDRY (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)
Settingfont-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 theonclick
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 statereplacing
seagreen
andwhite
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:
.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>"
smallesteding-ed Conclusion
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% |
Posted on July 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.