If Web Components are so great, why am I not using them?(daverupert.com) |
If Web Components are so great, why am I not using them?(daverupert.com) |
My impression previously was that it brings more power back to native HTML, and lets you extends HTML elements by defining your own modular custom components instead of doing so in JS the `modern` way. Instead, turns out it's doubling down on JS to create elements, and was actually targeted towards these framework in the first place.
Curious what you feel defining a custom component in the browser would look like, if not with JS?
Anyway like I mentioned, I didn't get the chance to play with it and understand its purpose. Misconception -> higher expectations -> disappointment.
Those of us who programmed in say VB6 or .NET winforms back in the day may remember that you could purchase some "ready made" grid-table editor with lots of functionality and pretty.
But I haven't seen that open source nor commercial.
It's amazing how much the current web frontend dev have to reinvent over and over again.
I'm partially relieved that web components never became that popular, because that would have made accessibility on the web worse.
On one hand, there are some use cases for this new complexity. On the other hand, it's difficult to see how the vast majority of say, React users, would ever benefit from this complexity, given that a lot of them don't seem to have problems that are solved by web components.
If we were going to focus on adding even more new browser features, I think it'd be better to focus on stuff that helps close cross-site scripting vulnerabilities, or provide better approaches towards anti-SPAM/anti-abuse than lazy, harmful remote attestation. (I mean hell, I'd love to see some very basic hashcash functionality as part of the browser; Personally, I think that would be an interesting idea for anti-SPAM, and nowhere near as catastrophically harmful as WEI.)
Web Components mostly seem to be just adding complexity for the sake of avoiding simplicity.
Certainly there could be some use for custom ones, just as you can make custom native widgets. But there shouldn't be that much need that often if more of the standard functionality was available (and standard).
They did add color picker, date picker, range, and the datalist to kind of sort of emulate a combobox, so some credit for that. But unfortunately they're not consistent across browsers and may or may not be consistent with the native platform.
I’m not sure I understand the hostility here. Isn’t the point of the web to be open standards that reach consensus?
They do a lot, but stop just short of being useful without something of a framework on top. I tried hard to use them directly, but found that it was untenable without sensible template interpolation, and without helpers for event binding.
Here's my shot at the smallest possible "framework" atop Web Components that make them workable (and even enjoyable) as an application developer:
https://github.com/dchester/yhtml
It's just ~10 SLOC, admittedly dense, but which make a world of difference in terms of usability. With that in place, now you can write markup in a style not too dissimilar from React or Vue, like...
<button @click="increment">${this.count}</button>
Whereas without, you need to bind your own events and manage your own template interpolation with sanitizing, handling conditionals, loops, and all the rest.If we could get something in this direction adopted into the spec, it would open up a lot of use cases for Web Components that it just can't address as-is.
The bigger problem is that the web platform stakeholders (IE the committees that industry influence and the browser makers) simply didn’t listen at all to what regular developers have been saying about what they want from the web platform w/r/t better built in platform provided primitives. It’s seems to me like web components have largely not taken lessons of component based development as everyone has come to expect. It’s still a weird imperative API with no rendering or data binding primitives
[0]: https://github.com/WICG/webcomponents/blob/gh-pages/proposal...
Many modern devs are too deeply involved in the React cult to see past its many over-engineered and flawed abstractions and struggle to see the simpler approach which necessitates adhering to some basic but highly beneficial architectural constraints.
As for events, binding is really very easy to do and it's already localised to the component so managing them is trivial.
Loops are also trivial; you can simply use Array.prototype.map function to return a bunch of strings which you can incorporate directly into the main component's template string. In any case, you can always use the native document.createElement and appendChild functions to create elements within the component and add them to its DOM or shadow DOM.
I've built some complex apps with plain HTMLElement as a base class for all my components and found is much simpler than React without any unexpected weirdness and using fewer abstract technical concepts. Code was much more readable and maintainable. I didn't even need a bundler thanks to modern async and defer attributes of script tags among others.
I think the reason why people are using React still is just marketing, hype and inertia. The job market which is gatekept by non-technical recruiters demands React. It's all non-tech people making the big decisions based on buzzwords that they don't understand.
I would not say it's easy. Considering your adversaries are very motivated to do XSS and the web platform is very complicated.
> It could be argued that automatically sanitizing everything including already safe data types like numbers and system-generated content adds an unnecessary performance overhead for certain projects.
I don't think there's a substantial performance loss from doing a type check on a value to see that it's a number, and then putting it verbatim into the output (within your sanitization code).
I don't know what "system generated content" is, and I'd argue that neither does a framework. Which means the far safer route is to assume it came from a user by default and force the dev to confirm that it's not from the user.
> Loops are also trivial; you can simply use Array.prototype.map function to return a bunch of strings which you can incorporate directly into the main component's template string
Combined with the "it's fine" mentality on data sanitization, it's concerning that we're using the term "string" in relation to building DOM nodes here. I hope we aren't talking about generating HTML as strings, combined with default-trusted application data that in most applications, does in fact come from the user, even if you might consider that user trusted (because it's Dave from Accounting, and not LeetHacker99 from Reddit).
And there are libraries that handle all of this for you! But then you have tooling and non-standard syntax that puts you a lot closer to other JS frameworks.
And once you're in that space, the benefits of web components may or may not stack up against all the features and your team's pre-existing knowledge.
<button onclick="innerHTML++">1</button>You can even use React-like templates. You need a 500-line lib to do that: https://github.com/wisercoder/uibuilder/tree/master
Where is the next wave of tiny libs that can make the web feel responsive again?
https://github.com/cyco/WebFun/
Example for a `tsx` component:
https://github.com/cyco/WebFun/blob/master/src/ui/components...
Pure TypeScript / scss components.
And yes, I understand that when you program using one of these frameworks your explicitly pointing out what pieces of state will change and when, but in my professional experience, people just code whatever works and don't pay much attention to that. In the naive case, I feel like the browser would probably beat react or any other framework on re-renders every time. In the naive case where developers don't really disambiguate what state changes like they're supposed to. Are there any benchmarks or recent blogs/tech articles that dive into this?
I am using them, because I've landed in a few projects where adding a JS build step is too heavyweight for very minimal JS needs. But here are my gripes:
1. Templates are way too complicated to be useful. I have no idea what they were thinking here.
2. The shadow DOM not inheriting styles means I effectively can't use the shadow DOM for anything useful. My ideal use case would be that I could child elements to the current component into the shadow DOM. This allows me to do (for example) things like have a <tab-area> where the user can define <tab-> child elements which in turn can contain arbitrary content: when the user selects a tab I copy the contents of the <tab-> tag into the shadow DOM so that that's what becomes visible. But if I do that, the element content becomes unstyled. There are a bunch of really useful elements that are just completely blocked by this poor design choice. As a result, I just don't use the shadow DOM, but then I have to mutate the tabs to make them visible in place, and that means changes to the HTML of tabs can potentially cause issues for my <tab-area> and vice versa.
The first is a missed opportunity, but it's fairly easy to just not use them. The second problem is one of the worst API decisions I've ever seen, which would have been detected very quickly if anyone designing this API had been arsed to try to use their own API.
In my entire career, I've had one use case that web components were perfect for: framework agnostic component interfaces. I specced the use case for a product at Stripe [0] and it was wildly successful. What it solved was avoiding the need to ship a library for each framework to embed our components: you just used HTML. Web components are perfect for this, because they are the lowest common denominator that everyone supports. Even if you're not using a framework, you can still embed the components with no real effort. That's the best I can say about web components, though.
[0] https://stripe.com/docs/connect/get-started-connect-embedded...
The fact that scoped elements are not yet part of the standard is quite a turn off. Every web component is global and the first one that uses a name wins. Even if you are not competing with third party libraries you will need to have multiple versions of your same component very soon to handle breaking changes.
The string only passing is an headache when you'd like to pass object/functions to compare later on.
The fact that some CSS properties can bleed in the shadow Dom, while others don't.
To introduce them into an existing codebase you need support, and last time I checked the support from React was clunky
Tools like React, etc al, requires almost doing everything that way.
RiotJS fits in for adding a bit to "old-school" web-apps (PHP, Ruby, Perl, ASP3).
Legacy backend, slightly more modern frontend.
Svelte supports custom elements: https://svelte.dev/docs/custom-elements-api
However I am a total noob so it might not be entirely idiomatic. An example for the end result is at https://releases.bruta.link
Preact does it just fine so I have to assume it’s possible but simply not in the interests of the React team. React feels like the enterprise lock-in of 2020s development: if you use it you’re using it top to bottom and it’s very difficult to mix and match with other frameworks. It’s a shame.
Even in frameworks that do support them, they're just not widely used.
If it were much easier to mix and match components that use different frameworks then people would be using web components whether they know it or not. But we’re in a React monoculture and React folks seem quite content with that.
React pretends they don't exist.
My bet is WASM being the Great Leap Forward because everything else that has been done in JavaScript is just layer on layer of abstraction, obfuscation and horrendous complexity.
I would rather develop in a mature language and have that transpiled to WASM rather than fight the frameworks (I still remember when Angula 5 was a 15,000 file desktop mom install !).
JavaScript has fulfilled an important role as the COBOL of the web. Low barrier to entry - anybody can do something useful without education or expense.
Now the web needs move forward to become the first choice desktop. That will take tools that can really do the whole stack, like C, Go, Java, Zig, Virgil productively.
Industry has been busy on the JS bandwagon and adding vast amounts of complexity to browsers and web infrastructure when maybe (happy for opinions to be voiced on this) we should be just adding more language runtimes to browsers.
Not that any of the points are bad. As read, they all seem like solid reasons to not use something. Indeed, the fact that frameworks have no reason to use these over user space constructs seems very compelling, all on its own.
The rest feels like a thinly veiled rewording of the second point? That is, this feels like advocacy that is blaming the messengers for not successfully convincing us that we should use this superior thing. Which... is still a heavy handed advocacy. :(
So even if I love web components since it got released (and have used them in some toy apps) I can't count on them yet thanks to Google.
You can't trust anything coming from Google. There is no assurance that they announce tomorrow that they remove support for them in chrome. Or even worse, they will remove support in 6 month (killing all the developers market and destroying any production app). To announce in 3 years that they will continue the effort of removing them...
It's just sad. </Rant>
I ended up here:
https://hacks.mozilla.org/2014/12/mozilla-and-web-components...
And left very unsatisfied that this is the current answer, only because I'd like to better understand how the use of JS modules over time has played out wrt html imports.
The basic security story is that browsers never care about file extensions, they care about MIME types. A developer might add an import to a third-party HTML or JSON file somewhere and expect one "safe" behavior, but the third-party could just return a MIME type of "text/javascript" and inject an entire script and the browser is supposed to respect that MIME type.
To keep things safe, browsers want a way to signal that an import is supposed to JSON (or HTML or CSS) rather than JS and error if it gets back something "wrong" from a server request. That's one of the proposed uses for Import Attributes to suggest expected MIME types for non-JS modules in ES module imports.
Unfortunately, there are other proposed uses for Import Attributes (things like including hashes for integrity checks) and so there have been quite a few revisions (and multiple names) for Import Attributes trying to best support as many of the proposed uses as possible, and that has slowed progress on it a lot more than some people would wish.
(and this is something a polyfill can only partially bridge)
[1] https://github.com/WICG/webcomponents/issues/509#issuecommen...
[2] https://bugs.webkit.org/show_bug.cgi?id=182671
[3] https://github.com/WebKit/standards-positions/issues/97
[4] https://html.spec.whatwg.org/multipage/custom-elements.html#...
As for representing Apple's state of mind w.r.t. alternatives; they've had the better part of a decade to shit or get off the pot, so I don't hold a shred of vendor sympathy. In that context, "open to alternatives" just sounds like product manager code for "we have no ideas of our own" and "let's kick the can down the road as much as we possibly can". If anything riles me up, it's the disinterest in developing a substitute capability, and if Apple weren't a steward of the standard it'd matter far less. I'll compare & contrast Cisco's running battles over their own PoE vs the IEEE802.3 standards that they'd had a hand in developing; at least the vendor offered a concrete alternative.
People say they’re slow, but things I’ve hand rolled this way, while none are super complex, I can make thousands of elements and not put a dent in any metrics or lighthouse runs. I tend to bind stuff I’m going to manipulate in the child tree to variables in the constructor, so I never really have the cost of constant lookups, which maybe amortizes well once you’re looking at whole program performance.
We have a bunch of sites written in different frameworks (React, Rails + Hotwire, Phoenix + Liveview) that we'd like to have a shared set visual components between them. Think things like:
- Buttons - Text spacing - Layout cards - Headers
Pretty low level stuff, the most dynamic behaviour might be something like showing / hiding content in an FAQ.
Web Components seems like a reasonable fit to encapsulate the design elements and reuse them across all these different frameworks (probably coupled with Tailwind CSS on each site to enforce consistent colours + spacing).
IMHO you probably want to look at design tokens or other ways to centralize your visual styles--it's really more of a workflow problem and not just something to throw more javascript at with web components.
I think what gp wants is css!
This is false. Use <template> and <slot>. First-class progressive enhancement.
Say you have a component that does something with titles (h1 etc.), then you would wrap the semantic element with a web component.
You do the same with buttons, text or input fields etc.
This way you 100% retain the semantic structure (before loading JS). And it’s possible to progressively enhance the DOM if that is a goal.
Here's legacy React's like_button.js, without JSX, from their old documentation:
'use strict';
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked this.';
}
return e(
'button',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer = document.querySelector('#like_button_container');
const root = ReactDOM.createRoot(domContainer);
root.render(e(LikeButton));
Here's the equivalent using the Web Components specification[1]: // https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
// Create a class for the element
class LikeButton extends HTMLElement {
static get observedAttributes() { return ['liked']; }
constructor() {
// Always call super first in constructor
super();
this.liked = false;
// Create a shadow root
/* const shadow = */ this.attachShadow({mode: 'open'});
}
get liked() { return this.hasAttribute('liked'); }
set liked(state) { this.toggleAttribute('liked', Boolean(state)); }
attributeChangedCallback(name, oldValue, newValue) {
this.render();
}
connectedCallback() {
this.render();
}
render() {
const shadow = this.shadowRoot;
if (this.liked) {
shadow.innerHTML = 'You liked this.'
return;
}
shadow.innerHTML = `<button onclick="this.parentNode.host.liked = true;">
Like
</button>`;
}
}
// Define the new element
customElements.define('like-button', LikeButton);
The React API is slightly nicer to use, especially with JSX.Just as an example - a widget to manage a list of number ranges. Build it up like a form element - it provides a value prop with the type ‘[number,number][]’
You need to display the current collection of min/max pairs, and add / remove / edit the collection - it’s essentially a mini CRUD component.
There is no default html element that provides this functionality - you need an ordered list of pairs of number inputs, a confirm changes button, a ‘add new’ and ‘remove this entry’ button - you need a whole collection of stuff to provide the functionality.
But once you’ve got it all worked out, wrap it in a web component and you can use it in 8 different places across your admin tool - ‘<ranges-picker />’ and boom, you’re ready to go.
Using web components to define a library of common interface elements works just fine.
The scope and limitations I had:
- Should be framework-agnostic
- Should require the minimum effort in installing
- Should not conflict with anything on the page
So, Web Component was the perfect candidate for the lib.
The framework-less approach felt overwhelming, and lit-html smelled like Polymer. So, initially, I picked up Stencil. It is still fantastic for creating component libraries, but it happened to be overkill for a tiny component. So in a few years, I rewrote my WC without Stencil, and now it feels just right.
I developed several more Web Components professionally in the past few years. I saw them as a perfect fit for the problems I had to solve. The scopes of problems were similar:
- Should be framework-agnostic
- Should require the minimum effort in installing
- Should not conflict with anything on the page
https://docs.astro.build/en/guides/client-side-scripts/#pass...
Web components aren't popular because they haven't become popular yet. There is a lack of sufficient network effect moving in that direction.
Once they start becoming popular, the issues with them (which are relatively minor) will be resolved quickly and improvements added.
Why are most of our cities and cars still designed with fundamentals basically the same as in the 1930s? Not because there aren't better designs. It's just momentum.
High technology is like that still even though nothing physical needs to be changed.
Humans are herd animals. And the rationalization for behavior comes after the behavior not before. We subconsciously copy what other people are doing. People use React or whatever flavor-of-the-month.
Give it a couple of years and many of the flavor-of-the-month frameworks will probably be built on top of web components.
I agree with your first sentence: things are unpopular by default. However I disagree with your assumption: web components won't become magically popular overtime without a good reason for them to be used over all the other options. Your "flavor-of-the-month" take ignores the fact that react is now 10 years old and is boring tech. It is still one of the most widely used frontend framework.
Yes it is, it is always a bad thing. It's not necessarily a dealbreaker, but it's never good or even neutral. I already have a mental model of the incredibly slow DOM, I don't need another dimension of performance cliffs to think about. Instead of more convoluted web standards, I want fewer, simpler and faster APIs - and may the best abstractions win.
Ah yes, I've heard about this "mixing CSS into your HTML".
The entire point of CSS is that you can write selectors that can affect anything, import it in the header, and you're done. If you're telling me I have to import a new CSS file (or repeat myself and import the same CSS file) inside of every custom element I create, obviously I thought of that. I'm telling you that's a terrible, awful idea, because it breaks the fundamental way CSS is supposed to work.
Consider: if I'm distributing a library of web components, which of my users' CSS files that I know nothing about do I include? Did you read the example I already gave? The solution you're offering doesn't solve the problem I've described.
> Additionally, web components work just fine without the shadow DOM, it's optional,
You should read the post you're responding to in order to find out why that also causes problems.
> but for a great many custom elements you don't want them inheriting all the styles, because that can break the custom element.
...just like all of the rest of CSS. We have strategies for dealing with that. The only strategy you've suggested for doing the opposite is a completely useless non-solution.
I've tried to find a workable solution using style inheritance, but it doesn't work everywhere. On code sandbox sites like codepen.io, the stylesheet is generated for you and sometimes updated in-place. You'd have to watch with a MutationObserver and then propagate the change to every instance of a custom element on the page.
<tab-area>
<tab- text='First'>
...arbitrary HTML that should work intuitively, i.e. be styled
like the rest of the document.
</tab->
<tab- text='Second'>
Note the lack of slot attributes on these tab elements. That's
because we want this to behave like normal HTML where you
can nest without having to worry about how tab-area was implemented.
Why should we have to care that tab-area was implemented with
templates/slots?
</tab->
<tab- text='Third'>
There could be any number of these tabs so you can't put them
into slots without some sort of late-generating the slots anyway.
</tab->
</tab-area>
We want this to render like a group of tabs where "First" and its body are visible, and tabs are peeking up from behind with the texts "Second" and "Third" but the content of those tabs isn't shown until you click the tab (at which point the content of "First" is hidden).That's not achievable with slots.
In general, I haven't found a case where templates/slots are actually useful--it's a lot of work to create a bad interface.
I don't really see how Web components fill that picture as cleanly. You would basically still forced to choose between component overhead/custom code OR static HTML, creating a formal discintion rather than a boolean or flag or whatever. Why create that distinction just because it's "native" to the browser? The DOM is sufficient when your default is static and components are always SSR in the same DOM model.
It basically only makes sense if you're not building a "holistic" single frontend framework driven site and rather are mixing various server side view systems and occasionally adding a couple components.
Basically the sort of thing better suited for major legacy sites like Google rather than something you'd ever choose from the ground up.
- You cede rendering control from your framework to the web component, if it does anything like conditional children etc. meaning it can be hard to optimize or you get stuck using refs, which is suboptimal and in some frameworks makes optimizations hard to achieve
- it’s de facto CSS in JS and to boot your styles are also locked to shadow dom. Yes I know this isn’t true in an absolute sense but in practice all the web component implementations leverage this. This makes styling harder, can have performance impacts etc. also means you can’t leverage CDN caching and distribution for your styles
- FOUC is a real problem with web components as it exists today.
- Interop varies and can have gotchas, especially with custom events that may be emitted by components
- You can’t get fine grained optimizations in some frameworks using them because web components in practice often contain some kind of logic one way or another.
- to use slot you must use shadow dom, and that means your app needs to be rendering against a shadow root of some sort, which can be real clunky
It's a solid stable niche, not a growth kind of niche.
customElements.define('like-button', class LikeButton extends HTMLElement {
connectedCallback() {
this.innerHTML = '<button></button>'
this.addEventListener('click', this.onClick, true)
}
onClick() {
this.innerText = 'You liked this'
}
})I don’t think the Web Components API was meant to be used directly all the time. You can use a framework like StencilJS:
When you consider the 100KB of bulk the React example brings with it the web component example is actually pretty impressive.
That’s a big win that can be easily overlooked. But it depends what you’re optimising for…
Which is not a bad thing. Incremental builds have gotten much better and much easier to deal with.
When I go to a website and all I see if a bunch of blank gray boxes that means they're using web components. It encourages developers to not put the text or images in the HTML and instead to load them externally after the page is loaded with javascript. That's slow, brittle, and stupid for most web documents. It's the opposite of progressive enhancement that fails gracefully. Web components just leave blank nothing that ruins accessibility for screen readers.
The tech is fine. You can achieve amazing progressive enhancement with web components by understanding and effectively using <template> and <slot>. However, many web component developers never learn this.
The problem you’re describing is a training/marketing issue, as discussed in the post.
They are strong encapsulation around a user defined component. So if you wanted to, for example, lock down the styling on your widget, you can! (hurray?)
My cynical thoughts of why google pushes this is because it's another route to get in front of adblock. Make an ad component and now it's a lot harder for an addon to change or remove that component (and easier for the website to detect when that happens).
I generally just head to the next site on my list/search results.
Content-Security-Policy: script-src 'self'; script-src-attr 'unsafe-hashes' 'sha256-9lIp1merGZMC6sfoM+OcgpSSRJJr18teLzyFangr0FY='
Web components aren't 'an' api, it's a specific way of using a set of apis, and specifically requires you to use a set of templates in a way that frameworks (which looovvvveeee DSLs) won't accept.
The other bits? shadow dom? Custom elements? eh. Those are things a framework will wrap happily enough if they see value in it. Maybe some already do? As you say, you'd never know...
...but those html templates will never fly. They're just crazy bad ergonomics and framework authors will never accept them.
That's really the core of the issue; invoking components in your layout requires you to use some kind of layout template, and every framework does it differently.
That's why cross framework css solutions (eg. tailwind) are massively successful, because they can be called easily by anyone regardless of the templating; the same is not... and, frankly, seems like it never will, be true of web components.
It recommends a build step, but yes, you can use it without one. Also, TypeScript isn't a standard language. So if you're using .ts you may as well use .svelte.
> is faster
I don't know that Svelte has been benchmarked with custom elements. Either way it's plenty fast.
> is smaller as you amortize the library size over a collection of elements
That's kind of a funny thing to worry about. It has improved in Svelte 4, though. https://svelte.dev/blog/svelte-4 I also don't know that Lit is so small in practice. With Lit you get to use map and the ternary operator and all that jazz, like React. https://lit.dev/docs/templates/conditionals/
JS sites almost always fail very badly when it fails (a relatively common event). Text sites cannot fail even when they fail.
There are still some frameworks that let you write fancy front-ends without a build-step and associated tooling. And there's people like me who prefer those since they meet our needs.
:)
I'm truly skeptical there is much to be gleaned accessibility-wise from templates if JS is disabled. There's no way to know how and where the template was meant to be instantiated in the DOM and accessibility tree without JS.
Web components are superficially similar to React/Angular/Vue, but very different in practice. Different strengths. Different use cases. Different opportunities. Like the post says, the web components community has been terrible at articulating and marketing these differences.
This solution works in that you can create a mixin that does this and use it in your custom elements. But if you profile it I think you'll discover it's painfully slow. It's not noticeable if you're using a few custom elements here and there, but if you're, for example, making a table of custom elements, the page will load slowly.
<https://codepen.io/webstrand/pen/jOzYVpL> is as far as I got. The FOUS is pretty annoying, too, before the templates get properly registered. But I could live with that.
Having a lifecycle that you can hook into to know when to run code related to this particular instance of the custom element.
See e.g. a recent talk on web components — https://youtu.be/jBJ7eoPtmY8?t=367
The problem web components is solving is just the strong encapsulation. Closing the escape hatches as it were.
React, while it may provide similar functionality to the programmer, is not natively understood, and while it can run in browsers, the 'components' in React are not handled in the same way in the browser as native HTML elements, JavaScript and its APIs don't know about them as HTML elements, DOM isn't aware of them (only their parts), and dev tools can't offer much insight because the app that's running is an unpredictable black-box to the browser.
From Apple's point-of-view they're probably waiting for someone to propose a solution that doesn't have the same perceived issues. They've been doing good work on declarative shadow DOM, selection, a11y, template instantiation, etc., in the mean time.
Indeed, judging from both words & (in)actions, I think we can safely conclude that they don't. There's likely no direct commercial pressure to implement this part of the standard, and I'd expect all members of WHATWG's steering group perceive the vast influence they consequently hold as a competitive advantage, not a social contract.
I'm not sure what competitive advantage that gains them or anyone else. There's an impasse, and effectively no one uses this feature.
The main risk of XSS is when you inject some unescaped user-generated string into a template and then set that whole template as your component's innerHTML... All I want to point out is that not every piece of data is a custom user-generated string. Numbers, booleans don't need to be escaped. Error messages generated by your system don't need to be escaped either. Enum strings (which are validated at insertion in the DB) also don't really need to be escaped but I would probably escape anyway in case of future developer mistake (improper validation).
I agree that the automatic sanitization which React does is probably not a huge performance cost for the typical app (it's probably worth the cost in the vast majority cases) but it depends on how much data your front end is rendering and how often it re-renders (e.g. real time games use case).
This is making a lot of assumptions. Just because the data was acceptable in a database table does not mean it doesn't pose an XSS risk.
Bear in mind, in other branches of this discussion we're talking about using DOM text APIs to insert. Certainly that is a good, reliable way to avoid XSS, but you can consider that to be value sanitization just done for you by the browser. In the absence of that, advocating that "if it comes from the API it is safe" is a dangerous thing to advocate for.
The title "A world where <HTML> tag is not required for your web pages" might be perfectly valid to submit into your blog's CMS system, but that in no way means you can skip processing that in the frontend because "it is safe". Plenty of what you are saying is reasonable, but I think the topic requires a little more nuance in order to speak about the topic responsibly.
Although such sanitization function is trivial to implement... In my previous comment, I mentioned using document.createElement() as a fallback if in doubt. It's safe to create the elements with the DOM API and using the textContent property as you suggest. That's why I don't see sanitization as a strong excuse to avoid using plain Web Components.
If you are distributing a library of web components, wouldn't you provide a CSS api for the things that are supposed to be styleable via CSS custom properties and styleable parts?
Case in point: consider Shoelace.
> a completely useless non-solution
Consider the existing libraries of web components — Shoelace for something generic, RedHat's Patternfly or Adobe's Spectrum for something company-specific. How much of a non-solution are they, really?
If you read my previous comments you'll see that the parts that should be styleable--i.e. the content of the component I've defined--are user-defined, so no, I can't document them.
> Consider the existing libraries of web components — Shoelace for something generic, RedHat's Patternfly or Adobe's Spectrum for something company-specific. How much of a non-solution are they, really?
If you read what I said in context, I'm not saying components are useless, I'm saying the unstyled shadow DOM is a completely useless non-solution to the problem I've described.
The "way CSS is supposed to work" was always a bad idea and is totally unworkable for large projects / teams. Throwing it out and simply inlining all your styles is absolutely the right call.
Inline styles make a lot of sense if you're using webcomponents pervasively, but there's a high up-front cost to doing that because you'll have to either a) define web components for everything you might want to style, including existing elements like <p>, or b) repeat yourself a lot.
In regards of slots, they are a nice way to allow a user of you webcomponent to overwrite/replace parts of your component with something else. This is most of the time a more advanced feature and I find it quite nice to build headless components
That's exactly what I'm saying is bad. Why does the user of the howto-tabs have to type "slot='panel'" when that information is already communicated by the fact that it's a "howto-panel" inside a "howto-tabs"? This is a useless leaking of implementation details.
And that page just keeps going and going. Sure, some of that is because they're implementing a few neat features, but most of it is because slots are way overcomplicated for something that's actually easier to do without them.
class HnTabs extends HTMLElement {
constructor() {
super();
this._tabs = {}
this.attachShadow({ mode: 'open' })
const ul = document.createElement('ul')
this.shadowRoot.appendChild(ul)
const slot = document.createElement('slot')
this.shadowRoot.appendChild(slot)
slot.addEventListener('slotchange', () => {
ul.innerHTML = ''
const tabs = Array.from(this.querySelectorAll('hn-
if (!tabs.some((tab) => tab.hasAttribute('active')
tabs[0].setAttribute('active', '')
}
tabs.forEach((tab) => {
const li = document.createElement('li')
li.innerText = tab.getAttribute('text')
ul.appendChild(li)
li.addEventListener('click', (e) => {
tabs.forEach((t) => t.removeAttribute('active'
tab.setAttribute('active', '')
})
})
})
}
}
customElements.define('hn-tabs', HnTabs);
class HnTab extends HTMLElement {
static get observedAttributes() {
return ['active'];
}
constructor() {
super()
this.attachShadow({ mode: 'open' });
this._slot = document.createElement('slot')
this.shadowRoot.appendChild(this._slot)
this._slot.style.display = 'none'
console.log(this.slot)
}
attributeChangedCallback() {
this.#activeStatus()
}
connectedCallback() {
this.#activeStatus()
}
#activeStatus(){
const active = this.hasAttribute('active');
this._slot.style.display = active ? 'block' : 'none'
}
}
customElements.define('hn-tab', HnTab);Until then I don't know if template and slot will take off.
Ironically it was Firefox that killed it, not Google. Google was all in on HTML imports. It would have also given a non JS way to dynamically load HTML. Streaming would have been great with this.
I am no fan of React, but this comment confuses me.
React didn't even invent the idea of reactive components; that was Angular 1. I honestly don't know why React became so popular initially. After Angular 1, there was Google's Polymer which was far more elegant and closer to native Web Components (and fixed many of Angular 1's flaws) - I suspect it's because some devs didn't like that you had to use some polyfills for certain non-Chrome browsers.
Anyway now we have Web Components which work consistently without polyfills on all browsers so I don't see any reason not to use plain Web Components or use something more lightweight such as Lit elements.
You know what's really, really, really slow? Anything involving the DOM, reading, writing, whatever.
> often leads to unnecessary double or triple rendering
"Renders" on the other hand are very cheap if they don't actually touch the DOM.
React renders aren't always cheap. It depends on the complexity of the component and you don't have as much control over the rendering. I've worked on some React apps which were so slow due to unnecessary re-renders that my new computer would often freeze during development. I was able to fix it by refactoring the code to be more specific with useState() (to watch individual properties instead of the entire object) but this flexibility that useState provides is a React footgun IMO. With HTMLElement, you're forced to think in terms of individual attributes anyway and these are primitive types like strings, booleans or numbers so there is no room for committing such atrocities.
Watching for attributes changing is not the whole picture. Why did the attribute change in the first place? Probably because you're doing stuff in terms of DOM attributes, which is slow - not individually, but in aggregate. Death by a thousand papercuts.
> I've worked on some React apps which were so slow due to unnecessary re-renders that my new computer would often freeze during development
I haven't managed to achieve that. If your computer freezes, that almost certainly means you're running out of RAM, not that your CPU is busy. I admit that the React "development build" overhead is considerable.
> I was able to fix it by refactoring the code to be more specific with useState() (to watch individual properties instead of the entire object) but this flexibility that useState provides is a React footgun IMO.
I admit that React has some footguns, but I don't see how the reactive model can be implemented entirely without them. It's a price I'm willing to pay, because it makes most things far easier to reason about. 90% of my components have no state at all. Of those that do, the vast majority have no complex state.
But if you mix updates and reads, the browser needs to recalculate the layout before the read occurs, otherwise the read may not be correct. For example, if you change the font size of an element and then read the element height, the browser will need to rerun layout calculation between those two points to make sure that the change in font size hasn't updated the element height in the meantime. If these reads and writes are all synchronous, then this forces the layout calculations to happen synchronously as well.
So if you do ten DOM updates interspersed with ten DOM reads in a single tick, you'll now get ten layout calculations (followed by one repaint).
This is called layout thrashing, and it's something that can typically be solved by using a modern framework, or by using a tool like fastdom which helps with batching reads and writes so that all reads always happen before all writes in a given tick.
Is it really immediately? I thought that was a myth.
I thought that, given toplevel function `foo()` which calls `bar()` which calls `baz()` which makes 25 modifications to the DOM, the DOM is only rerendered once when foo returns i.e. when control returns from usercode.
I do know that making changes to the DOM, when immediately entering a while(1) loop doesn't show any change to the DOM.
The browser will, as much as it can, catch together DOM changes and perform them all at once. So if `baz` looks like this:
for (let i=0; i<10; i++) {
elem.style.fontSize = i + 20 + 'px';
}
Then the browser will only recalculate the size of `elem` once, as you point out.But if we read the state of the DOM, then the browser still needs to do all the layout calculations before it can do that read, so we break that batching effect. This is the infamous layout thrashing problem. So this would be an example of bad code:
for (let i=0; i<10; i++) {
elem.style.fontSize = i + 20 + 'px';
console.log(elem.offsetHeight);
}
Now, every time we read `offsetHeight`, the browser sees that it has a scheduled DOM modification to apply, so it has to apply that first, before it can return a correct value.This is the reason that libraries like fastdom (https://github.com/wilsonpage/fastdom) exist - they help ensure that, in a given tick, all the reads happen first, followed by all the writes.
That said, I suspect even if you add a write followed by a read to your `while(1)` experiment, it still won't actually render anything, because painting is a separate phase of the rendering process, which always happens asynchronously. But that might not be true, and I'm on mobile and can't test it myself.
Firstly, the DOM is stateful, even in relatively simple cases, which means destroying and recreating a DOM node can lose information. The classic example is a text input: if you have a component with a text input, and you want to rerender that component, you need to make sure that the contents of the text input, the cursor position, any validation state, the focus, etc, are all the same as they were before the render. In React and other VDOM implementations, there is some sort of `reconcile` function that compares the virtual DOM to the real one, and makes only the changes necessary. So if there's an input field (that may or may not have text in it) and the CSS class has changed but nothing else, then the `reconcile` function can update that class in-place, rather than recreate it completely.
In frameworks which don't use a virtual DOM, like SolidJS or Svelte, rerendering is typically fine-grained from the start, in the sense that each change to state is mapped directly to a specific DOM mutation that changes only the relevant element. For example in SolidJS, if updating state would change the CSS class, then we can link those changes directly to the class attribute, rather than recreating the whole input field altogether.
The second issue that often comes with doing this sort of rerendering naively is layout thrashing. Rerendering is expensive in the browser not because it's hard to build a tree of DOM elements, but because it's hard to figure out the correct layout of those elements (i.e. given the contents, the padding, the surrounding elements, positioning, etc, how many pixels high will this div be?) As a result, if you make a change to the DOM, the browser typically won't update the DOM immediately, and instead batches changes together asynchronously so that the layout gets calculated less often.
However, if I mix reads and writes together (e.g. update an element class and then immediately read the element height), then I force the layout calculation to happen synchronously. Worse, if I'm doing reads and writes multiple times in the same tick of the Javascript engine, then the browser has to make changes, recalculate the layout, return the calculated value, then immediately throw all the information away as I update the DOM again somewhere else. This is called layout thrashing, and is usually what people are talking about when they talk about bad DOM performance.
The advantage of VDOM implementations like React is that they can update everything in one fell swoop - there is no thrashing because the DOM gets updated at most once per tick. All the reads are looking at the same DOM state, so things don't need to be recalculated every time. I'm not 100% sure how Svelte handles this issue, but in SolidJS, DOM updates happen as part of the `createRenderEffect` phase, which happens asynchronously after all DOM reads for a given tick have occurred.
OP's framework is deliberately designed to be super simple, and for basic problems will be completely fine, but it does run into both of the problems I mentioned. Because the whole component is rerendered every time `html` is called, any previous DOM state will immediately be destroyed, meaning that inputs (and other stateful DOM elements) will behave unexpectedly in various situations. And because the rendering happens synchronously with a `innerHTML` assignment, it is fairly easy to run into situations where multiple DOM elements are performing synchronous reads followed by synchronous writes, where it would be better to do all of the reads together, followed by all of the writes.
How do you explain different religions being very popular and yet contradicting each other on many critical points? They can't both be right if they contradict each other yet they may both be hugely popular...
That makes perfect sense, except that I don't understand how using a shadow DOM helps in this specific case (A DOM write followed immediately by a DOM read).
Won't the shadow DOM have to perform the same calculations if you modify it and then immediately use a calculated value for the next modification?
I'm trying to understand how exactly a shadow DOM can perform the calculations after modifications faster than the real DOM can.
The goal when updating the DOM is to do all the reads in one batch, followed by all the writes in a second batch, so that they never interleave, and so that the browser can be as asynchronous as possible. A virtual DOM is just one way of batching those writes together.
It works in two phases: first, you work through the component tree, and freely read anything you want from the DOM, but rather than make any updates, you instead build a new data structure (the VDOM), which is just an internal representation of what you want the DOM to look like at some point in the future. Then, you reconcile this VDOM structure with the real DOM by looking to see which attributes need to be updated and updating them. By doing this in two phases, you ensure that all the reads happen before all the writes.
There are other ways of doing this. SolidJS, for example, just applies all DOM mutations asynchronously (or at least, partially asynchronously, I think using microtasks), which avoids the need for a virtual DOM. I assume Svelte has some similar setup, but I'm less familiar with that framework. That's not to say that virtual DOM implementations aren't still useful, just that they are one solution with a specific set of tradeoffs - other solutions to layout thrashing exist. (And VDOMs have other benefits being just avoiding layout thrashing.)
So to answer your question: the virtual DOM helps because it separates reads and writes from each other. Reads happen on the real DOM, writes happen on the virtual DOM, and it's only at the end of a given tick that the virtual DOM is reconciled with the real DOM, and the real DOM is updated.
> So to answer your question: the virtual DOM helps because it separates reads and writes from each other. Reads happen on the real DOM, writes happen on the virtual DOM, and it's only at the end of a given tick that the virtual DOM is reconciled with the real DOM, and the real DOM is updated.
I still don't understand why this can't be done (or isn't currently done) by the browser engine on the real DOM.
I'm sticking to the example given: write $FOO to DOM causing $BAR, which is calculated from $FOO, to change to $BAZ.
Using a VDOM, if you're performing all the reads first, then the read gives you $BAR (the value prior to the change).
Doing it on the real DOM, the read will return $BAZ. Obviously $BAR is different from $BAZ, due to the writing of $FOO to the DOM.
If this is acceptable, then why can't the browser engine cache all the writes to the DOM and only perform them at the end of the given tick, while performing all the reads synchronously? You'll get the same result as using the VDOM anyway, but without the overhead.
The answer here is the standard one though: if you write $FOO to DOM, then read $BAR, it has to return $BAZ because it always used to return $BAZ, and we can't have breaking changes. All of the APIs are designed around synchronously updating the DOM, because asynchronous execution wasn't really planned in at the beginning.
You could add new APIs that do asynchronous writes and synchronous reads, but I think in practice this isn't all that important for two reasons:
Firstly, it's already possible to separate reads from writes using microtasks and other existing APIs for forcing asynchronous execution. There's even a library (fastdom) that gives you a fairly easy API for separating reads and writes.
Secondly, there are other reasons to use a VDOM or some other DOM abstraction layer, and they usually have different tradeoffs. People will still use these abstractions, even if the layout thrashing issue were solved completely somehow. So practically, it's more useful to provide the low-level generic APIs (like microtasks) and let the different tools and frameworks use them in different ways. I think there's also not a big push for change here: the big frameworks are already handing this issue fine and don't need new APIs, and smaller sites or tools (including the micro-framework that was originally posted) are rarely so complicated that they need these sorts of solutions. So while this is a real footgun that people can run into, it's not possible to remove it without breaking existing websites, and it's fairly easy to avoid if you do run into it and it starts causing problems.