📜 ⬆️ ⬇️

Why I don't use web components

I am writing this mainly for myself in the future, so that I have where to refer to when someone asks me why I am skeptical about web components and why Svelte is not compiled into web components by default. (However, it can be compiled into web components, as well as integrated with them, as evidenced by the excellent rating on Custom Elements Everywhere ).


Nothing written below should be taken as a critique of the hard work done on the web components. Perhaps I also made mistakes in this publication, in which case I will gladly make amendments. I also do not declare that you should not use web components. They have their own scope. I just explain why they are not suitable for me.


1. Progressive improvement


This may be an old-fashioned conviction, but I believe that websites should work without javascript as much as possible. Web components without JS do not work. This is normal for things that are interactive in nature, such as custom form elements (<cool-datepicker>), but this is not normal for site navigation, for example. Or imagine a <twitter-share> component that encapsulates the logic of building a URL to be sent to Twitter . I could implement it on Svelte , which will render this HTML to me on the server:


 <a target="_blank" noreferrer href="..." class="svelte-1jnfxx"> Tweet this </a> 

In other words, the usual <a> in all its accessible magnificence.


When JavaScript is enabled, a progressive improvement occurs - instead of opening a new tab, a small pop-up window opens. But even without JS, the component is still working fine.


In the case of a web component, HTML would look something like this:


 <twitter-share text="..." url="..." via="..."/> 

... that it is useless and not suitable for use if JS is blocked, or for some reason broke, or the user has an old browser.


In addition, the class="svelte-1jnfxx" provides us with style encapsulation without a Shadow DOM. Which leads us to the next item.


2. CSS in, uh ... JS


If you want to use the Shadow DOM to encapsulate styles, you will need to insert your CSS in the <style> . The only practical way to do this if you want to avoid blinking the loading content (FOUC) is to embed CSS as a string in JavaScript, which defines the rest of the logic of your web component.


This is contrary to the performance improvement advice that says "less javascript please". The CSS-in-JS community, in particular, has been much criticized for not using css-files for CSS, and here, with web components, we are here again.


In the future, we will be able to use CSS Modules as well as Constructable Stylesheets to deal with this problem. We will also be able to stylize the insides of the Shadow DOM through ::theme and ::part . But here it was not without problems.


3. Platform fatigue



This is a painful crown for me - I advertised these things as “Future” for several years, but in order to keep up with the present we had to fill the platform with a bunch of different features, exacerbating the gap between browsers.

At the time of writing, on https://crbug.com , the Chrome bug tracker, there are 61,000 open bugs that show the tremendous difficulty of writing a modern browser.


Every time we add a new feature to the platform, we increase the complexity - we create the potential for new bugs and make it less and less likely that Chrome will have a new competitor. It also creates difficulties for developers who are being urged to learn these new features (some of which, for example HTML Imports or the original version of the Custom Elements standard, didn’t take root outside Google and are now in the process of being deleted).


4. Polyphils


The fact that you need to use polyfiles to support older browsers does not contribute to the development of the situation. And it does not help at all that the articles on the topic Constructable Stylesheets written in Google (hello, Jason!) Do not mention that this feature is available only in Chrome. (All three authors of the specification work for Google. Webkit seems to have doubts about certain aspects of this standard).


5. Composition


It can be useful to control when the contents of a slot should be rendered. Imagine that you have <html-include> to download some additional content when it is visible:


 <p>Toggle the section for more info:</p> <toggled-section> <html-include src="./more-info.html"/> </toggled-section> 

Suddenly! Even if we have not yet opened the toggled-section , but the browser has already requested more-info.html , along with all the images and other resources that are there.


This is because the contents of the slots are rendered in the web components in advance . In reality, it turns out that in most cases you want to render the contents of slots lazily. Svelte v2 adopted a proactive redner model to meet web standards, but this turned out to be a major source of inconvenience — we could not create something like React Router, for example. In Svelte v3, we moved away from the behavior of web components and never looked back.


Unfortunately, this was one of the fundamental characteristics of the DOM. Which brings us to ...


6. Confusion between properties and attributes.


Properties and attributes are the same, in principle, the same thing, right?


 const button = document.createElement('button'); button.hasAttribute('disabled'); // false button.disabled = true; button.hasAttribute('disabled'); // true button.removeAttribute('disabled'); button.disabled; // false 

Almost:


 typeof button.disabled; // 'boolean' typeof button.getAttribute('disabled'); // 'object' button.disabled = true; typeof button.getAttribute('disabled'); // 'string' 

There are names that do not match:


 div = document.createElement('div'); div.setAttribute('class', 'one'); div.className; // 'one' div.className = 'two'; div.getAttribute('class'); // 'two' 

... and there are those who are not agreed at all:


 input = document.createElement('input'); input.getAttribute('value'); // null input.value = 'one'; input.getAttribute('value'); // null input.setAttribute('value', 'two'); input.value; // 'one' 

But we could deal with these quirks, the interaction of the string format (HTML) and the DOM. There are a finite number of these features, they are documented, so that at least we can learn about them, with time and patience.


Web components change the situation. There are no guarantees about the relationship of properties and attributes, and you, as a developer of web components, are required to support both. Which leads us to such a thing:


 class MyThing extends HTMLElement { static get observedAttributes() { return ['foo', 'bar', 'baz']; } get foo() { return this.getAttribute('foo'); } set foo(value) { this.setAttribute('foo', value); } get bar() { return this.getAttribute('bar'); } set bar(value) { this.setAttribute('bar', value); } get baz() { return this.hasAttribute('baz'); } set baz(value) { if (value) { this.setAttribute('baz', ''); } else { this.removeAttribute('baz'); } } attributeChangedCallback(name, oldValue, newValue) { if (name === 'foo') { // ... } if (name === 'bar') { // ... } if (name === 'baz') { // ... } } } 

You can do it the other way round - attributeChangedCallback called by getters and setters. In any case, the convenience of working with this is simply depressing. At the same time, frameworks have a simple and unambiguous way to transfer data to a component.


7. Flowing design


This item is a bit vague, but it seems strange to me that attributeChangedCallback is just a class method. You can literally do the following:


 const element = document.querySelector('my-thing'); element.attributeChangedCallback('w', 't', 'f'); 

Attributes have not changed, but the code behaves as if it happened. Of course, there were always a lot of ways to hurt JavaScript, but when I see the implementation detail sticking out in this way, it seems to me that there is something wrong with the design.


8. Bad DOM


Ok, we have already established that the DOM is bad. But it is still hard to exaggerate how inconvenient the way to make interactive applications.


A few months ago I wrote an article "Write less code" , designed to illustrate how Svelte allows you to write components more efficiently than frameworks like React and Vue. There was no comparison with vanilla DOM, but it should. In short, we have a simple component <Adder a={1} b={2}/> :


 <script> export let a; export let b; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p> 

That's it. And now let's write the same thing via the web component:


 class Adder extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <input type="number"> <input type="number"> <p></p> `; this.inputs = this.shadowRoot.querySelectorAll('input'); this.p = this.shadowRoot.querySelector('p'); this.update(); this.inputs[0].addEventListener('input', e => { this.a = +e.target.value; }); this.inputs[1].addEventListener('input', e => { this.b = +e.target.value; }); } static get observedAttributes() { return ['a', 'b']; } get a() { return +this.getAttribute('a'); } set a(value) { this.setAttribute('a', value); } get b() { return +this.getAttribute('b'); } set b(value) { this.setAttribute('b', value); } attributeChangedCallback() { this.update(); } update() { this.inputs[0].value = this.a; this.inputs[1].value = this.b; this.p.textContent = `${this.a} + ${this.b} = ${this.a + this.b}`; } } customElements.define('my-adder', Adder); 

Yeah.


Notice, if we synchronously change both a and b , then we will have two separate updates. Frameworks for the most part do not suffer from this problem.


9. Global names


I will not dwell on this for a very long time, suffice it to say that the dangers of working in a single shared namespace have long been known and dismantled.


10. All these problems have been solved.


The greatest sadness is that we already have good component models. We are still learning, but the basic task - synchronizing the view with some state through updating the DOM in component-oriented style - has already been solved for several years. And we are still adding features to the web platform just to catch up with what we already have in libraries and frameworks.


Since our resources are not infinite, time spent on one task means a lack of attention to another task. Considerable energy was spent on web components, despite the general indifference of the developers. What could we achieve by spending this energy on something else?


')

Source: https://habr.com/ru/post/457010/


All Articles