Also as for the state management side of things there is nothing at all stopping you from hooking up whatever state management solution you want. I’ve even seen a bunch of solutions that use the browsers built in event model as well if keeping dependencies to a minimum is your goal.
Finally, the last thing I would suggest is that writing an entire app in vanilla web components is kind of crazy talk in my opinion. For 5kb you can have a super nice developer experience using Lit (https://lit.dev)
I 100% agree with this. For me it was more of a question of "can I do it", and that was something I wanted to find out. You notice that I ended up having to recreate a significant chunk of lit-like functionality on my own via a base class: https://github.com/jjcm/nonio-frontend/blob/master/component...
I would very much recommend not going full vanilla. Using a library like lit will definitely help making things easier/more polished, and will integrate better with existing tooling.
Why?
I've done many.
My guiding principles:
- Avoid shadow dom unless it's a library. Stick with the light DOM and css system of your choice.
- Use lit-html or similar for rendering
- Use class properties for reactivity (add a render() call to setters, as needed)
- Ignore attributes entirely. Just use properties.
- Don't bother with composable components and slots. You generally don't need them
- Use a nice router like vaadin or ionic or similar to hoist components and support deep linking
- Let components maintain their own state. Structure the dom so that it's easy to grab state from a component with querySelector. If this doesn't work, I have a little state lib called ApplicationState[1] that works great.
- Really think about what needs to be a component vs normal html. Don't overdo it with components.
- Structure the app with high level "scene" components that map to URL deep links, and app components that you use to build the scenes. Scenes are targets of the router.
I've been doing this for years for many apps for many clients. Blazing fast, simple, no framework churn, no compilation, no framework download or overhead...
It's pretty tough to make myself use a framework at this point.
The entire reason I said that was because there is going to come a time after you have built probably your 5th component where you are going to start moving a whole bunch of shared logic into a base class that extends HTMLElement and that is the point you should probably stop and just bring in Lit at around 5kb total because they already did exactly what you’re doing and they almost certainly have done a better job with a bigger team and access to browser engineers with thousands of existing users and a bigger test suite with better documentation and well.. you get the point.
A lot of your argument seems to be quite focused on the idea that frameworks are some heavy weight thing which is perfectly true in many cases. But other options exist, and with a 5kb-10kb library download budget those arguments don’t carry a lot of weight.
I kind of thought this too at first, and started down that path several times. I actually wrote a whole framework similar to Lit early on, thinking the same thing.
What I've found over time, is 99% of that is YAGNI (You ain't gonna need it).
All my components just extend HTMLElement directly.
I explicitly dislike Lit because you lose control over the component lifecycle. I don't find it easier, I find it harder - especially when you're mixing in pure js libraries like draggable or whatever.
I prefer having to call render() explicitly, and controlling when/how that's done. At the end of the day, it's not actually any more code.
> a better job with a bigger team and access to browser engineers with thousands of existing users and a bigger test suite
Yeah, I don't need all that to show some interactive markup on the screen and organize code well. That's really what WC's are for me - just a convenient unit of encapsulation for vanilla JS/DOM stuff.
Lit is cool, so's stencil. I just don't need it.
lit-html, however, is probably my favorite piece of tech in the last 5 years. I've been using it since it was originally created and was only like 200 lines of code.
All that code I wrote is still working great, and hasn't needed any sort of update. On the other hand, if I'd gone with the Web Components Framework Of The Day, I'd have done Polymer and caused a massive headache for my clients.
Many people were making the exact same arguments you were about Polymer back then.
And then LitElement.
And then Lit.
Lit 2.0.
Now Lit 3.0.
Same story with <fill in your framework here>.
Angular 1.5 anyone?
Framework churn can kill a company.
I code to standards, keep it as vanilla as possible, and prefer libraries over frameworks (though sometimes the lines get blurry there - looking at you Ionic).
I’m quoting directly from here: https://lit.dev/docs/v3/releases/upgrade/
“For the *vast majority* of users there should be no required code changes to upgrade from Lit 2 to Lit 3. Most apps and libraries should be able to extend their npm version ranges to include both 2.x and 3.x, like "^2.7.0 || ^3.0.0".“
And here is v1 to v2 for completeness sake: https://lit.dev/docs/v2/releases/upgrade/
Most of your examples are kind of odd. One of them was a brand change that involves no code at all.
There is only one meaningful bit of churn in there which also was an overlay of the Web Platform itself.
Polymer was a WebComponentsv0 library
Lit is a WebComponentsv1 library.
I think most developers are very familiar with the framework churn that I'm referring to.
In my case, I've been dealing with it since the 90s.
So my biases are there for a reason.
What are the cases when this easy no code upgrade fails?
And yes, v2 update is quite a breaking change
> Polymer was a WebComponentsv0 library
It wasn't. It got upgraded to v1 almost immediately
Is performance a win at all? I would expect performance to be worse than using a regular web framework (React, Vue, Svelte, etc). Because these tend to get their performance from batching DOM manipulations across the entire page, and you can't do that will web components.
I figure web components are pretty much dead in the water for this reason alone. I guess they can be useful if you just want small "islands" of functionality rather a fully client-rendered page.
Also web components tend to use more modern layers of the web stack (style isolation, shadow DOM, etc) - which allow the browser to perform more optimizations, as well. I think we'll find that they become more performant over time. Also consider that some "native" html components are effectively implemented the same way as web components these days.
Often you'll find that React/etc are perf hits (vs finely tuned components) because their declarative nature tends to lead to unnecessary work …which is totally fine 99% of the time, because the productivity win from that is HUGE.
For state mgmt I have noticed the same problem and was thinking about trying to take a page out of desktop development’s playbook by having a window-level message bus that all components subscribe to and filter data from, and every state change being published on that message bus.
That and the the other big win comes from using native compiled C++ browser code to implement large chunks of what libraries typically provide in userland JS.
React is probably the worst offender here as it for all intents and purposes brings its own implementation of both DOM and events. It’s just an outdated architecture choice that made a huge amount of sense at the time but nobody would do that today if they were building a front end library from scratch.
Finally regarding your point about the message bus idea you might want to take a look at the BroadcastChannel API if you aren’t already familiar with it. Sounds like it might be what you’re looking for.
Really amazing. Thank you for this. Didn't know that.
OP above or anyone else can say anything about routing in context of web components? Deep linking? Any pointers?
Noob backend guy here.
When you bring in modern things like service workers and the prefetch API you can still very much keep the speed that is traditionally associated with SPAs.
Otherwise I’ve seen good things said about both of these options:
1. https://github.com/vaadin/router
2. https://github.com/lit/lit/tree/main/packages/labs/router
Both follow the mental model of mapping a URL pattern to a component fairly intuitively.
The big win here would be that if you have a huge app (let's say an ERP kind of app that has too many screens and dialogs and what not) then that can be easily decomposed into separate pages that are far more manageable.
You can easily wrap these pages up as Electron/Tauri app or Capacitor etc for mobile.
Deep linking however would be interesting in that if someone pastes a URL, we have to load the exact screen/dialog etc.
Not at all meaningfully different from say
void main(List<String> arguments) {
runApp(MyApp(arguments));
}
But you can!
Here's an example of passing complex objects as attributes (look for zx-listeditor): https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
And here's the web component itself: https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
And here's the tiny (500-line) lib that enables it: https://github.com/wisercoder/uibuilder/#jsx-for-web-compone...
You object to this tiny amount of code?
Or you just write a 'value' getter and setter and be done with it. I write most of my web components to operate like a form component. Forms have state. Forms have mechanisms for gathering state from all components and saving them into a single object. Forms have a means of restoring all that state.
You _barely_ need a layer of abstraction to handle all this. I've honestly never understood the "state management problem" or why anyone would think a framework is the way to solve it.
State management is still unanswered. You'll end up having to do a lot of this yourself if you aren't using a wrapper framework. One nice thing about React is state management is mostly solved and there are existing patterns for it. There's expectations of how it's done. With web components it's the wild west, and since you can't pass complex objects as attributes like you can in jsx, you end up doing a mix of attributes, events, and calling class functions directly.
Performance isn't as big of a win as I'd have hoped. I actually reverted some smaller atomic elements (i.e. icons) away from being web components as their instantiation cost wasn't worth it when I had hundreds of these elements on the page. Static elements, despite the readability they provide in code (i.e. <nonio-icon glyph="hamburger">), just aren't worth using web components for, which was fairly disappointing.
That said, there are things I REALLY enjoy about web components. As the author shared, it's incredibly easy to add a component to new pages, regardless of the framework being used. If something is built using web components, you can truly use it anywhere. This has been really nice when building supporting sites like api docs for nonio, where I can easily pick and choose what components I want despite having a different stack. Need to embed a <nonio-user> component? Import the js, add the html, you're golden.
Style encapsulation of shadowroots is quite nice. I really love not having to worry about breaking styling of any component as I work on a page. It means less guesses and checks of clobbering styling of other elements, and less of a need for a visual diff framework to ensure this hasn't happened.
The other aspect I enjoy is how slim the package sizes are. No libraries or anything extra to load, everything is baked in. Because you can pick and choose components you need so easily as well, it's incredibly easy to keep package sizes down.
If anyone is curious, source components for nonio are here: https://github.com/jjcm/nonio-frontend/tree/master/component...