Web components
MOVING FROM REACT TO LIT REQUIRES A SHIFT IN ATTITUDE
React is the number one most popular web application development framework, hands down, with twice the user base of its next competitor, Angular. My framework of choice is LitElement, which doesn't even appear on some of these charts.
According to Stack Overflow, 55% of developer "admire" Lit, and less than 1% get to work in it. I, fortunately, get to work in Lit, but after four years of React experience I've discovered that Lit is so very different from React, Angular, and the rest that it takes a heavy mental shift to work in it.
I work professionally on a medium-sized web application written in Lit that orchestrates and administers single-sign-on for small-to-medium businesses. I really like Lit, and working on this thing has been like being thrown into the deep end of the pool. There are two aspects of Lit that make it really different from the others and can make it hard for some developers to make the mental shift.
Lit is HTML in a way that React is exactly not
Lit is built on the Web Component Standards. A web component isn't just a "component" in the React or Angular sense; a web component literally becomes a new HTML tag for the duration of a session. This creates a very stark and different developers experience. Consider this React code:
<Login user={user}></Login>
Invoked like this, you will not see the string <Login>
in the resulting page's source code or
Inspect
window, because React is a domain specific language for generating HTML. Instead, the
Login
component itself will eventually, somewhere down the line, generate HTML using the
traditional toolkit of <div>
, <label>
, <span>
and <input>
tags.
This is because React (and JSX) is an XML-based domain specific language (DSL) for generating HTML with some associated behaviors. It's not itself HTML.
The equivalent in Lit looks almost the same:
<user-login user=${user}></user-login>
However, in this case, the term <user-login>
will appear in your page's source code. And it
will appear in the Inspect
window when you're trying to debug some CSS issue. Lit creates new
HTML tags dynamically and registers them with the browser as valid tags! While Firefox had that
capability since the early 2000's, the other browsers only standardized on the API for it in 2017,
six years after React was released. The life cycle of
all web components (including the traditional ones) is the same, and hooking into that allows
developers to create new HTML tags with impressive and comprehensive power.
This can create a bit of anxiety in the React developer: the functionality they want to put onto the page is now cluttered with these markers indicating where that functionality came from. To the React developer, the React Toolkit does a good job of separating the HTML and React into different, but mirror-image, trees of information; in Lit, those trees are the same tree.
Lit is about isolation and protection first
Lit's use of the Web Component Standard has a second and even more difficult issue to handle: the
:host
barrier. Every registered custom web component is a host for the behavior it encapsulates,
and so web components use something called the ShadowDOM to isolate that behavior from its
surroundings. This has two serious consequences:
What happens inside a web component usually stays inside a web component.
Events that happen inside a web component do not automatically bubble up outside of it. If you want
your component to just define the look and feel of an <input>
or such, you're also going to have
to wrap all of the event handling as well, at least to forward it out of the host element. The good
news is that you'll probably only have to do it once; the bad news is that you'll have to do it
all.
On the other hand, since the root handle of all web components is the :host
element, and that
element is technically outside the ShadowDOM of its contents, it can dispatchEvent()
all it wants.
Browsers now support a CustomEvent
protocol that can be used to pass rich, object-heavy events up
to parent elements, and those elements can respond by changing the arguments passed to Lit elements,
causing a re-render, the one-way cycle that we've come to expect from React and other frameworks.
Inside a web component is another world, stylistically speaking
The component's contents have a unique and independent style sheet that's unrelated to any containing HTML context. The CSS you put on your page will have no effect on the components. (This is not strictly true; there are two caveats.)
In order to have a web component honor your style, you'll have to re-import it into the component's
ShadowDOM. It is possible to register a style sheet as a single, referencable entity in the browser
using the constructedStylesheet
API and share it among those components, but this is still a
separate and independent copy of your style sheet, and managing those constructed stylesheets is
still something of a challenge.
The good news is that there are two ways to allow other people to style a web component:
- You can define your layouts and colors in terms of CSS Custom
Properties; these do get past the
:host
barrier, and components can pick them up and apply them to their CSS when those components are first registered with the browser. - You can mark parts of the component with the
part="some-name"
property, and the::part(some-name)
CSS syntax will allow outside CSS to apply specifically to those tags with the defined part name. The::part()
syntax does not cascade, however.my-element::part(tab) a
will not style any anchors even if it has a parent ofpart="tab"
.
Lessons learned
The first lesson of Lit is this: don't be afraid of the dozens of new tags your content may generate. That's what browsers are best at: parsing and rendering tags. But generating new tags means new and unique behaviors.
The second lesson: Have a plan. Know what you're building and why. Sketch it on paper first. Do designs in Penpot (or Figma, I guess, if you're rich... but if you are, support Penpot instead). Sketch the information flows and come up with a way to control them.
The third lesson: Control your themes with CSS Custom Properties. That's the safest way to dictate to all the components you use (and write) have the right colors and sizes.
The fourth lesson: Have restraint. Don't go trying to replace <table>
on your first go with it.
Tables and so forth have their own ways of doing things, and if you don't understand them you're
likely to spend a lot of time trying to recreate all of that functionality by yourself. Let the
browser do what the browser does best. If you must have a dynamic <my-table>
component, figure out
how to populate the rows and cells, but dictate those rows and cells within the table hosted by
<my-table>
.
Lit is cool
I've been working in Lit professionally for several months now, and it's been a hell of a ride. Lit is incredibly more powerful than React, and at the same time is hampered by a lack of the kind of development attention React has gotten through the years. If you're developing mid-sized applications, especially those destined to be progressive web apps, consider using Lit for your next project.
After all, React is a framework owned by Facebook. Lit's Web Components are an international standard.