Por qué no uso componentes web

Escribo esto principalmente para mí en el futuro, de modo que tenga un lugar para referirme cuando alguien me pregunte por qué soy escéptico sobre los componentes web y por qué Svelte no está compilado en componentes web de forma predeterminada. (Sin embargo, puede compilarse en componentes web, así como integrarse con ellos, como lo demuestra la excelente calificación de Custom Elements Everywhere ).


Ninguno de los siguientes debe interpretarse como una crítica del arduo trabajo realizado en los componentes web. Quizás también cometí errores en esta publicación, en cuyo caso me complacería enmendarla. Tampoco declaro que no debe usar componentes web. Tienen su propio alcance. Solo explico por qué no son adecuados para mí.


1. Mejora progresiva


Esto puede ser una creencia pasada de moda, pero creo que los sitios web deberían funcionar sin JavaScript tanto como sea posible. Los componentes web sin JS no funcionan. Esto es normal para cosas que son inherentemente interactivas, como elementos de formulario personalizados (<cool-datepicker>), pero no es normal para la navegación del sitio, por ejemplo. O imagine un componente <twitter-share> que encapsula la lógica de construir una URL para enviar a Twitter . Podría implementarlo en Svelte , que me muestra el siguiente HTML en el servidor:


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

En otras palabras, el <a> habitual en todo su esplendor disponible.


Cuando JavaScript está habilitado, se produce una mejora progresiva; en lugar de abrir una nueva pestaña, se abre una pequeña ventana emergente. Pero incluso sin JS, el componente todavía funciona bien.


En el caso de un componente web HTML, se vería así:


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

... que es inútil e inadecuado para usar si JS está bloqueado, o por alguna razón se ha roto, o si el usuario tiene un navegador antiguo.


Además, class="svelte-1jnfxx" nos proporciona un estilo de encapsulación sin Shadow DOM. Lo que nos lleva al siguiente punto.


2. CSS en, eh ... JS


Si desea utilizar Shadow DOM para encapsular estilos, deberá insertar su CSS en la <style> . La única forma práctica de hacer esto, si desea evitar cargar el parpadeo de contenido (FOUC), es incrustar CSS como una cadena en JavaScript que define el resto de la lógica de su componente web.


Esto contradice el consejo de mejora del rendimiento que dice: "menos JavaScript, por favor". La comunidad CSS-in-JS, en particular, ha sido muy criticada por no usar archivos CSS para CSS, y aquí estamos nuevamente con componentes web.


En el futuro, podremos utilizar módulos CSS y hojas de estilo construibles para solucionar este problema. También tendremos la oportunidad de diseñar los elementos internos de Shadow DOM a través de ::theme y ::part . Pero aquí no fue sin problemas.


3. Fatiga de la plataforma.



Esta es una corona dolorosa para mí: he anunciado estas cosas como el "Futuro" durante varios años, pero para mantenernos al día con el presente, tuvimos que llenar la plataforma con un montón de características diferentes, exacerbando la brecha entre los navegadores.

Al momento de escribir, en https://crbug.com , el rastreador de errores de Chrome, 61,000 errores abiertos que muestran la enorme complejidad de escribir un navegador moderno.


Cada vez que agregamos una nueva función a la plataforma, aumentamos la complejidad: creamos el potencial de nuevos errores y hacemos menos probable que Chrome tenga un nuevo competidor. También crea dificultades para los desarrolladores a quienes se alienta a aprender estas nuevas funciones (algunas de las cuales, como HTML Imports o la versión original del estándar Custom Elements, no se han arraigado fuera de Google y ahora están en proceso de eliminación).


4. Polyphiles


El hecho de que necesite usar polyfiles para admitir navegadores antiguos no contribuye al desarrollo de la situación. Y no ayuda en absoluto los artículos sobre hojas de estilo construibles escritos en Google (¡hola Jason!) No mencione que esta función solo está disponible en Chrome. (Los tres autores de la especificación trabajan para Google. Webkit parece tener dudas sobre algunos aspectos de este estándar).


5. Composición


Puede ser útil controlar cuándo se debe representar el contenido de una ranura. Imagine que tiene <html-include> para cargar contenido adicional cuando es visible:


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

De repente! Incluso si aún no hemos abierto toggled-section more-info.html , pero el navegador ya ha solicitado more-info.html , junto con todas las imágenes y otros recursos que están allí.


Esto se debe a que el contenido de las ranuras se representa en los componentes web de antemano . En realidad, resulta que en la mayoría de los casos desea renderizar el contenido de las ranuras de manera perezosa. Svelte v2 adoptó un modelo de enrojecimiento proactivo para cumplir con los estándares web, pero esta fue la principal fuente de inconvenientes: por ejemplo, no pudimos crear algo similar al React Router. En Svelte v3, nos alejamos del comportamiento de los componentes web y nunca miramos hacia atrás.


Lamentablemente, esta fue una de las características fundamentales del DOM. Lo que nos lleva a ...


6. Confusión entre propiedades y atributos.


Las propiedades y los atributos son básicamente lo mismo, ¿verdad?


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

Bueno, casi:


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

Hay nombres que no coinciden:


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

... y hay aquellos que no están de acuerdo en absoluto:


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

Pero podríamos lidiar con estas peculiaridades, la interacción del formato de cadena (HTML) y el DOM. Hay un número finito de estas características, están documentadas, por lo que al menos podemos aprender sobre ellas, si tenemos el tiempo y la paciencia.


Los componentes web hacen la diferencia. Ya no hay garantías sobre la relación entre propiedades y atributos, y usted, como desarrollador de componentes web, debe admitir ambos. Lo que nos lleva a tal cosa:


 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') { // ... } } } 

Puede hacer lo contrario: llamadores y establecedores de attributeChangedCallback . En cualquier caso, la conveniencia de trabajar con él es deprimente. Al mismo tiempo, hay una manera simple y sin ambigüedades en los marcos para transferir datos a un componente.


7. Diseño con fugas


Este elemento es un poco vago, pero me parece extraño que attributeChangedCallback es solo un método de clase. Literalmente puede hacer lo siguiente:


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

Los atributos no han cambiado, pero el código se comporta como si sucediera. Por supuesto, siempre ha habido muchas formas de hacer daño en JavaScript, pero cuando veo un detalle de implementación sobresaliendo de esta manera, me parece que algo está claramente mal con el diseño.


8. DOM malo


Ok, ya hemos establecido que el DOM es malo. Pero aún es difícil exagerar cuán inconveniente es crear aplicaciones interactivas.


Hace unos meses, escribí un artículo, "Escribir menos código", para ilustrar cómo Svelte puede escribir componentes de manera más eficiente que marcos como React y Vue. No hubo comparación con el DOM de vainilla, pero debería. En resumen, tenemos un componente simple <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> 

Eso es todo. Ahora escriba lo mismo a través del componente web:


 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); 

Si


Tenga en cuenta que si cambiamos sincrónicamente a y b , entonces tendremos dos actualizaciones separadas. La mayoría de los marcos no sufren este problema.


9. Nombres globales


No me enfocaré en esto por mucho tiempo; basta con decir que los peligros de trabajar en un solo espacio de nombres compartido han sido conocidos y desarmados.


10. Todos estos problemas ya han sido resueltos.


La mayor tristeza es que ya tenemos buenos modelos de componentes. Todavía estamos aprendiendo, pero la tarea básica: sincronizar la vista con un cierto estado mediante la actualización del DOM en un estilo orientado a componentes, ya se resolvió hace varios años. Y todavía agregamos características a la plataforma web solo para ponernos al día con lo que ya tenemos en bibliotecas y marcos.


Dado que nuestros recursos no son infinitos, el tiempo dedicado a una tarea significa una falta de atención a otra tarea. Se gastó mucha energía en componentes web, a pesar de la indiferencia general de los desarrolladores. ¿Qué podríamos lograr gastando esta energía en otra cosa?

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


All Articles