Assessing Web Component Libraries
I’ve started on a project recently and realized it’s a perfect use case for web components. It’s an Eleventy site that’s primarily markdown, so dropping custom elements into the markdown is just too easy. It requires basically no new plumbing beyond the bare bones esbuild setup I’ve already got in place.
This is my first time in web components doing anything more involved than a quick dabble, so I ended up spending the last week exploring some of the various web-component-based framework option out there, and boy do I have opinions.
General thoughts on WC and frameworks ¶
Before I dive into the various frameworks I assessed, I want to share some general thoughts on web components that color the impressions I had.
First of all, I don’t want Shadow DOM except in a few very specific circumstances. So many in the WC community make a huge deal about “scoped styles” provided by Shadow DOM. Let me put it plainly: this is a flat out lie, and probably the biggest thing that put me off from web components for a long time.
Shadow DOM does not provide scoped styles; it provides isolated styles. That’s a very different thing for a very different purpose than every day application development. Thankfully, others in the community are starting to come around on this. The thinking seems to be shifting here, For me, the realization that I can use custom elements all in the light DOM has finally made web components a practical option.
The second thing about vanilla web components is that they, by themselves, aren’t a substitute for traditional JS web frameworks. They are just too low level, and are missing way too many essential quality of life improvements found in those frameworks.
Thankfully, there are some very lightweight frameworks built on native web components that aim to ease development. I’m still a bit unsure whether these are suitable replacements for more broadly used frameworks like React when it comes to full-featured, complex apps that are on the far end of the spectrum away from static web pages — i.e. true applications — but these certainly can have a place near the middle of that spectrum.
Lit ¶
Lit is the most well known of these frameworks. It’s got the longest history — it’s a successor to Polymer, which was the first (very polyfill-dependent) big WC framework.
My first impressions were good here, though I also had the name “Lit HTML” in my head and it took a while to piece together that Lit is the framework and lit-html is the library it uses internally, which parses HTML template strings in order to build a DOM representation.
If you really want, you can build your own framework on top of lit-html. I entertained that idea for a bit, but eventually decided that was signing up for a whole lot of work when I really just wanted to get going on my actual project.
Defining a custom element comes down to creating a class that extends
LitElement, which itself extends the native HTMLElement.
I like that this means if I need to get to the bare metal and do something
that’s not explicitly supported by Lit, I can.
The inner markup is defined via a template literal in a render function,
which looks something like this:
class MyComponent extends LitElement {
render() {
return html`<div>Component content here</div>`;
}
}
The one thing I really don’t like about Lit is it defaults to putting every
component in Shadow DOM.
Opting out requires overriding the createRenderRoot function in each and
every one of your components.
I really wish this was doable with a simple flag to the constructor or
something instead, or even better, a global config option that made it so
Shadow DOM was opt-in rather than opt-out in each component.
My big stumbling block here came when I needed to build a component that used
<slot>s but I wanted to stay in the Light DOM.
This lead me to the next framework.
Enhance ¶
Enhance aims to be a full-featured web framework.
It’s got its own build pipeline and has out-of-the-box
SSR,
which is really impressive.
However, you don’t have to do all that and can instead just use their
CustomElement class to to build custom elements in the site you’ve already got,
which suits my needs more right now.
I like that flexibility.
It also supports <slot> elements in Light DOM components, which is also
very nice.
Similar to Lit, you create a custom element by extending their CustomElement
class, which is extends HTMLElement.
It also uses a template literal to render component contents.
As I experimented with Enhance a bit, though, some of its opinions started
to rub me the wrong way.
The main CustomElement class doesn’t do anything to wire up event handlers
for you;
you have to manually bind them to the class,
manually invoke addEventListener,
and manually evoke removeEventListener for each one.
There is an odd way around this,
where, instead of extending the base class,
you define your entire component in an object literal then pass that to
an enhance() function which builds the class for you.
I can’t entirely put my finger on why, but this just feels wrong to me.
I shouldn’t have to be an extra layer removed from the abstraction for what I
consider a bare bones essential feature of a modern framework.
The other thing I didn’t like was how strictly prescriptive the framework
is regarding file structure.
This doesn’t matter if you’re only using their CustomElement class in your
own build pipeline,
but as I read through the docs, this left a bad taste in my mouth.
Enhance really pitches the idea that it’s a full-fledged application framework,
but this convention feels much more in line with a static site generator.
It gives me the impression it’s trying a little too hard to be both,
and I’m cautious about what compromises they’re going to have to make to go
down both paths at the same time.
This is probably a fantastic framework for some folks, but eventually I decided there was just too much I wasn’t a fan of.
WebC ¶
I saw this listed alongside Lit and Enhance around the web, so I looked it up. This was created by Zach Leatherman as what I guess I’d describe as a sub-project of Eleventy. The WebC docs seem to live on the Eleventy website.
WebC is not written in JavaScript. It’s written in HTML, with a series of custom directive-like attributes, which look something like:
<div webc:for="(myItem, myIndex) of [1, 2, 3]" @text="myIndex"></div>
or:
<syntax-highlight
language="js"
webc:import="npm:@11ty/eleventy-plugin-syntaxhighlight">
function myFunction() { return true; }
</syntax-highlight>
This to me is an immediate non-starter. Sure, this is great for some small, light-touch stuff, but I need to build my components in a Turing-complete language. I don’t want to deal with the headache that inevitably emerges the moment I need to do something that doesn’t have a corresponding, already-supported directive. I’ve been down that road with too many languages in the past.
It didn’t take long for me to toss this one aside. Sorry Zach.
Final thoughts ¶
Evaluating these were hard. I’ve been developing primarily in React for over a decade, so I have a lot of habits and opinions that have been influenced by that framework.
I know web components are a whole different thing, so I tried my best to come at them with an open mind. It needs to be okay for a new framework to not look like the old one, so I often had to stop and sort whether I disliked something on merit versus disliking it because it’s not what I’m used to.
I eventually landed back on Lit for my current project.
I even devised a way
to use <slot>s in a Light DOM custom element.
I think I’m happy with it. We’ll see once I’m a lot further down the road.
