Making JavaScript fun (or, how I learned to stop worrying and love web components)

You're viewing a Gemini article on the web

It's much better to view it on Gemini!

I am not a fan of JavaScript or developing for the web in general. I've been pretty clear on this opinion before. It feels like web development these days is so focused around making SPA-like experiences with little regard for performance or accessibility, and that's a world I simply do not want to be a part of.

However, in my day job I face a unique challenge. I am generally not working with structured data at all, but instead with content written by technical writers. This means that my solutions have to work no matter what the author decides to throw at it. Working with Astro components makes the content part of this easy enough; you can simply slot the content in and your CSS/other Astro components just work. However, what about interactivity?

Astro grants the developer the ability to write JavaScript in a component as a script tag, and the system is smart enough to scope that script and deduplicate it so that you only end up with a single payload regardless of how many instances of a component exist on the page. This is really cool, but it fundamentally doesn't fix the issue of writing JavaScript being annoying and tedious. Having to structure your element HTML and then needing to use selectors and loops to make things interactive is still a pain.

As a brief aside, recent advancements in web technologies like the Popover API have reduced the need for some of the more tedious and common JavaScript use cases. Long may this continue. I never want to write a JavaScript toggle to "open" an element again.

Popover API (MDN)

Recently, I've been writing a lot more web components. This much maligned browser feature might just be the most important thing to come out of modern web development. Now, rather than needing to select existing elements on the page and use procedural JavaScript to enhance them with intersection observers and event listeners, you can use a custom tag to encapsulate styles and behavior. You can even use a shadow DOM to render contents out on-the-fly in an efficient manner. It's very cool tech, and I wish that more people thought about using it when they reach for React yet again.

Web components (MDN)

For some use cases, this shadow DOM interaction makes a lot of sense. I have created a search application which receives data from Algolia and has to present said data in a series of components representing controls and results. In this case, the shadow DOM is essential because I don't know what will be on the page at any one time, so the whole thing has to re-render upon receiving new data. The shadow DOM makes this safe and efficient.

Where my thinking was wrong, however, was in using these components for known data. For example, let's say I have a definite structure I want to apply to a set of data. If a writer inputs the following to create an accordion (collapsible section), for example:

<Accordion title="Open me">
	This is markdown content. I just wanted to hide it.
</Accordion>

I could of course create a component like this:

<accordion-wc>
	<slot />
</accordion-wc>

Then I can make use of the shadow DOM to strongly encapsulate everything and create a new element with all the interactivity I want and the content slotted within. However, this creates a couple of issues relating to accessibilty and performance. Making use of the shadow DOM is great when things need to re-render constantly, but in an otherwise static page its slowness is apparent. Shadow DOM hydration is always going to be slower than the initial page load because the browser needs to register the tag and construct each instance. This means there is always a visual flicker. Fortunately, the content stays on the page because it's slotted, but it's still not ideal.

However, the shadow DOM is not a hard requirement at all. It's perfectly reasonable to instead write out your entire template in Astro so that it arrives on the client fully styled and populated, then wrap the template in a web component that adds any listeners/interactivity to the template elements once the page loads. This gives you the best of both worlds: you can tightly encapsulte your interactivity using a custom class, and you deliver content to users as quickly as possible.

This particular use case highlights to me that the React way of thinking (heavy shadow/virtual DOM usage and JavaScript for everything) is really hurting web development overall. Most of the content I've read about web components focuses almost entirely on how they can be used to replicate the tight scoping and encapsulation of frameworks like React using the shadow DOM, while ignoring the very real use case of simply adding small amounts of interactivity to extant light DOM elements. These days, I'm finding that using JavaScript inline at all is largely unnecessary because web components and light DOM-focused interactivity give me everything I need with no appreciable performance impact.

Tell me what you think.