I know you are thinking, yet another article in this back and forth between Web Component proponents and detractors. I just feel the discussion tends to stray from the more fundamental issues.
Some background. I've been using Web Components in production for 7 years. I've developed libraries based on them, written more than a couple of polyfills for the Shadow DOM and largely have been an advocate for them. I work at a startup that has been trying to find the right time to move out of our MVP application and build things better this time. I was positive for the past 2 years that we'd continue to use Web Components and as browsers caught up to standards we'd finally hit that golden time for development. Instead, when the time came although we started with Web Components their role was quickly mitigated and finally completely removed.
So I want to impress this isn't coming from an "us vs them" mentality, but rather how what I've learned in the past couple years has changed my perspective considerably. I'm not going to focus on what some people consider mismanagement or the disagreements between vendors. I actually believe they are just the wrong solution for the problem. Or the problem as it has been presented.
Components !== Web Components
The collection of standards(Custom Elements, HTML Templates, Shadow DOM, and formerly HTML Imports) put together to form Web Components on the surface seem like they could be used to replace your favourite library or framework. But they are not an advanced templating solution. They don't improve your ability to render or update the DOM. They don't manage higher-level concerns for you like state management.
At one point there were parties trying to extend the standards to make them more library-like. I think at this point this is well understood this would not be a great idea. There are too many opinions here and being too ambitious in scope would only serve to alienate. I would suggest even the current standards were too ambitious when considering the Shadow DOM. Yet the Shadow DOM solves 2 essential pieces to the problem in style isolation and inserting (slotting) child elements.
However, those facts still don't lend particularly well to the newer rhetoric. "Use Web Components with your favourite library". All but the simplest Web Components are an investment in JS bundle size, performance loss, and new complexity.
Is it any surprise that there is friction with the UI library and frameworks? Libraries that were very Web Components forward, like Svelte or Vue, have backed off a bit. The biggest problem Web Components are hitting now is that the JS library ecosystem has grown up. In many cases, it is no longer just about progressive enhancement. To create a user or development experience, like that of application requires looking at things more holistically. The lifecycle of a modern JS library transcends the DOM lifecycle. Components exist before they are ever rendered, and things like the slotting of children are something that they desire the utmost control over.
See the problem is by the time something is added to the DOM it is too late. You've paid the cost. When libraries are using Virtual DOM representations or even in-memory trees this is very limiting. It is very common in libraries to lazily evaluate slots or
props.children. Things like Suspense or even windowing (only drawing what is on screen) don't want to take the hit at render time. Obviously you can hoist state out of your Web Components and not rely on connected callbacks, but it's not natural. None of this is Web Component's fault. It's simply they are built with the DOM and live by the DOM. These are the events and interfaces we deal with.
Component's asynchronous timing with upgrading and connected callbacks are also awkward for libraries that synchronously render. It can make things like passing a Context API through difficult. Sure Web Components can have their own DI system but trying to use your library as intended can be hard. Each Web Component an island. While encapsulated and modular they serve as a boundary we have to cross constantly if used in great number.
Where does that leave us?
I'm not completely sure. Progressive enhancements like
<a is="my-button" />, 3rd party widgets, and micro-frontends all seem reasonable. I'd still use Web Components as an alternative to packaging a JS SDK, or as a reasonable way to isolate development on a single page.
But Web Components as a framework or as a way of augmenting my applications within my framework of choice? It's hard. Although I do not like constantly re-inventing the wheel, knowing that an implementation in my framework of choice will be smaller, faster, more consistent will always be nagging. The hope of future-proofing is not a guarantee when libraries are pushing the boundaries of the web application experience in a way that doesn't see these as necessary. I'd love to lend towards the future of the platform, but I'm not convinced this is it anymore.
It's not that Web Components failing to be what they are meant to. Even if they are in a couple places many of those can be addressed. It comes down to their fundamental nature. How could they be anything different? They are just DOM elements. It's that maybe they aren't the right abstraction for the problem.
Top comments (8)
I think that's a fair assessment, well said.
I agree that the narrative definitely has shifted, the original narrative came off as combative and created this rift. It also was not abundantly clear why we should "use the platform" when tools like React were out there and were better. I think the Web Components brand suffers from criticism of the early spec and the word component coming to mean something more akin to "a React-like component with props, state, and templating." Developers who use React look at them and do not see how or why they should use them because at the surface they should be similar and do the same things, but they aren't. Many React users also may not have a reason to use them if they have a single app that will only ever be in React, the overhead wouldn't be worth it and they should just continue writing their components as React components.
From my point of view, I think the benefit is when working in a large enterprise company. There can be many disjoint applications that must be maintained. If they use no framework or different frameworks and the visual design needs to be refreshed and consistent, it may be a lot of repeated effort to achieve that. If those elements use Web Components, there can be reusability across applications. I think the way to achieve that is through Web Components that focus on the visual display but leave the state to the framework as you mentioned. I think that is where being a primitive HTML element works in their favor. It takes a little bit of a mindset shift as well. Adding a
<button>element, then applying classes and styles, then event handlers. Adding a
<ryan-button>should be thought of like a similar thing, only instead of the getting the browser's default gray and having to apply classes/styles, you get your default style instead. If you think of that custom element as another standard button tag, I think it makes more sense. If it is viewed as a full-featured React component, it may be harder to get on board with it.
Yeah exactly as I said in the conclusion I see 3 uses:
<button is="ryan-button" />. Only awkward thing about this is you don't get isolated styles. At which point why didn't I just make
<button class="ryan-button" />. You can go the other way and just add properties to HTMLElement ie
<ryan-button />but then you have to manually add all the functionality of button. Like accessibility, or like handling form submission. Implementing form validation api, and being recognized by the parent form. Forms are actually surprisingly awkward with the Shadow DOM. They are working on it but if you look at Polymer etc they had to make their own form Web Components to handle this correctly. CSS duplication is a bit of a mess with the Shadow DOM right now as well. This is really my only issue with them as a design system. The platform doesn't serve them well enough, but it would be their most natural fit. Unfortunately they kind of fail here today too.
I guess the challenge is the first point should be the use case but they still fall a bit short there so even with cross browser support they aren't quite compelling enough yet. Which has forced people to find other solutions like CSS Modules. So unless you work at a scale where your frontend is being written in multiple technologies these won't be great. And even then I'm not sure if the current problems are worth it compared to other CSS systems. It's debatable. I did seriously consider using them for a design system. I think if that is possible it would be really great.
It's interesting that you say that they are "just DOM elements" and that therefore they "aren't the right abstraction". For me that is what makes them powerful! But for me DOM elements can be many more things than visible presentation elements. Afterall, script, meta-tag, head, link, and many other elements serve all sorts of purposes in the DOM... So what is it that you can't do? Why is it that state management is not an element? I am baffled how from a design perspective React and Redux are orthogonal yet we insist on transpiling them into one big block of js. IMHO, this is equivalent to monolithic approaches in the middle tier.
Reading the article you can understand why perhaps I'm in this disillusioned area. I went all in Web Components for years. I could picture this future where everything just played nicely. My test harness that I play with is like 10 different libraries in Web Components all on the same page. In a sense it's kind of wonderful. I wrote a simple wrapper that could take any existing library component (VDOM, non-VDOM, reactive whatever) and turn it into a Web Component with basically a HOC.
And then the renderer I'd been working on proved that it wasn't just fast. It was the fastest. And as I got so engrossed in this zone I realized that Web Components were going to be the bottleneck. That's fine we don't need to wrap everything. And then when I started looking at the system holistically I realized Web Component timing based on DOM events was going to be very difficult. How do you do Suspense if the Web Component relies (and for good reason) on the connected callback. Well let's attach it to a different document when rendering offscreen. But what happens as soon as you are dealing with many isolated roots. The overhead and complexity due to time of using DOM APIs for things that don't have to do directly with the rendering was unnecessary. Similarly if you are not using a Virtual DOM but don't wish components to work under isolated roots (for things like Context etc) eager evaluation of child slots is broken. It's backwards. Basically there is always a performance cost. Web Components always add an additional overhead. I'm not going to say it isn't worth it in some cases, but it's hard to be defacto.
Rich Harris author of Svelte had an article a while back on Web Components and while I don't think he is actually right about most of the points. Point 5 is dead on. dev.to/richharris/why-i-don-t-use-.... Someone might just shrug that off as how the platform works. But it's incredibly important when you consider the ambitious things these libraries try to do.
So I understand the monolithic argument and I think using Web Components as a sort of micro-frontend approach seems reasonable. But this isn't unlike the classic issue we've had with state management. I suspect that the ability for the backend to split was the trend to make it more stateless, RESTful, etc. But someone is left holding the bag, the problem doesn't go away. We just pushed it to the client. And why not the closer to the client the more reactive we can be. Early MVC client frameworks like Angular1, Ember1 all failed here and required massive rewrites because they didn't understand their role in state management. That effectively a Controller was insufficient. KnockoutJS had a better approach with View Models which were instance based. React a few years later made it about Components (essentially the same thing) and we are here.
So how much can the client be split? Its and interesting question. We can use code-splitting to only load certain parts of the code as needed, but we are still working off a single backbone. I think there are opportunities to identify and isolate parts of an app, but we have to acknowledge the desire is for the pieces to coordinate in unison. This is very different than a micro-services backend. Even a completely separate advertisement panel should flow into existence. How much state does it share? How does it share that state? These are solveable but its not a hurdle you want to jump every time you render a panel. Or atleast that is my feeling. Libraries could work better with the platform for sure. But when they go beyond the whole DOM how can they view these components as anything other than just DOM elements?
I never understood why there is a war between WC and JS libraries like React, I always thought WC were more intended to low-level parts of an app (like tabs) to basically supply as HTML tags what HTML doesn’t, then use use something like React to build the app can keep track of state. Somehow everyone expected to be able to build a whole app with WC 🤷♂️
I think we tend to look for solutions to escape vendor lock-in. We don't like the prospect of rewriting our own code at someone else's behest. What I saw with Web Components was a full solution between it, tagged template literals, and
Object.observe. I was already on the reactive programming bandwagon back in 2013 and I thought that at some point I'd just write custom element classes with tagged template literals that constructed DOM elements and bound each expression. The web would be the ultimately be my platform and I could drop all libraries. Others would do this too and we could all share our web components in a single unified web. What changed though was big advancement in technology like compilation/bundling where I could write my applications in even better ways and abstractions. Even that wasn't enough until I realized that practically speaking those approaches could be even more performant or provide better UI than the way I'd be writing using Web Components. It's not that I couldn't do it all vanilla better at the cost of DX. I wouldn't be able to get things both ways. Obviously the dialogue has changed a bit the last couple years. But this did come from natural place at one point.
To quote the conclusion chapter of my master thesis:
Component-based UI libraries have been the standard for building feature-rich applications and modern web frameworks like Angular, React or Vue allows developers to assemble the user interface from reusable components. The big difference between these frameworks and Web Components is that Web Components do the componentization on the native DOM level. Although, their goal is not to replace these frameworks since frameworks focus on a reuse-based approach to defining, implementing and composing loosely coupled independent components into systems. These systems glue components together and can provide much more functionality than encapsulating building blocks. Nonetheless, Web Components can enhance those frameworks and have the potential to replace their component layer with a native and uniform solution when the time of disappearing frameworks has come.
Yeah it seems reasonable. Although I think disappearing frameworks is a myth. Even Rich Harris has changed his tune a bit on the role compilation takes in the process. I'm not saying it isn't doable or people won't do it. It's more like a design system that butts its nose into every styling decision you make, I think you will see UI frameworks get more intrusive like this. Their ability to orchestrate the experience is something we are seeing more of. And in so while the weight of the runtime is being reduced the reach of the libraries is not. Conventions and patterns are still being applied if only to be statically analyzed and ultimately define the compiled output. This doesn't mean the result is more generalized.
In that world, only the simplest of components have any place(ignoring micro-frontends for a moment). Better organizational patterns like inversion of control only makes this harder when you need the children calling the shots (think Suspense, Context API). I do think this somewhat comes down to the perception of reusability. See I don't see React Components as that much of a re-usability mechanism. Sure you can, but more that their modularity makes them replaceable. You throw out a piece without throwing out the whole system. Whereas Web Component's value has to be in generalization and re-usability to be compelling over what the library already has available. Basically patching holes in the native controls we lack. Which is fine but it also means that we are talking about a much smaller scope.