Very cool overview and a great article—it’s fascinating to see how far web components have come. Data passing, interactivity, and state management still seem pretty tedious in vanilla, though!
Data passing seems inherently broken in web components, because all HTML attrs must have string keys and values. Such a model just can't be built on top of.
I'm late getting to this but someone emailed us to point out that your attempt at code formatting didn't quite work, so I fixed it, using the formatting markup documented here: https://news.ycombinator.com/formatdoc. Hope that's OK!
Presumably I've defined a .toString() method on w that will behave as I wish when implicitly invoked to perform this coercion.
If I haven't, then presumably I'll be satisfied with the inherited default behavior, which will probably look something like "<h1>[object Worker]</h1>".
If I care about this extremely contrived example case, in other words, I'll do something to handle it. If I don't, I won't. If I do, it's been easy for at least 25 years now; iirc .toString() was specified in ES3, which was published in March 2000.
If I want in the general case to append a child node to a parent (as here with the h1 as parent and the stringified interpolated value as child), I will in almost every case call parent.appendChild(child), where parent and child both implement Node, which is the parent class of Element. The result will correspond closely to the element tree which would be constructed by assigning a string like your example to some other element's innerHTML. (You are essentially using the browser DOM implementation as a templating engine. As sugar over a lot of createElement calls and piecewise tree construction, this isn't a terrible strategy! The JSX with which you're familiar is a more elaborate and more typesafe solution for essentially the same problem.)
Similarly, these references would be from the JS perspective a POJO with lots of seriously heavy implicit "render magic," so you can use them, as with any first-class Javascript value, as function arguments parallel to but a superset of what React does with its props. See the MDN documentation on Node.appendChild (and Node, Element, HTMLElement, etc) for more: https://developer.mozilla.org/en-US/docs/Web/API/Node
If I want to represent the state of a worker thread in the UI, a problem I first recall solving over a weekend in 2016, the way I do it will end up closely resembling the "MVC pattern," with the Worker instance as "model,"
the DOM element structure as "view," and a "controller" that takes a Worker and returns an element tree. Even if I'm using React to build the UI - which I have also been mostly doing for about as long - I am still going to handle this translation with a library function, even if my component actually does accept a Worker as a prop, which it actually very likely will since that will enable me to easily dispatch effects and update the UI on changes of worker state. I might define that "business logic" function alongside the component which uses it, in the same module. But React or vanilla, I won't put that logic in the UI rendering code, unless it is trivial property mapping and no more (unlikely in this case, since any interesting worker thread state updates will arrive via message events requiring the parent to keep track in some way.)
will just stringify the worker and pass that string to the `my-component`. To get the worker instance to be passed correctly, I'd need to do something like
Every time I go back to give Web Components another 5 minutes, I hit this point where using lit or a lit-like would take a lot of the pain of the problems Web Components don't solve and have no planned solution for away.
But once I decide to cross the "no dependencies" line, using something like Preact + htm as a no-build solution would also take the most of the rest of the pain away, and solve many, many other problems Web Components have no solution and no planned solution for.
So this isn't even a question about web workers, it's a question about how to prop-drill non-string/number data through multiple layers of web-components.
Tbh, I'm not sure there's a way for that. But why not just define a method in your target child component and pass the worker in there?
Yeah, I think the original question was a bit weirdly worded which made people focus on web workers rather than complex data in general.
You can use properties (as opposed to attributes) as I demonstrated, and you can use methods like you suggest, but these are both verbose and limited, and add an extra "the component has been created but the props haven't been fully passed" state to the component you're writing. Imagine a component with maybe five different props, all of which are complex objects that need to be passed by property. That's a lot of boilerplate to work with.
I showed earlier how it takes multiple lines and some fiddling with DOM to set a simple property with vanilla web components. Sure, if you're using a framework like lit, you have access to template binding, but at that point you might as well use an equivalent framework like SolidJS or Svelte which just skips the web component layer.
You're right, it is just a method call from a class. Nothing interesting or new. And that's exactly why I like it! I like me FE code as boring, unimpressive and as simple as possible.
The "old ways" are relevant again here. Just like in the old Progressive Enhancement era (the jQuery/Knockout era) to pass objects/arrays as attributes you use the special data- attributes and the `dataset` property. As the old ways suggest you probably still want to account for strings and serialize/deserialize via JSON for the most compatibility with things like static rendering, but many a jQuery script "upgraded" such things in-place.
Imagine 50 years ago: “Data passing seems inherently broken in Unix, because all processes must use strings for input and output. Such a model just can't be built on top of.”
As a matter of fact, Tcl/Tk has been doing exactly that since the 1990s. Of course, Tk has been "borrowed" by other languages, Python's tkinter is well-known. Any language using Tk widgets still has to input options in text form. Obviously that's not particularly difficult to accomplish.
Well it's not a lie, it's how HTML and the DOM work. Attributes are strings, and what you write in HTML will be passed as attributes.
You do also have access to properties, but only in Javascript — you can't for example write something like `<my-custom-component date="new Date(2024, 02, 04)">`. That means that if you need to pass around complex data types, you need to either manipulate the DOM objects directly, or you need to include some sort of templating abstraction that will handle the property/attribute problem for you.
This is my main criticism of web components. At the simplest levels, they're not useful — you could build this website very easily without them, they aren't providing a particularly meaningful abstraction at this level of complexity. But at the more complex levels, they're not sufficient by themselves — there's no state management concept, there's no templating, there's not even much reactivity, other than the stuff you could do with native JS event emitters.
As far as I can tell, the best use-case for web components is microfrontends, which is a pretty useful use-case (much better than iframes), but it's very niche. Apart from that, I really don't see why you wouldn't just write normal Javascript without worrying about web components at all.
It is absolutely a lie, because web components can handle arbitrary data just at much as any framework.
You're holding web components to a higher standard here in expecting them to take arbitrary data in HTML when HTML itself doesn't support arbitrary data. Notably you can't assign arbitrary data to framework components from within HTML either, so how are web components any more limited?
The original comment was that "all HTML attrs must have string keys and values", which is completely true.
The point of web components is that they create normal HTML elements. So it makes sense to consider what the value of them being normal HTML elements is. You can write them directly in your HTML source code, for example. But if you do that, you only get access to attributes and not to properties, and therefore everything needs to be strings. Alternatively, you can treat them as DOM nodes in Javascript, at which point you get access to properties and can use non-string values, but now you've got to deal with the DOM API, which is verbose and imperative, and makes declarative templating difficult.
Yes, we could compare them to components from other frameworks, but that's honestly an absurd comparison. They're simply trying to do different things. Frameworks aren't trying to create HTML elements. They're trying to reactively template the DOM. It's just a completely different goal altogether. The comparison doesn't make sense.
At that point you're not using HTML anymore, you're just calling html() in a fancy way, and that's the whole point of the complaint, that custom-elements are not good at being plain HTML even though that's like its whole thing.
That's not web components, though that's lit-html. That's an additional library you need to pull in to manage your web components. Which kind of ruins a lot of the stated benefits of web components. If I need a framework to write my web components, why not just pull in a different framework that skips the web component level completely and just outputs HTML/CSS directly? What is this intermediate step actually bringing me?
In theory it is completely an implementation detail, I agree. In practice, it's bloat. If every web component I use in my project might pull in a completely different framework behind the scenes (and worse: if those web components depend transitively on other web components that pull in yet more frameworks), then I now have to deal with all of those different frameworks. Each of them will load more bytes and make my page's startup time slower. Each of them will have their own state management systems. Each of them will have their own approach to templating. Each of them will behave subtly differently in practice.
Why bother when I can just write everything in a single framework?
State management is arguably one of the most important problems to solve. That's why we use React and Vue. You can very easily build complex user interfaces at scale with those frameworks. And you can even take web components along for the ride if you want to. They are partially overlapping solutions for different problems.