JavaScript is not the problem
Tags: javascript, webdev, webcomponents, performance
Published: 2024-05-16 12:27:47

The other day I was asked to examine a performance issue on an Angular 11 app. Simply typing in an `<input>` field was very sluggish, and CPU load skyrocketed at 100% from the moment a key is pressed to ~3 seconds after the last is released.

Angular should be the culprit, right? A silly crafted module that caused a loop that gets broken by a "Maximum call stack size exceeded" error or similar.

But the main thread wasn't occupied by script execution. It was a _rendering_ issue.

It turns out that _this_ CSS rule was the cause instead:

```css
.form-control-extra.is-invalid,
.form-control-extra:invalid::placeholder,
.form-control.is-invalid,
.was-validated .form-control-extra:invalid,
.was-validated .form-control:invalid {
  border-color: rgb(43, 239, 117);
  color: rgb(43, 239, 117);
  padding-right: 4px;
  background-image: none;
  background-repeat: no-repeat;
  background-position: right calc(0.1875rem + 0.375em) center;
  background-size: calc(0.375rem + 0.75em) calc(0.375rem + 0.75em);
}
```

Simply removing it from its stylesheet would iron things out. And that's not even the plot twist! In fact, _that rule wasn't even applied on that page!_

...

🦗🦗🦗

...

To this day, I still have no idea about the _real_ cause of the problem. It was tested on Chrome only, as it's an app for internal use, and Chrome is the only supported browser. The problem isn't easily reproducible, but I suspect it's a bug on the Blink engine.

My conclusion was to _not_ ship a 200+ KB blanket CSS file with 2000+ rules when only 2% of them are actually used, and partially.

## What's this about?

On May 14th, Chris Ferdinandi published his article [The wrong way to do Web Components](https://gomakethings.com/the-wrong-way-to-do-web-components/) on his blog. Chris is incredibly knowledgeable about web development, and an excellent teacher that gives a lot of great tips.

But indeed he has peculiar ideas about _how_ the web should be developed.

The article above starts with a clarification:

> There’s not really a “wrong” way to build Web Components. But there are some approaches that are, in my opinion, antithetical to the spirit of them, and maybe miss the point of what makes them so great.

From my experience, I've always struggled to precisely outline what's the "spirit" of Web Components. Since their original conception in 2011 by Alex Russell, many things have changed. I've used them for the following goals:

* building UI Kits (probably their best use case);
* creating easily transportable widgets;
* wrapping branches of legacy applications;
* developing "framework-less" web applications;
* encapsulating micro-frontends.

But if Chris has a clearer idea, it's worth listening to him.

The article continues mentioning Web Awesome, the "next iteration" of [Shoelace](https://shoelace.style/) by the same author @claviska Cory LaViska. Then it highlights:

> **But here’s the problem: Shoelace is not a progressive enhancement library.**

... No, it's not indeed. But what _is_ the problem, exactly?

> Far too much of the UI renders nothing at all without JavaScript. Other stuff starts out as useless junk.
>
> That `<sl-button>`? Without JS, it’s just text. You can focus on it. You can’t interact with it. Why do I need JavaScript to render a button!?!

So the problem is... that it _uses JavaScript_, then?

Of course, a button is one of the basic tiles of the Web. We don't _need_ JavaScript to have a working button. We can very well choose to use native `<button>`s.

You need some JavaScript if you want that button to:

* have a consistent, encapsulated and controlled style;
* have a compact, declarative interface like `<sl-button loading>` to express complex states;
* emit custom meaningful events.

(Worth mentioning: Shoelace's buttons _could_ have been a progressive enhancement over native buttons - in the form of `<button is="sl-button">` - if built-in extensions were supported in all browsers instead of facing the [obstinate opposition](https://github.com/WebKit/standards-positions/issues/97) from Safari's team, but that's another story.)

Moreover, there are very common UI patterns that _require_ JavaScript to work correctly, at least if you want to follow to [WCAG's directions](https://www.w3.org/WAI/ARIA/apg/patterns/) to create accessible components. You might also notice that all WCAG's examples use vanilla HTML, CSS and JavaScript, no frameworks, no libraries whatsoever.

You want tabs? You need JavaScript. You want a combobox? Only with JavaScript. A menu button? JavaScript.

It's great that Chris' blog can work without JavaScript. But he surely had to avoid those patterns. He probably didn't need them anyway, but not every web page is Chris' blog.

## Must all the web work without JavaScript?

If we apply that concept, it's clear that we should make without all those patterns, many of which are pretty common to web users. But that's not just that.

Many UI patterns emerged from decades of web UI evolution. Take [accordions](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/), for example. Or [modal dialogs](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/). Or [tooltips](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/). All of them have been implemented with JavaScript libraries for a long time, and only recently new standard elements have been introduced and supported by browsers to reproduce the most common use cases.

The matter here is that, if the whole world developed their web sites and applications in a manner that they could *always* work without JavaScript, they could have chosen one of the following:

* implement new UI patterns, but always providing alternative fallbacks that could work without JavaScript;
* forget about new patterns and use the JavaScript-less alternative right away.

I'm inclined to think that most customers would have chosen the second alternative, as it would have meant _paying less_.  This would have meant that those new patterns would have had a much, _much_ harder time emerging, so maybe today we wouldn't even have `<dialog>` as we have today. And probably not even `XHRHttpRequest`, let alone `fetch`. It would have been a completely different web.

## Ok, but now we have all we need, right?

Not really. Several patterns still require JavaScript, as there's no native equivalent yet. Think about carousels, for example. Or, yet again, tabbed content. Or tooltips (we may be close to achieve them, thanks to [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) and [Anchor Positioning API](https://developer.chrome.com/blog/anchor-positioning-api)).

So, yeah, we probably still need JavaScript. Once the JavaScript usage has been legitimized, the next step is to use it *wisely*.

With this outcome, flat-out suggesting that _everything_ we do with JavaScript should be progressive enhancement, relegating the role of Web Components as cute wrappers around native elements, sounds frankly narrow minded to me.

## What's the fuss about JavaScript anyway?

The JavaScript part of a web page is a something that often blamed for poor site performances. And with a reason, too!

![Chart that shows the growth of the the median total data transferred for web pages, from 0.5 MB in 2011 to 2.5 MB in 2024. Source: httparchive.org](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ji78cgbgv8p3lpd49rzc.png)

While [discussing with Chris](https://mastodon.social/@MaxArt2501/112441763627524438) on Mastodon, he linked [this scheme](https://www.kryogenix.org/code/browser/everyonehasjs.html) by Stuart Langridge. It highlights all the possible problems that originates from having some JavaScript in the page.

Most of those points are valid indeed, but also apply to other content: CSS, fonts, images, even the HTML that you have to load right at the start. Depending on the role, the page could end up broken as much.

Some other points are indeed specific to JavaScript and, most of all, are mostly _avoidable_. Because, after you optimize and lazy-load them you can't take away much more from images and fonts, but there's a lot to be done for JavaScript. It's not rare to download something like 5-6 MB of script behemoths nowadays.

And I'm not saying that most of the code isn't used (even though it very well could be!), just that loading, parsing and running it can be deferred at a later time, when it's actually necessary.

So, what about Shoelace? Does it load a multi-megabyte script for its components? Does it block the main thread while doing so?

Not at all. Shoelace is comprised by several small chunks, the result of a finely grained code splitting. An autoloader loads them only when necessary (it uses a `MutationObserver` internally), but you can opt for a full-library loaded or even cherry-pick only the components you need. (The full library is ~300 kB compressed with Brotli, by the way.)

![Screenshot of the network waterfall of Shoelace's components, from Shoelace's site. Several .js chunks are loaded, and most of them are under 1 kB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qeh46k8l9aic52pe137v.png)

In short, **Shoelace would be the least of my concerns when it comes to performance issues**.

## So what are the *real* concerns?

The usual answer when it comes to development: it depends.

Optimizing static assets is trivial nowadays and fundamental to modern web development. I don't want to see a 3 MB animated .gif when I could do much better with a 60 kB .webm video, thank you.

JavaScript bundles would probably be my next target. There are so many things to look into. Major performance offenders include: analytics libraries, monolithic bundles, lack of code splitting, minification, compression.

I like JavaScript as a language and an ecosystem, but I try to write as little JavaScript as possible, delegating the work to HTML and CSS (when working on the front-end) when possible.

But I'd never dream to impose to use JavaScript for progressive enhancement only. There are applications that far too complex to rely on what HTML and CSS can offer alone, and you don't always have the luxury to dictate the back-end to do everything that's needed.

Moreover, as we've seen at the beginning of this article, the problems could be caused by something completely different and unrelated. But, as much as [CSS Naked Day exists](https://css-naked-day.github.io/), you won't hear many asking that every website should work without CSS either.

(And yes, that would raise accessibility concerns as well.)

In the end: JavaScript, *per se*, is *not* the problem. Its excessive use is.

## Addendum: on Shadow DOM

The next day, Chris [clarified even further](https://gomakethings.com/but-what-about-the-shadow-dom/) w.r.t. Web Components and the Shadow DOM:

> [...] the shadow DOM, in my opinion, is an anti-pattern.
> 
> It provides little benefit, with lots of cost. It’s harder to work with. It’s harder to style. It can break accessibility, depending on how it’s implemented. It *requires* JavaScript to work.

The point on accessibility is, unfortunately, correct. The main problem nowadays is [cross-root ARIA element referencing](https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/), which is currently discussed but still not possible. So everything should be carefully engineered in order to not break accessibility.

But if Chris cares about accessibility, he should alleviate his resentment against JavaScript as well.

Is Shadow DOM harder to work with? Sure. Just like any new API, it brings several new concepts to the table as well, especially when it comes to encapsulation.

You're not *forced* to use the Shadow DOM, if you don't feel confident. But it's not like it's been conceived on a whim, without any real reason, like Chris seems to suggest.

I also find *much easier* to style custom elements *with* a Shadow DOM. It's basically a miniature DOM where the cascade is much more manageable, and I rarely even use classes there. The *real* challenge, in my experience, is creating a way to permit style variations from the consumer without allowing to mangle the component's layout and overall meaning, which is usually a thing you want e.g. for a design system.

Chris Ferdinandi's approach to Web Components and web development in general is legitimate and based on concrete motivations, which are weighted by his own preferences. On the other hand, his usage of terms like "wrong way" or "anti-pattern" expresses way more than his personal opinions and leans towards unwarranted absolutism.

In short: always take your context and use cases into consideration before rejecting a pattern, library, framework or any other technology.