Web Components Eliminate JavaScript Framework Lock-In
jakelazaroff.com
jakelazaroff.com
Specifically there are two type of components that really thrive as web components (as opposed to react):
1. Highly interactive components - Components that implement complex interaction management (but sort of agnostic to application state) are ideal web components. You don't need to mess around with `useEffect` or `useMemo`. You get really tight control of rendering and state updates.
2. Highly internally stateful components - Components that track a lot of state that doesn't escape the component, work great as web component. You get strong encapsulation without a framework requirement.
React conflates two concepts that I think are better when separated: templates, and components. Templates provide a "re-render the world" approach, and components encapsulate state and interaction patterns. Conflating these two things causes the standard useEffect and useMemo headaches. It also means that any consumer of your component, must also use your templating system (react's render function).
The `lit` library does this separation extremely well, allowing you to implement components using templates if you want, or not. And consumers do not care how the internals are implemented.
Whether a React code base conflates them depends on the code base, but the more familiar terminology is container vs presentation. [1]
[1] https://www.patterns.dev/react/presentational-container-patt...
One issue I see though and I almost feel dumb for saying it, I don't like how web component code looks. Using innerHTML feels really weird when your team is so used to using JSX for react components for example. I don't think this would stop me from researching web components more but does anyone feel similarly or have a solution or obvious answer for me in this area?
That said I think this is mostly a non-issue.
[1] https://lit.dev/
You don't need those in React either. Whatever you do in Web Components can probably (most likely) be done in React.
After all, Web Components are a solidified 2010-era design. The reason React has hooks now because people have moved on, and are exploring other ways of building stuff. The only mistake React did was to keep reactivity on component level (hence all the hooks and their weird rules) when most people moved on to more granular reactivity. Hell, even Angular did.
I mean... sometimes you do, that's why they exist. But yeah, most of the time you don't.
> Whatever you do in Web Components can probably (most likely) be done in React.
Of course! React is a good and powerful framework. But everything you do in React can be done in Angular 1.0 or even backbone.js. As always with frameworks it's about the productivity / performance ratio for your team (or for libraries, the ratio for your consumers).
> After all, Web Components are a solidified 2010-era design.
A lot of the web components related APIs are being actively developed including constructable stylesheets, shadow DOM APIs and more. Regardless, the era of design is not a great point in either the "pro" or "con" column, and is usually an ambiguous shorthand for the actual quality being critiqued.
They exist because they're useful, but you can write traditional React components that look a lot closer to Web Components, if you think that's better.
For example lit-html templates support syntax like:
<my-element .someProp=${new Foo()}></my-element>
Note that it's still possible to pass complex objects to native we components but only imperatively via properties so it's a lot higher friction (as it should be).
> At this point React is legacy technology, like Angular. Lots of people are still using it, but nobody can quite remember why. The decision-makers in organisations who chose to build everything with React have long since left. People starting new projects who still decide to build on React are doing it largely out of habit.
I'm going to withhold my own thoughts about that quote, cause I'm curious what everyone else thinks.
Wrt. Web Components, I played around with Web Components about half a decade ago and they felt promising but not ready for prime time yet. They were pretty messy and felt like they did a poor job of encapsulation. (At least at the time.) I haven't looked at them recently, but the code examples in these articles look much better (and cleaner) than what I remember.
I can remember why. This, and every other article I've ever read arguing to replace React with Web Components, completely misunderstands the point of React. It isn't about JSX. It isn't about encapsulation. It isn't about reusability.
It is about enabling a design pattern where *the user interface is a pure functional transformation of the application state.*
I kind of feel like people get tripped up by the fact that "virtual DOM" and "shadow DOM" sort of sound similar. They have literally nothing to do with each other. The React "virtual DOM" allows you to *completely re-output the entire user interface* on every state change, which is not possible with any other design pattern, and is not possible without a framework, because actually re-rendering the entire tree on every state change isn't performative (or usable).
Anybody who is advocating an alternative to React needs to do one of two things:
(A) Make a convincing argument that a different design pattern is better. Some things we've tried, which most people think are worse:
1. Using the DOM as your data model (jQuery)
2. Manually writing virtual representations of every view (Backbone)
3. Auto-magically two-way binding some data structure with the DOM (Angular 1)
4. Observables (Ember, maybe Angular 2+?)
(B) Advocate a framework other than React that uses the same pattern as React, but improves the usability. I think the two places there is the most room for improvements are:
1. Animations
2. useEffect() in general
I have not seen anybody successfully do either A or B.
Recommended reading: https://acko.net/blog/get-in-zoomer-we-re-saving-react/
Don't forget Knockout which was the OG. You can also make the case that Svelte, Vue, and Qwik all are different takes on Observables (at least as much as Angular 2+ is) all with more or less magic and more or fewer escape hatches from Observable best practices to imperative(-looking) code.
I got a "What if we did Knockout but with with the compile-time benefits of TSX and Pure RxJS Observables?" itch a couple months back and have made some wild progress on it. I certainly don't think it is ready yet to advocate as an alternative to React, but it's probably in an interesting A4+B2 tangent on your map right here, and I find that interesting.
It was something that was also attractive about early React that React was similarly just a view engine and could do MPA or SPA or things in between and it didn't try to have the full kitchen sink. I don't know exactly where React stopped being that, but it always feels harder to argue that current React is "just" a view engine.
In the larger context here with this article, libraries that are just view engines should be great for building the internals of Web Components. (I'm hoping the view engine I've been working on might serve that role well, though with a dependency like RxJS I'm not sure if it would be towards the top of the list for many developers. It will certainly feel bigger than Lit, for example, no matter how well it tree shakes.)
> I probably would not choose to use javascript or observables/signals.
This seems to be a case where we differ. In one obvious part because I don't tend to like signals and think they are very different from observables (because they are harder to encapsulate and offer fewer operators and tend to bleed too many accidental imperative escape hatches). Observables, to me, are just as useful to describe "open this file, read its contents, convert it from Markdown to HTML, then bind it to innerHTML here" on the server side as "fetch this URL, read its contents, convert it from Markdown to HTML, then bind it to innerHTML here" on the client side. Very different forms of interactivity, but there is still an interactivity there that a good observable pipeline can describe. Especially when you start to get into the idea that the difference between "open this file" and "fetch this URL" is tiny and can be dependency injected. Same component, slightly different inputs, slightly different expected outputs (you expect the server side binding to complete, whereas you expect the client side one to live for some time and continue to respond to different URLs from input).
But anyway, the more UI frameworks, the more ideas get out there, the better. I'm hoping that some day in my lifetime, web UI gets "solved" and I'm not willing to believe that React is the answer. Does your project have a name yet? I'll keep an eye out.
I'm calling my view engine Butterfloat, and I only just finished the first documentation pass, so be gentle, but feedback is very welcome: https://github.com/WorldMaker/butterfloat
This is only true for read-only components.
There is no "pure functional" when you introduce state and interactivity. See here: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
This is rarely done, because there are pragmatic reasons (e.g., animations) not to, but it is possible.
The other mistake alternatives make is to try to make components having internal state "easier." It should not be easier! Every single useX() is a statement that "I am violating the proper design pattern of this application," and it's a feature not a bug of React that you have to be obvious and intentional about it.
useEffect should have been named useDangerousSideEffectAndThisIsNotALifecycleHook and the js influencers should have never compared useEffect to component lifecycles and the React team should have updated their docs and not wait 4.5 years to do so and the dependency array should absolutely not be optional.
To achieve that, it doesn’t matter whether the state is local to a component or global across the application.
That works for simple, read-only components. The moment the component starts managing its own interactivity you end up needing state, and at that point things break down. If you need to change props, you have to essentially recreate the component by changing key [1], and at that point, what is the benefit of React?
[1] https://legacy.reactjs.org/blog/2018/06/07/you-probably-dont...
It's decoupling those two that brings all the power.
But the idea doesn't represent at all the web. So react is a mess of complex code trying to make the things you can create on the web behave like stateless pure components. Yet, it mostly works, and the react does indeed implement the idea that the interface is a pure functional transformation of the application state. (But I do disagree on claiming this the "entire idea", it's half of it at most.)
Personally, JSX is the mainly thing that makes React attractive. As JSX is just modern E4X. It really ought to be put into the ES standard again instead of this Web Component stuff.
Another good JSX implementation (SSR) is: https://nanojsx.io
If you just want something light JSX, then this seems the way to go for now.
Just don't change application state from a component lifecycle function and you're golden.
> 1. Animations
> 2. useEffect() in general
I'm not normally one to "shill" a web framework, and I still mainly use (and love) React, but I'm curious if you've tried Svelte, because it definitely manages animations better than React, and has a different idea of state that's more ergonomic for many use cases (though I won't go so far as to say it's definitively "better" than useEffect)
It can't be that that design pattern because React did not invent it. Every server-side templating library is a pure functional transformation of application state. I can tell you that many people also ported that design pattern to JS.
What tripped library writers up is once they applied that pattern to JS, there is no way to actually define the transformation because to do that, you either write a template or the code-equivalent (i.e. using DOM as your data model). And that's where JSX came in.
Also, I think you have patterns completely confused.
Backbone is NOT in the same class as jQuery or React. Backbone is more like Redux, mobx, or React hooks because it's a data model library. In Backbone, you define "models" and "collections" -- basically, you use Backbone to store your application data in memory.
Backbone did have a views component to it but it actually could not generate any HTML. Look at this example from their docs:
var Bookmark = Backbone.View.extend({
template: _.template(...),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
It literally has jQuery and underscore.js! Backbone had no templating/HTML generation ability so when you used Backbone, you combined the fat Backbone classes with whatever horrific HTML-generation method you used to create a huge monster.You can actually use Backbone.js with React since they are not in the same class. You would not, of course, because mobx is basically the same style of data modeling as Backbone.js but with almost no boilerplate.
But Lit templates solve these problems in a more browser integrated way, without the need of a virtual DOM. How you manage the state is free to your choice, that is also not something exclusive to React and your favorite pattern can also be used with Lit. I wrote a tiny state management library (LitState [0]) which makes it very easy for multiple components to share the same state and stay in sync. I personally find it much more convenient and cleaner than any other state library I've used before. And it integrates very nicely with Lit.
- not a framework, but a library
- can do fine gained state updates without unnecessary re-renderings (reactivity)
- uses dom instead of vdom to produce best in class SPA performance
- uses jsx like react
Performance claim / proof: https://krausest.github.io/js-framework-benchmark/
So maybe some kind of "non-functional"/reused state issue exists regardless?
(Admittedly, these benefits haven't been thoroughly researched, as far as I'm aware, so it still mostly relies on gut feeling and experience.)
Simple Model-View-Controller pattern works well for JavaScript, just as it does for ASP.NET Core, JSP and JSF, Ruby on Rails, Django and so on.
Want proof? Browse this code:
https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
This is the application: https://github.com/wisercoder/eureka
In fact back then, everyone’s big app was MVC - usually Backbone. I worked on Airbnb’s host-side web app called “Manage Listing”, it had probably 30+ models, 150+ views, 100+ controllers in Backbone. There were many bugs in this scale of app around making sure the DOM reflected the latest change to some model. the engineering team regarded the more complicated screens as a nightmare to maintain in our fast paced environment with many teams touching the code.
Engineers at Airbnb started to adopt React as a solution to the problems we all experienced with MVC - not because it was a “hot new thing”. When React came out, it was widely regarded as weird - it was more like “eww, this smells of PHP and needs a weird compiler, but pure function of state is a lot better than fiddling DOM manually…”. Eventually we replaced Manage Listing backbone views with React components one by one until there was no backbone left.
React is still kinda weird but it does solve this problem the best out of the modern frameworks. It’s happy to over-render by default and prefers a correct DOM-for-state over everything else. It’s clear thought that the mental models popularized by React are worth it - now Apple and Google are switching to the React model in their own frameworks.
> There were many bugs in this scale of app around making sure the DOM reflected the latest change to some model. The engineering team regarded the more complicated screens as a nightmare to maintain in our fast paced environment with many teams touching the code.
I've seen this same issue in Cocoa/UIKit/Android views. Older Cocoa in particular has a lot of hairy wiring up of signals and first responders and delegates. I think it's less of an issue there because the rate of change is typically lower; in web land we expect to deploy continuously and with a very high number of engineers, anything targetting the app store will usually deploy at most weekly (2 orders of magnitude fewer deploys) and with many less engineers (usually an order of magnitude at least. My hypothesis is the difference in change rate is why this kind of bug was more of an issue for web developers.
Personally, I have not run into large amounts of bugs of this type, and I have written plenty of MVC code using VanillaJS.
Nothing will ever beat the following as tech demo for how easy it is to make interactive UIs, but anybody who ever tried to reason through a large Angular 1 application would seriously hesitate to want to use it for something much more complicated.
<script src="angular.js" /> <input ng-model="inputValue"> <div>{{ inputValue }}</div>
How many times have you seen the equivelent to a fullname = firstName + lastName inside a useEffect? Its outstanding. :(
Doing something like this.shadow.innerHTML = `your HTML` with web components is terrible. document.createElement() is terrible. $('div').appendChild() is terrible. <script language="html" name="my-template"><!-- --></script> is terrible. HTML(Div(Strong(text))) is terrible*. Storing templates as JSON and writing a one-off loader is terrible. <div style="display:none">template</div> is terrible. I've done it every possible way and they are all terrible.
JSX fixed all that.
The biggest previous attempt at something like JSX was E4X where you could embed XML straight into JS, which was kinda nice except it added the entirety of XML and its complexity. I creamed when it came out but then I tried it and it was not it. (E4X ended up surviving a little longer in Adobe Flash though.)
E4X: https://en.wikipedia.org/wiki/ECMAScript_for_XML
*This is what JSX compiles to behind the scenes.
This all feels rather half-baked. Why can't the parser/prprocessor distinguish between that "class" and "class" in JS source code? We can have reusable HTML snippets (some may call components) with normal templating engines easily. Look at something like Jinja2 and how to reuse blocks and macros. Yes, some React component can encapsulate its own interactive behavior. That is only because we are already writing JS though, so we can already write frontend logic. But do we actually want that? Coupling state and behavior? I think we might not. Writing a script that gets served only on those rendered templates, where it is needed is not so hard either, when using a normal templating engine.
React is simple. Here is your data, and here is a function that transforms that data into a view.
Templates are terrible, and that is a hill I am willing to die on.
Is that aspect so much different from templating engines? In templating engines the rendering step is simply abstracted one step further: You get a generalized render function, that you pass data to and a template to render with that data. That is also a function to turn your data into a view. I don't see how React is better in that aspect.
But you know, if you are trying to put HTML templates in separate files, you have to now send extra HTTP requests. That's an even bigger no no.
In other languages, you can just put your template in a separate file and load it from your package/.jar/assembly. Super easy and most importantly, 100% standard and provided by the environment.
So people just settled with React because it worked and no successfully invented a standard bundle format for the web.
F' it we said.
It's not actually, with HTTP/2. The separate requests are all multiplexed over the same connection, often with prefetching, and compressed together so that there's little to no overhead vs. putting them in the same file.
This illustrates a common failure point for web frameworks though. They encode a lot of folk knowledge about how the web works, but that folk knowledge becomes obsolete as browsers change.
Another prominent React example fits into this category: the virtual DOM. React assumed that DOM manipulations were slow and Javascript manipulations were fast, and so they built up an internal representation of the DOM in Javascript so they could manipulate that one and diff the change to apply it to the real DOM. This was true from approximately 2008 (when V8 introduced JS JITting) to 2013 (when Blink/Webkit/Firefox all moved to a dirty-bit system for DOM manipulations), which not coincidentally is when React was designed. But since then, DOM manipulation has been just pointer swaps and a dirty bit flip, and if you're careful not to invoke any operation that triggers a reflow and repaint you can make your modifications directly in the DOM for roughly the same cost as making them in JS. Since React is declarative it could easily have enforced the no-reflow rules without the virtual DOM, but because DOM manipulations were expensive when its designers learned web programming, it introduces an unnecessary concept.
But HTTP/2 is not the right example. You're missing some things on the web timeline.
Long before HTTP/2 (time in web terms), and after React (and Angular, etc.) came about, people created actually-popular bundler toolchains that can bundle arbitrary files together and then incrementally load them as needed. This effectively fixed the multiple HTTP request problem long before HTTP/2 existed. Efficient template files have been possible for years. (Granted, not anything standard though.)
The issue right now is that someone has to make a full toolchain with strong developer support to make this a de-facto and popular way to distribute apps. Just because you have a bundler or HTTP/2 doesn't mean you have a nice way to make use of it. You need a nice library. Your usage examples need to look pretty. It needs to work with other toolchains. It needs to look like your library will be supported for a long time, just like how React was supported by Facebook.
You can't just write a blog post about how it's possible because you're basically asking your readers to invent and maintain a library or worse, roll their own internal library that will confound people at their company.
Slightly longer answers:
[modifies] https://developers.google.com/speed/docs/insights/browser-re...
[measures] https://gist.github.com/paulirish/5d52fb081b3570c81e3a
Also there's a bunch of exceptions where you can modify things without triggering a full reflow. Anything that takes elements out of normal flow (position: fixed, position: absolute, float:, overflow: hidden) creates a layout boundary where changes inside only reflow the containing element. Any element with a transform: or opacity: property gets rendered as a 2D texture on the GPU, and then you can do subsequent transform/opacity animations purely on the GPU without touching the CPU. Used to do this to make performant animations on mobile devices.
> What forces layout/reflow. The comprehensive list.
> All of the below properties or methods, when requested/called in JavaScript, will trigger the browser to synchronously calculate the style and layout*. This is also called reflow or layout thrashing, and is common performance bottleneck.
Most projects that I have been involved with have some kind of build system that does all sort of magic. It should be possible to include the template in the JS file during the build process
> This all feels rather half-baked. Why can't the parser/prprocessor distinguish between that "class" and "class" in JS source code?
Remember that React is "just JavaScript", JSX is a syntax transformation of JavaScript. When you use `className` in your JSX, you're using the standard DOM Attribute [1]. You wouldn't use `class` either in vanilla JS to set the CSS class, you'd use `className`.
https://developer.mozilla.org/en-US/docs/Web/API/Element/cla...
In a usual templating engine valid HTML is also valid template. One does not have to use templating engine specific things. A HTML snippet can simply be HTML. No harm done. But in JSX one cannot use normal HTML, as shown by the `class` case.
> However, that means, that now one has something, that is neither looking like HTML, nor is it looking like JS, even if it may be JS through some pre-processing (but then actually it is not really JS?).
Yes, it's not HTML or JS, it is JSX. It's an XML-based syntax to declaratively write JavaScript. It's optional too - one can write the function calls directly in JS, and indeed that's what people did before JSX existed. `React.createElement("div", React.creatElement("span"),...)`. It could be worth using a REPL [1] to gain an intuition of the JavaScript that is produced from transforming different JSX examples.
> The result is, that one has to learn some different syntax that is specific to React.
JSX was originally created for React, but now is not specific to React, other libraries and frameworks use it as well. It's a general purpose XML-based syntax for writing JavaScript declaratively.
And yeah, if you want to use JSX you have to learn it. It's easy to learn if you know JavaScript and XML and shouldn't take very long. It's XML and then anything inside curly braces is a JavaScript expression.
If you're using TypeScript replace everything "JS" I'm saying with "TS", it's the same result. And obviously type checking works without issue because it's a syntactic transformation to TS, not a semantic transformation.
> In a usual templating engine valid HTML is also valid template. One does not have to use templating engine specific things.
JSX is not an HTML templating engine [2]. JSX is a way to write JavaScript declaratively using an XML-based syntax.
> But in JSX one cannot use normal HTML, as shown by the `class` case.
Yes, as expected. JSX is not an HTML templating engine, it's a way to write JavaScript declaratively using an XML-based syntax. In JavaScript, you use the DOM APIs [3], not HTML attributes [4].
[2]: https://legacy.reactjs.org/docs/introducing-jsx.html
[3]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Ob...
[4]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
Yeah it’s another framework with its own quirks but it lets me think about the UI in the same way I’ve thought about it for 25 years. For someone like me that’s a godsend.
[0]: https://lit.dev/
Here's an example of using a web component in a JSX template (look for zx-listeditor): https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
Here's how the web component is implemented, also using JSX: https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
It's new and has no significance for the history. But I think it's relevant to point out that <template id="myhtml"><div></div></template> isn't horrible.
And that those articles about web components are doing a really bad job of using constant strings instead of this.shaddow.innerHTML = getElementById("myhtml").innerHTML.
It's bad because it's inline and your templates have no obvious place to go. You also can't import them on the fly (or rather, it's as idiomatic to import as any HTML). And obviously, because they have no effect on HTML and can only be used in javascript
> This is what JSX compiles to behind the scenes.
Oh, it is much worse than that. By classic default it is:
React.createElement('div', { /* props and attributes ... */ },
React.createElement('strong', { /* ... */ }, text))
Recent updates and other JSX frameworks simplified the name of `React.createElement` to just `jsx` or `h`. I've seen projects that manually write `h` calls like that everywhere, and it is indeed terrible.(Source: I've just completed a bunch of crazy TSX work and got pretty familiar with the compiled forms.)
{ tag: 'div', padding: '2px', position: 'absolute', content: [myText] }
than <div style="padding: 2px; position: 'absolute'">${myText}</div>
The main reason I ended up settling for JSX was just convention: I don't want to be the one with my own unique format for UI trees unless I can convince everybody else to use my format.Note that Jetpack Compose has a very similar declarative reactive paradigm as React, but represents components with Kotlin language mechanisms. IMHO I like it much better, because it's much more composeable.
1) boring, old technology that has been around for so long that no one even remember why it was chosen in the first place, and
2) extremely fast-churning, bleeding edge software that is constantly changing and breaking because the JS community is more interested in chasing trends than building robust stuff.
1) the ecosystem keeps chasing shiny tech that is constantly changing, not boring solutions
2) everything should be written in Rust
Back in reality, React is slow and massively overbuilt for the majority of stuff that most people are building.
If you played around with Web Components a long time ago then you probably played with the v0 spec, which was somewhat different to the current version.
You do know that Google sponsored and ran things like Polymer conf? That it builds lit? That Google's devs have literally overrun and overruled most web specs committees? That Google has spend hundreds of millions of dollars promoting Web Components?
And yet here we are.
As for Web Component specs, we got v1 because of Apple and Mozilla: Google would happily have stayed with v0.
In retrospect, I think I avoided web components because of Google and their aggressive dev relations pushing Polymer. It felt so one-sided that it didn't seem like a web standard. It felt more like a Google "standard" like NaCL. Having to ship Polymer to support non-Google browsers felt even more heavy weight than React.
Then you should skip Web Components :)
If React won, it was precisely because it was not "overbuilt."
It was simpler (deliberately) than Backbone, Angular, Vue, Svelte, etc.
You can implement a React clone in a few hundred lines, a la Preact.
React won by being early and different. Svelte came out three years after React and got (unfortunately) steamrolled.
One is that the implementation is very simple/transparent, e.g. C.
The second is that it allows users to have very simple code, e.g. Python.
React is mostly the first with a bit of the second. Svelte is the reverse.
Everything else, even if it has a better technical fit for your project, is likely riskier for your organization.
If I was going to learn a language though, I'd probably learn Golang. Their rate is so much higher.
We're hyper-specializing people in a specific framework that then don't even understand the basics of JavaScript itself.
I already find the divide between frontend and backend developers unnecessary the majority of the time. This takes it a step further and in turn creates monstrosities like the leftpad debacle.
Can we stop with this nonsense? Hire good developers and let them spend 5 minutes to learn the god damn framework of the week. If your hire can't learn React in a week, I have some bad news for you.
I think that is the point. The original "pitch" for react in my mind was that it was lightweight and simple. I don't think you can say that any more, and I think a lot of rough edges have been identified (e.g. hooks fiasco, app state management, dependency-hell etc to name just 3) after years of use.
It's the same cycle that always happens. Old thing ossifies and grows fat overtime trying to be all things to all people. Then some new thing appears that starts with a clean slate and no prior expectations that proves popular as a result
I can easily justify the reasons I still use React, but I can't be bothered writing it out every time some gimmicky front-end tech hits HN.
1. Go back to reactive templates.
2. Sprinkle directives over plain HTML a-la early Angular 1.
3. HTML over the wire.
All those ideas pre-date React and React won against them back in 2014.
4. The React model but with compiler support to improve the ergonomics (and with better static checks).
I was skeptical at first but I've seen in production a lit.js/WC component library used with React and Angular successfully in the banking sector and it works surprisingly well.
I prefer the terms stable, mature, or hardened.
People use React/Angular because large products are forced to have a way to organize things across multiple developers and time. Now don't get me wrong I think these JS bloated websites are an abomination that have largely made the web worse and not better, especially since UX "engineers" feel a compulsive need to change the layout every 6 months and now even simply scrolling through a page involves 50 web requests, and takes a full 3 minutes to load the next page... but I digress.
1. Eases candidate selection. There is no uniform baseline of competence in software generally and certainly nothing exists for web development. Its often the blind leading the blind, so outsource everything to a tool. At the very least, people that cannot use that tool are then not qualified to be there if you discount absolutely everything else. After all, it isn't as though employers are willing to train new developers to perform as required, so they need to turn this into a commodity as much as possible.
2. Composition. Most of the people who do web development cannot write original software, plan, or organize at the level required by modern applications. Employers still need people to do trivial things to get text to appear on screen. As the demand for these trivial tasks still exists employers still need to hire people to do this work, and so they attempt to outsource the parts that require higher intelligence.
These beg the obvious question: What will happen when employers realize they don't need to overpay developers to do this work when half of it can be pushed into content management systems and the other half can be pushed into AI?
They are still messy. https://w3c.github.io/webcomponents-cg/2022.html
Even the name "web components" is basically just marketing fluff. When people say "web component", they mean using two particular DOM APIs: customElement and shadow DOM. Those are both pretty niche APIs. There are some cases where they are helpful, but 99% of the time, they don't add much to a project versus good old querySelectorAll and normal CSS.
Let's say you're writing a Vue app and you really want to use a library that's only available as a React component. You can wrap that library in a web component and use it in your Vue app just like you would any other HTML element. The rest of your app can continue being Vue, using normal Vue components.
You may add another layer of abstraction on the top of it. But that only make you another framework (like polymer) instead of actually using web components.
In other word. Web component is useful to encapsulate an 'app' (for example: a interactive map viewer that accept an address) inside another app. But is mostly useless to encapsulate anything other than a simple ui/input component. There is basically 0 functionality to inter-blend between parent and child components of different frameworks.
If you want to embed something that doesn't use your page's styles, we already have iframes. Shadow DOM is just a half-assed recreation of that.
If you want to include something "as-is" (i.e. the demos you mention), use iframes. If you want to include a component like a button, use any of the existing frameworks that work just fine. Trying to build a button that pretends to be a browser-native component but actually isn't is a terrible idea.
In any event, shadow DOM is just a worse version of scope selectors. There's not really a good use for it now that you can just do a style reset on a donut selector inside an import layer. It can all be done declaratively with CSS instead of using an imperative JS API that has a ton of gotchas.
- transclude other elements (technically you can with iframes, but hopefully it’s clear why making something like this breadcrumb component would be extremely awkward [1])
- control and react to the display of child elements via <slot>s
- call methods on custom element objects
etc etc. It’s fine if it doesn’t solve any problems you personally have, but that doesn’t mean it’s not useful.
The other things are just plain old JavaScript and have nothing to do with Shadow DOM. Yes, they can be done with customElement, but they can also just be done without it.
To do the same thing in CSS you would do <div class="myelement"><div class="slot"><div class="a">Transcluded content</div></div></div> @scope (.myelement) to (.slot) { /* rules that apply to .myelement but not .a */ }).
On topic: I don't think WebComponents are going to "make it" until someone builds a nice framework on top of them. React, Vue, Svelte, etc. solve a number of problems that are not directly solved by Web Components. State management, rendering, routing - right now, these are, imho, the high level areas that need solid solutions for an UI app to function in any sane way. How much of that is solved by going Web Components?
It's got reactivity, declarative templates, great performance, SSR, TypeScript support, native CSS encapsulation, context, tasks, and more.
It's used to build Material Design, settings and devtools UIs for Chrome, some UI for Firefox, Reddit, Photoshop Web...
https://lit.dev if you're interested.
As in:
- reactivity. Specific to lit
- declarative templates. Specific to lit
- SSR. Specific to lit.
- context. Specific to lit.
- tasks. Specific to lit
"minimal" and "low lock-in".
Please do not hesitate to call it a framework. If you call React a framework, then lit is definitely a framework.
Lit is also modular. The template library, lit-html, is usable independently and used by other web component and non-web component libraries. The reactive custom element base class ReactiveElement can be used to build web components with a different template system like Preact.
So yes, "lock-in as low as possible".
Almost all of the things you listed are specific to lit, and lit only. So, people who will develop with lit will be locked in to lit. Because it's not like you can just pop the code you wrote with lit into stencil or ionic, and will just work.
> So you can port from Lit to something else component-by-component.
I personally saw a huge project ported from Angular to React basically doing the same. It's not a testament to lit. It's what people have been doing since time immemorial.
Do Angular and React components talk to each other? Lit and other Web component frameworks can share components. To refactor Lit components into other web component framework or raw web component is orders of magnitude easier than converting Angular <-> React.
You can make them talk to each other. Depends on what exactly you need, how components and projects are structured etc.
> Lit and other Web component frameworks can share components. To refactor Lit components into other web component framework or raw web component is orders of magnitude easier than converting Angular <-> React.
You're putting a false equivalency between "talking to each other" and "refactoring one code base into another".
Preact, Svelte, Vue, and Angular can all be compiled to web components. How easy do you think it would be to convert app code between each of these frameworks? Convert that code to and from lit?
you can make almost whatever you want with code but they weren't build for that. Besides that, both angular and react are bloated software, adding another layer to create web components version of their components is just more bloat. It took some time for chariots to be considered a legacy way of transport but you can't fight its obsolescence just by saying that chariots will also carry you from one place to another.
Lit is basically a library that makes use of the native Web Components APIs in modern browsers. If someone else would make a similar library, it would most probably have similar functionality.
So I don't really see the issue with vendor lock-in here. Most logic regarding your app is not gonna have anything to do with Lit. Only the way the templates are structured is a bit specific. But it is way less specific than JSX. Because it's just normal HTML with a few specific attributes to easily bind event listeners on HTML elements.
So Lit basically just gives you easy access to powerful browser APIs, making it really easy to build complex webapps without using a big framework.
Indeed.
> Lit is basically a library that makes use of the native Web Components APIs in modern browsers. If someone else would make a similar library, it would most probably have similar functionality.
Maybe, similar, perhaps.
The meat of the matter is this. There are a lot of things in lit that are specific just to lit. They have nothig to do with web components. Custom DSL. Tasks. Reactivity. etc. etc.
So yes, when you're buying in to lit, you're locking yourself to lit, and the way lit does things.
For example, Stencil, another popular web component library, is completely different from lit: it uses a different templating mechanism, a different data binding mechanism etc. etc.
> So Lit basically just gives you easy access to powerful browser APIs
No, it doesn't give access to those APIs. It goes out of its way to hide those APIs and provide a different, ore ergonomic API on top.
And there are actually not that many things specific to Lit, and it's only things that would need to be solved in some kind of (opinionated) way anyway. But I think Lit does it in an elegant, intuitive and flexible way. You might like it or you might not.
I don't know Stencil, but looks nice. But their main npm package [0] is 47.8 MB, whereas Lit [1] is only 105 KB. So I would say that there are a lot more things specific in Stencil then there are in Lit. Which would probably make it harder to move from Stencil to Lit, than it is to move from Lit to Stencil.
Literally everything that's on the web is using "native technologies and strategies". Because there's nothing else.
So, extraordinary claims require extraordinary proofs. I eagerly await a description, with examples, of how much work there is to convert a non-trivial lit component to Stencil.
Why non-trivial? Because trivial components are easy to convert from anything to anything. You could convert a trivial React component into knockout.js, then to Angular, and then to Lit, and back, in under half a day even if you've never worked with those technologies before.
> And there are actually not that many things specific to Lit
I've listed a few of them. They are opinionated enough that lit code and stencil code are completely different and incompatible.
Besides, there are a dozen or so libraries and frameworks that can, or do, compile to web components. Their code is also different from lit's.
> But their main npm package [0] is 47.8 MB
This is completely beside the point. We're not discussing Stencil, or the opinions its authors may or may not have taken.
Lit doesn't need JSX and a virtual DOM, which are React specific technologies. You have to design your React components with these technologies in mind. With Lit there are much less dependencies like that, so your code will be less designed for a specific framework, making it easier to move to another framework that doesn't impose a specific workflow.
But lit needs lit's custom DSL, lit's custom reactive components, custom everything.
Even in their most trivial example literally everything is custom, and specific to lit:
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
// custom lit-specific decorator
@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
// custom lit-specific css function
static styles = css`
:host {
color: blue;
}
`;
// Custom, lit-specific reactive properties
@property()
name?: string = 'World';
// custom lit-specific render function
render() {
// custom lit-specific DSL
return html`<p>Hello, ${this.name}!</p>`;
}
}
So. So, extraordinary claims require extraordinary proofs. I eagerly await a description, with examples, of how much work there is to convert a non-trivial lit component to Stencil.BTW. Claiming that Virtual DOM is somehow a react-specific technology (and that somehow apparently affects conversion from React to something else) really shows how much you understand about the topic at hand.
Edit: hint: that is why Lit's library is also much smaller
I: ask to show me how converting from lit to something else is easier as you claim
You: ignore everything completely and answer a question literally no on asked, and pretend that the only custom thing is the DSL.
Yup. My choice to completely ignore you in a sibling discussion was correct.
On the other hand, after a few years of engaging with web component defenders, propoenents and propagandists I'm not surprised in the least. This behaviour is on par with the rest of them.
No. I'm calling it as it is. I don't pretend that something isn't a framework when it has all the same things that the frameworks they so love to vilify do.
> The Lit team are huge advocates of aligning with native standards
Which of the things that lit provides are native standards current or future? Its template DSL? Its custom decorators? Its data binding system? Tasks? Directives?
Not to mention the usual workarounds like support for SVGs https://lit.dev/docs/api/templates/#svg
This is one reason why we care so much about low coupling and lock-in, and a small, easy-to-understand codebase. You should be able to migrate away from Lit very easily, and fork or maintain it if necessary. We're also trying to build up our non-Google contributors.
When Custom Components v0 was barely released Google re-wrote Youtube with Polymer. Where's Polymer now?
> You should be able to migrate away from Lit very easily
And that should comes from which part of lit? Custom DSL? Custom decorators? Custom data bindings? Custom tasks?
As I wrote elsewhere, Preact, Svelte, Vue, and Angular can all be compiled to web components. How easy do you think it would be to convert app code between each of these frameworks? Convert that code to and from lit?
So because it's small and doesn't need much new features, it's also easy for the community to maintain it. I think not much has changed in the Lit library over the last few years. So it's also easy with upgrading.
So experimental that Google even had a Polymer Conference and was promoting it heavily, like it promotes lit now.
> because Lit doesn't want to be a opinionated framework
For a "non-opinionated" framework it sure does have a lot of opinions: custom DSL, custom directives, custom decorators, custom way of building elements...
- reactivity. Specific to lit
- declarative templates. Specific to lit
- SSR. Specific to lit.
- context. Specific to lit.
- tasks. Specific to lit
- directives. Specific to lit
"littlest amount of tools"
Splitting hairs
> The others are very basic functionalily for what you expect from a framework like Lit.
Indeed. Framework like Lit. These mental hoops and shenaningans lit defenders have to jump and go through to pretend lit is somehow not a framework and somehow different.
Literally the very first example you get on lit's own page show how invalid all those claims are: https://lit.dev/docs/components/overview/
Remember the definition of a framework: a framework is just a library that does not play well with others.
class MyElement
extends HTMLElement {
}
window.customElements
.register('my-element',
MyElement)
(the hyphen in the name is idiosyncratically required by the custom element spec).As such, "HTML web components" (aka custom elements, which have be around for many years) are targeted at developers not hypothetical web users/authors. Then what is the point? When you're relying on JavaScript anyway, you have already much greater freedoms - syntactically and otherwise.
Custom element declarations and custom vocabularies can make sense as a means to organize and isolate authoring concerns when you're creating hypertext documents. The fact alone that you have to use JavaScript for registering custom elements make them a non-starter though (ie consider an authoring tool which surely doesn't want to execute arbitrary JavaScript in client docs).
It's not that there's no precedent either. SGML has a mechanism for parametric macro expansion where your element is syntactically replaced into an arbitrary markup fragment, with type-checked arguments (= attributes at the call site, just as with custom elements), and with type-checking the resultant expansion in context (ie. checking whether the expansion adheres to the content model at the expansion site), also having of course the capability to include JavaScript. Moreover, SGML actually can serialize/re-parse such markup whereas HTML's preliminary APIs for fragment parsing won't deal with contextual tag inference at all.
<script type="module">
import lib from "/path/to/my/lib.js";
for (const target of document.querySelectorAll(".target")) {
lib(target);
}
// TODO: Watch for DOM change and apply lib.
<script>
<span class="target">Some Input</span>
Or I can simply ask them to import my web component module and use the custom element: <script src="/path/to/my/web-component.js"></script>
<my-lib>Some input</my-lib>
The latter certainly feels like a superior API for my users. <script type="module">
import lib from "the-lib.co.uk";
lib({ target: ".target" });
</script>
If your users might not want you to watch the _whole_ DOM for performance reasons: <script type="module">
import lib from "the-lib.co.uk";
lib({
target: ".target",
root: ".all-the-targets-show-up-here"
});
</script>
And if your users might already have a mutation observer in their apps: <script type="module">
import lib from "the-lib.co.uk";
lib({
target: ".target",
observer: theAppObserver
});
</script>Aside: Don’t you need to pass the library function into the callback while constructing the mutation observer?
import lib, { mutationObserverCallback } from "https://my-lib.example";
lib({
target: ".target",
observer: new MutationObserver(mutationObserverCallback),
});
Honestly, this feels like so much magic compared to a simple: <my-lib>Some Input</my-lib>
where I handle update in my library by listening to the slotchange event.Searching for this I found some kinda hacky solutions on Stack Overflow which use fetch to load html/css at runtime.
- Responding to being attached to the DOM
- Encapsulating DOM nodes (the Shadow DOM)
- Scoped styling (Shadow DOM + User defined stylesheets + CSS parts)
It is definitively not a templating engine. It doesn't provide any new APIs for creating DOM nodes, or mutating them a la React or handlebars or lit-html. Templating is basically the "next step up the stack". Using shadowDom.innerHTML = `...`; is basically a stand-in for having an actual templating engine.
There is work going on, developing a native templating system in the browser which may interest you. It's called the DOM Parts proposal and you can find info on it here: https://github.com/WICG/webcomponents/blob/gh-pages/proposal...
html`
<this is all syntax highlighted>
`Lit is a bit too "frameworky" for my taste, I generally just use vanilla classes that extend HTMLElement and have a render function that renders the lit-element template to the light dom.
Something like:
import {html, render} from 'lit-html';
class AppComponent extends HTMLElement {
connectedCallback() {
this.template = () => html`<markup here>`;
this.render();
}
render() {
if(this.template)
render(this.template(), this)
}
}
customElements.define('app-component', AppComponent);https://developer.mozilla.org/en-US/docs/Web/API/Web_compone...
Using innerHTML isn’t specific to Web Components in any way. It was and is a quick & dirty way to dynamically insert HTML.
Can you explain what you mean by that?
connectedCallback() {
this.shadow.innerHTML = `
<p>Hello from a web component!</p>
<style>
p {
color: pink;
font-weight: bold;
padding: 1rem;
border: 4px solid pink;
}
</style>
`;
}But there several alternatives:
1. Use DOM APIs (createElement).
2. Fetch the HTML as a separate resource.
3. Have the HTML in a separate file and bundle it with a bundler.
4. Use React/JSX.
5. Use a preferred templating library of your choice.
You can do whichever of these you like the most
I would really like it if we reached a point where a web project only had a single dependency that's a bundler like vite that you point to an index file, and that's it.
Oh and here's a readme example of how to set that up if you're using pnpm v42 with sveltekit v2 and webpack v99. For any other combination of the 5 packagers, 19 bundlers, and 56 frameworks commonly in use and giving examples like this recommending each other - have fun.
Can you elaborate further ? Anything that removes dependencies from external libraries is a huge plus for me so I am curious as someone who is not great at JS.
As you read the following, keep in mind that at this time Web Components have been in development for almost 12 years.
The core is just three standards, CustomElements, Shadow DOM and HTML Imports (already deprecated and removed in favor of JS-only imports). And people will go out of hteir way to sell you the idea that this is lightweight, all that you need etc.
However.
They've already spawned half a dozen new web standards just to deal with issues they inflicted on themselves. None of these issues are present in any other solution/lib/framework that exists.
These range from the fact that web components cannot participate in forms (fixed with a new spec: https://web.dev/articles/more-capable-form-controls) to their inability to share stylesheets (fixed with a new spec: https://web.dev/articles/constructable-stylesheets) to whatever else (hard to keep track).
They will need at least 20 more new web standards to fix other issues that, once again, are not a problem for literally every other framework, library, or hand-written code under the sun: https://w3c.github.io/webcomponents-cg/2022.html.
Among my favorite ones: a web component button cannot be a submit button in a form; you cannot reference an id inside a shadow root, and that breaks ARIA.
To call them half-baked is an understatement. They are badly thought-out, badly implemented APIs with no forethought or visible planning, and literally no end goal in sight. The people building this met to hash out what more is needed for them to be complete only last year, 11 years into development. All development before that looked like ad-hoc patches by people surprised that a yet another thing doesn't work, but is sorely needed.
> But in Lit, input fields can't communicate to a form in a different Shadow DOM
Indeed. They break the most basic functionality that exists in the browser. This issue doesn't exist in anything else. And they need a separate new web spec to barely fix it.
Does it have to do anything with React? No.
> That's logical of course because Lit is made to work with modern Web Component APIs
Yup, so modern that they creak basic browser functionality and need 20+ new web specs to fix issues that don't exist in literally anything else.
Yes, yes they do break semantics.
1. If you just put the input in a web component, it will not appear in the form. You have to manually add it. https://web.dev/articles/more-capable-form-controls
2. If you have an input in Shadow DOM, it cannot be referenced with a label from outside that shadow DOM. That breaks ARIA, and will be fixed god knows when with a new cross-root ARIA spec.
This has nothing to do with libraries, or React, or whatever you imagine. These are basic browser behaviours that everyone expects to work out of the box, and they are broken.
> that a different library has a different workflow.
This has nothing to do with libraries. This is basic browser functionality.
> If you addopt the correct workflow, you won't have any of the issues you're having.
No "correct workflow" can cover the fact that these behaviours are broken, and need 20+ new specifications to fix.
There's no correct workflow that will make cross-shadow ARIA work. There's no correct workflow that will make a custom component participate in forms if the author didn't add that functionality. There's no correct workflow that will make your custom button be able to work as a submit button. And so on, and so on, and so on, and so on.
Edit. Note: if you took the time to actually read the report from people who shove webcomponents into the browser, you will see that even they admit how much of an issue all this is, and it has nothing to do with React or libraries, and everything to do with self-inflicted wounds by a badly thought-out design: https://w3c.github.io/webcomponents-cg/2022.html (emphasis mine).
Note how none of these are issues for anything built for the browser. These are issues only for the half-baked, badly-designed web components.
--- start quote ---
This document tries to highlight the main features that are lacking from the web components spec that either block adoption for more developers and frameworks, or cause pain points for existing developers.
It's worth noting that many of these pain points are directly related to Shadow DOM's encapsulation. While there are many benefits to some types of widely shared components to strong encapsulation, the friction of strong encapsulation has prevented most developers from adopting Shadow DOM.
...
Shadow boundaries prevent content on either side of the boundary from referencing each other via ID references. ID references being the basis of the majority of the accessibility patterns outlines by aria attributes, this causes a major issue in developing accessible content with shadow DOM.
...
The form-associated APIs currently have no way for a developer to behave as a custom submit button.
It is currently unclear how form-associated custom elements should participate in the autocomplete lifecycle despite there being an API for that purpose.
...
Many web components could be implemented without JavaScript, taking advantage of encapsulated DOM and styles. However, web components cannot currently be rendered by users who have JavaScript disabled.
... ad infinitum ...
--- end quote ---
What people usually try to do is to somehow open up the Shadow DOM for certain things, to make some things allowed to pass, or global. But I think might be a bit holding on to an old familiar habit. And I think there are better ways of structuring a project with Web Component in mind.
For example, you can create a base class from which all your components extend, and put the base style in that base class, and have your components add style on top of that. I implemented this method in a tiny library which makes it easy to use [0].
But yeah it is hard for most developers to adopt a new way of thinking. And as long as the new way doesn't provide that much improvement over the old way, it won't get adopted. But I think the idea of Web Components still needs to be integrated more in the web dev community. And maybe they will manage to make some changes to make the adoption easier.
And besides that, of course Web Components still have lots of other issues. But for me it has reached a point where I don't feel the need for React anymore. I rather live with some quirks in Lit than doing things with React, which just feels clumsy to me now.
It's not "a new way of thinking". Things like functioning inputs, buttons, and ARIA are basic browser functionality.
> And as long as the new way doesn't provide that much improvement over the old way, it won't get adopted.
Not only it doesn't provide improvment, it needs 20+ new web standards to fix the issues that literally nothing else has. I've gone ahead and quoted directly from the report made by people who keep developing these specs.
And yet you've ignored literally everything I wrote and keep answering something entirely else.
> of course Web Components still have lots of other issues.
Indeed, they do. I listed a bunch of them. And yet you keep pretending that I'm talking about "React workflows" or something.
This is pointless until your read what I write, and not what you think that I write. Until then, adieu.
It seems you are only focussing and zooming in on the issues and limitations. And there are issues and limitations in any technology. I think it has to do with adopting a different way of thinking and structuring. All those issues you listed are either non-issues with the correct workflow, or are easy to work around.
And hey, I'm not saying you or anyone should use web components; if you like React better then just keep using that. But I don't see why you would critizice this technology so much. Remember that creating new web specs and standards takes a lot more time than to build an independent framework like React. So yeah the development and adoption is slow, and that might frustrate you. And as long as you think it's not good enough yet, you can just keep using React. But I personally find that it is mature enough for me to use it in combination with Lit instead of React. And I'm very satisfied about it. It does take some investment to learn a bit about how Web Components work.
The question literally was: "Can you elaborate further on why web components are a half-baked solution".
Did I say anything about React? No. Did I say anything about lit? No.
Stop making up what you think people are saying and start reading and understanding what people are saying.
> And there are issues and limitations in any technology.
Yes. Yes they are. And web components are literally the only technology that after 12 years in development needs 20+ web specs to not break basic functionality that literally no other technology breaks.
This has nothing to do with react. This has nothing to do with lit. Read what I wrote and not what I think I wrote.
> if you like React
> framework like React.
> keep using React.
> combination with Lit instead of React.
Jesus Christ.
Literally nowhere was I talking about React or lit. Not a single one of my comments in this thread had anything to do with either.
And yet you keep arguing with the voices in your head that are telling you that I'm talking about React.
> It does take some investment to learn a bit about how Web Components work.
So go ahead and learn about how they work, why don't you? Or, rather, about how they don't work, and why after 12 years they still need 20+ new web specs to to the most basic things.
Edit: from now on I will disengage from this conversation and sibling ones, because it is useless and pointless.
It's interesting how much of a component system one can build only by using `<input>`, `<label>`, and `<form>`. For the remaining interactive elements that can't be build easily without JavaScript I plan to use Web Components.
For me this is a reasonable 80-percent solution. But for web applications with high requirements I would use React and React-Aria components/hooks.
This is 100% correct. I think a lot of the disappointment about web components comes from a mismatch in expectation between the spec writers and what people when they think "components".
Web Components allow you to make new HTML elements. That is, you can make new kinds of DOM nodes out of other DOM nodes. This is powerful, but the resultant API is still a DOM-level API, and not a full templating system like React.
On the other hand, because all these templating systems (React, lit, svelte, etc.) ultimately boil down to a series of DOM manipulations, this allows you to create a kind of element that can be plugged into all of these systems.
Custom elements are a lot more akin to the C ABI. Than a fully featured framework.
However, anything can be passed as a _property_. React operates on properties by default (hence `htmlFor` and `className`, not `for` and `class`). In fact, react's upcoming improved WC support will first do an `in` check on a property name before falling back to attribute. This is similar in vue etc.
So yes if you're only authoring plain html you're confined to primitives, but if you're using a framework or WC lib, they almost certainly support properties
It's a 161-line file that's easy to reason about and modify as needed.
This is unfortunate because the Shadow DOM is essential if you want to work with child components slotted into it from the outside. This opens up many use cases.
There is one alternative which is not being considered; it's possible for components with a shadow DOM to inject their elements into the Light DOM (where they can be styled/skinned externally with CSS), you just have to make the user slot a div (or other element) from the outside to act as a 'viewport' so that the component can inject its children into it (instead of injecting them into its Shadow DOM).
With this approach, you can still access slotted elements. It's an ideal pattern for situations where you want the component to generate child elements from a slotted template.
I wrote an article about this approach here: https://dev.to/jondubois/web-components-the-template-viewpor...
In cases where you want to introduce just a little styling, the CSS Parts API is really cool: https://developer.mozilla.org/en-US/docs/Web/CSS/::part
const myStyles = litStyle(css`h1 { color: red; }`);
class MyComponent extends myStyles(LitElement) { // ..}
You can store the style definition in a separate file and import it and use it for the components that you want it for.
We should hate both equally.
Either approach is frankly an embarrassing way to build complex interactive applications. Web tech is just way too low level, batteries not included. We've added mountain of complexity to deal with it but it doesn't hide that the fundamentals are broken and that productivity is low.
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'm all for web components!
Is it a bad thing that organizations specialize on those concerns? Seems like the most obvious place to slice responsibility, even if it means every feature is split across two teams. After all, the performance of the backing store is impacted not by neigbhoring logic in the UI infrastructure but by neighboring state in the backing store; it should be someone's job to be looking at the backing infrastructure holistically and not sliced as "frontend-backend feature A, frontend-backend feature B, etc."
Any web component easily plugs into React, svelte, lit, etc. Existing components written in those frameworks can pretty easily be wrapped in a web component. It's a common base-layer we can all use.
If you attempt to build a web app using just web components and no libraries, you'll quickly find that you're doing lots of manual DOM updates (no templating engine) and lots of manual state management (no data management API).
As an ABI, it's wildly successful. It does, in fact, just work. It's just very low level.
where? I work at a fairly big org that some time ago decided to bet on web components. Everyone I work with (including me) hates it.
For a backend service, it doesn't matter if you use no framework or one or five, other than pain for your developers having to learn the complexity; storage is so cheap it's nearly free, executable RAM slightly less so, and the end-user doesn't care how complex your server is.
Frontend code gets pushed over a wire to your end-user. If you're using three overlapping frameworks to build one web service frontend purely because someone in your team likes Svelte and someone else likes React... that's a problem. You'll be pushing more bytes than you should to your end-users, and that cost doesn't scale the right way; in fact, the more popular your app, the more redundant bytes you'll be passing.
We don't want to enable multiple frameworks in the UI. We should be settling on one and committing everyone on the frontend team to using it (ideally lock-stepped to the same version of it) within each app-shaped thing we create as a frontend team.
- Gradually migrating from one framework to another
- Including interactive "islands" within a static or server-side rendered page
- Using a dependency only available in one framework from within another (this works best if the dependency framework has a small footprint — e.g. using Svelte within React is better than vice versa)
I am a backend dev so I don't really want to get into the intricates of React or Angular. But if I need some reactivity for some complex element, I use lit. It's awesome
No, you wouldn't. Very few JS frameworks (however new they are) use web components as their foundation for many, many, many reasons.
> there are ways to build a web app other than writing the entire thing with a single JavaScript framework
totally agree, and reducing rewrites or making converting them into "strangler" rewrites is a huge win
on the other hand, it reminds me of the microservices hype a few years back. Just because you can, doesn't mean you should. Uniform languages, frameworks, coded patterns make things SO much smoother for an organisation.
I would repeat the author's words of warning: "you should not build a real app like this!". you should seriously think hard before adding this level of complexity to any production app
Interesting exercise. But now I wish she/he would one-by-one replace each framework with web components, so that in the end, there are no frameworks, only web components. As in, "Oh shit! We're going to abandon Vue (or React, etc.) and replace it with web components" or "We've been taked to only use web componentst, and no frameworks." What does that migration / transformation look like? Isn't that a likely scenario?
Web components will outlive JavaScript frameworks - https://news.ycombinator.com/item?id=38012662 - Oct 2023 (244 comments)
When your tech needs 20 more web specs [1] to fix stuff that literally no one has issues with, it means that you will be locked in to frameworks that solve these issues.
Literally right now Web Component SSR is a framework-specific barely working non-solution. Even the most ardent web component proponents have given up and advocate a framework lock-in with lit-html and others.
And they want to CAPTURE you. This is why 99% of frameworks have a deliberately monolithic and isolating architecture, where it's all in, or all out. You don't want to miss the revolution are you? Are you stupid? Of course you're not stupid. So now your apps are locked in.
And Web Components won't stop frameworks from finding highly specific ways to be "useful" in a way where you need to do everything in them.
The solution is to know how to architect your app and stop believing marketing BS. Otherwise you'll keep fleeting to the Next Big Thing (tm) and never realize why it always ends the same way.
Web Components, once adequately specced, will become a compile target for higher level frameworks like React and Vue. That way you still get the benefits of higher level frameworks but the components can interoperate with each other. Vue CLI already supports compiling down to a WebComponent, though not sure how first class it is.
There are far too many lacking ergonomics to expect writing Web Components by hand to become popularized/convention
But the end state will be as a compilation target, not as a development platform.
And there are multiple other issues that make web components a very unattractive compilation target.
A good font should go out of my way in my opinion. When I don't notice it at all, the font is perfect. Android default fonts definitely fall in this category, for example.
I look at this stuff, and just think how sad it is that we haven't progressed beyond the state of gluing together freakin low level divs and spans within some JS code with callbacks galore.
For some reason I thought we'd have made it beyond that by 2023, and we could talk declaratively at a high level about the kinds of interfaces we want to render and where the data lives. Hopefully I'm wrong and the magic is buried under some Elm tree or a React library or something.
Something something get off my lawn...
Even in the old desktop world someone had to build the button widget (and all the others) that let you just throw stuff together in MFC or Swing or whatever.
Same today - someone has to build the web components in order for us to declaratively use them as high-level Lego.
Trouble is, there is no real high-level "general purpose" library of components beyond the core HTML elements (they'll get you a long way but there are notable things missing that we expect from apps, e.g. toolbars, menus, tab strips etc), and then the probably reason for their not being a general high-level library is that everyone wants something special for their websites (because historically websites have always allowed a lot of design flexibility, way more than your typical win32 app ever did for example).
I have several times really wished to have some solid "desktop style" components to use for things like menus, tool bars etc. That would make prototypes in electron/neutralino/tauri way way way faster to do.
This is why I hate making web UIs.
> then the probably reason for their not being a general high-level library is that everyone wants something special for their websites
This is why I hate using web UIs.
At this point it's hard not see this as a fully self-inflicted wound by the platform. I know there are component libraries, but the work always remains fragmented because someone decided that their buttons needed purple highlights while someone else's needed drop shadows.
And the devs seem to love staying at this low level of abstraction. Feels like they had to be dragged into the component model espoused by React et al over many years.
The biggest difference between other major platforms and the web, is that the web doesn't come with a default UI framework. The primitives for building UI on the web are pretty good though.
I do think the web is a lot easier to learn and the on-ramp is so much faster compared to older mobile UI frameworks, so it makes sense that they're sort of evolving toward the same patterns.
Building a declarative UI that compiles to that sort of declaration means building that from scratch. We do currently work like that though. React isn't that far and web components built into an element of that.
I think that's why frameworks like Tailwind etc. have got so popular -- they're much closer to the traditional native application way of styling. You set all the appearance details on the component and then you reuse the component, styles and all.
<Card>
<Avatar />
</Card>
This is talking declaratively at a high level about the kind of interface to render. Lots of component frameworks look like this example. Thinking "how sad it is that we haven't progressed beyond the state of gluing together low level divs and spans" sounds like a preconceived idea rather than a response to this article.By the way, I come from mid-80's BASIC on 8-bit home computers. You get off my lawn.
As a random example, HTML specifies that clicking a `<button>` inside a `<form>` automatically submits a POST request to the current URL. We can't change that because it will break a lot of webpages that rely on this default behavior, so now every component system built on top of <form> or <button> needs to prevent the default browser behavior, and there are so many more examples of things like this.
I’m not saying that old GUI component frameworks are vastly superior either. I just imagined that we’d have progressed well past the kind of approach the article is mentioning.
It’s a side rant, but I don’t think it’s entirely without merit.
Sadly elm hasn't really taken off but as far as frontend goes... it is just so simple together with elm-ui.
In my opinion, the community is lacking foundations, structure, and principles - something that would otherwise be instilled by formal education. JS is fantastic for exploring how programming works, it makes it easy to stitch things together, and the barrier to entry is basically zero; all you need is a text editor and a browser.
The flipside is that people regularly reinvent things, same sets of ideas get formed and re-formed, fall into and out of vogue over and over again. It is true that there is great complexity to be dealt with in this domain, but it feels like whenever the current set of tools and practices matures enough to face some of the hard problems, the instinct in the community is to "start from scratch" and before long some new shiny exciting thing appears. It shows a lot of potential, it's nowhere near facing the hard problems, people jump on the bandwagon, time passes, and the new set of tools and practices arrives at precisely the same place.
Web components have been in the works for a long, long time. The inherent competition between the major frameworks divides people into camps, which doesn't help. But, seeing the slow but steady progress in the web components realm indicates to me that they're apart from the patterns I was ragging on in the previous paragraph. They increasingly seem to deliver on the promise of creating advanced building blocks - perhaps one day a developer won't have to choose between a React Table, a Vue Table, a Gobagool Table, etc, but use a plain vanilla standard issue web component table to display their CSVs.
Most other GUI libraries is in a much worse state than front end web dev. That is probably also the reason why people use front end tech to make native apps today, because you can customize and make apps much faster than in basically any other way.
I think front end gui development is very easy today and very powerful as well and we have progressed a lot. Making stuff with web components isn't event that hard and if you want you can just use one of the many available syntax sugar libraries that exist like Lit and others.
INTERCAL could win with that feature set.
> why people use front end tech to make native apps today
Even when you have to deal with IT security losers to get apps installed, implementing it w/ web tech is extremely popular.
E.g. the #1 IDE by popularity [1] is a desktop app developed with web tech. (And yes, it was later ported to browsers, but it didn't start there.)
[1] https://survey.stackoverflow.co/2022#section-most-popular-te...
I'm certainly not going to suggest you're wrong. But I would like a sincere comparison between JavaFX (because I sorta know it) and "the web".
Maybe its unfair, since the Web is "just the DOM", an empty vessel many of the other techs are built upon, which is why there's so many options.
I'm not a front end person, by any means, and have been striving to make things work with FX. I mostly "get" FX, how it does things with its containers and layouts and properties and bindings and events and UI thread. It has CSS, I mostly don't use it (better living through gray!).
The web, honestly, intimidates me. It intimidates me with its vast variety, its complex build processes, the apparently galaxy of dependencies and tools and what not. Cross browser stuff terrifies me. And maybe that's the wrong reality, but that's the impression I get.
I'm managing to Forest Gump my way through FX, but I have a Java background.
The web feels downright cohesive, stable and well documented in comparison.
Why publish with restrictions when you can be free?
A simple example of this is most backend frameworks let you signal an error by throwing an exception. Good choice! Except when you go to throw one, you often have to choose from a bunch of HTTP status codes to bundle with your exception. I want to tell the user that validation failed, that's all. I should throw a ValidationError, not a HttpResponseError. They're not really abstracting anything, just adding a layer of structure, usually via libraries that are glued together.
Similarly, I've started to be of the opinion that the ideal HTTP request handler has very few HTTP-specific details in it, as the framework can introspect the types and annotations to determine how to pass data in and out. This is very different from typical frameworks which are happy to hand you raw requests and responses and tell you to do it all. Nothing wrong with that for special cases, BTW, except for people's tendency to mix HTTP, infrastructure (DB/messaging/APIs), and business logic in the same handler.
There will always be cases where you need direct control, but I've been moving towards stacks that automate most of this to cut down on the gunky makework feeling I get when doing webdev, and it has been integral to me actually enjoying the tooling rather than feeling like I'm filling out TPS reports. Currently that's Spring Boot + Kotlin, despite the enterprise stigma the JVM and Spring have.
While I agree, this is what we're stuck with. I have 25+ years of experience. Before JavaScript and CSS existed.
Now? I'm on an Angular 16 (moving to 17 SPA).
Worked with almost everything else in between.
I had all the same feelings. "We're doing what with JavaScript now??".
It turns out, it's not all that bad. TypeScript is pleasant enough, for me Angular is logical, and sure at the end of the day it's all just HTML/JS/CSS but what can you do?
We're stuck with it, unless WASM totally up-ends the entire thing and we have JIT'd languages like .Net and Java running on the client ... which is ...
Well, I'm not even sure what that is anymore. It's not HTML, JS, and CSS!
And yet it's so easy and forgiving that millions of 13 year olds learned it for their myspace and tumblr pages over the last 15 or so years.
Somehow, despite all the CS graduates and 20 year software engineering veterans bemoaning it, more and more people learn HTML, CSS, and Javascript every day while the number of people learning native GUI development struggles to keep up or outright fades away. I truly believe that most software engineers don't understand what ergonomic software or programming languages look like. They're blinded by their expertise
Maybe part of the problem is that the web is the only platform where people obsess over the size of deployed artefacts. Websites are certainly getting bigger, but they’re still an order of magnitude smaller than the average snap package or iOS app.
(^ feel free to copy-paste that to any HN thread)