- Para tratar las demos interactivas de un tutorial de CRDT como contenido duradero, los web components que pueden insertarse directamente en HTML son más adecuados que un framework específico
- Los componentes MDX de Astro ofrecen comodidades como build, alcance de CSS y TypeScript, pero fuera de Astro pueden generar costos de reescritura
- Los web components permiten poner etiquetas personalizadas como
<pixelart-demo></pixelart-demo> en Markdown y reutilizarlas basándose en etiquetas en cualquier parte de la página solo cargando el script
- Los frameworks y TypeScript también son dependencias, por lo que los cambios acumulados de versión, API y compatibilidad hacen que el código abandonado sea más difícil de recuperar cuanto más dependencias tenga
- La plataforma web es lo bastante estable como para que sitios antiguos funcionen en navegadores modernos, así que para trabajos que deban seguir siendo accesibles dentro de 5, 10 o 20 años conviene un enfoque basado en estándares
Preservar demos interactivas como si fueran contenido
- La serie de blog sobre CRDT necesitaba demos interactivas para mostrar los conceptos, y como ejemplo se usó un editor colaborativo de pixel art
- El criterio para elegir la tecnología era que, aunque la demo estuviera hecha con HTML, CSS y JS, se parecía más a contenido incrustado dentro de una entrada del blog
- Se parece más a algo que insertas dentro del texto, como una imagen o un video
- Debería poder moverse a cualquier lugar que pueda renderizar HTML
- En 2023 el sitio estaba hecho con Astro, pero antes había pasado por un generador de sitios estáticos propio, Hugo, un CMS personalizado en PHP, Tumblr, Movable Type y WordPress
- Aunque ahora guste Astro, es difícil asumir que el sitio web vaya a ejecutarse sobre Astro para siempre
Cómo Markdown redujo el costo de migración
- La razón por la que la migración reciente fue sencilla es que el contenido se mantuvo como archivos de texto plano en Markdown
- Eso reduce tareas que suelen hacer falta al cambiar de sistema
- Exportar desde un sistema e importar en otro
- Corregir problemas de compatibilidad
- Eliminar elementos para los que no se encontró forma de trasladarlos
- Si pones los archivos Markdown en un sitio nuevo, en su mayoría siguen funcionando tal cual
- Markdown permite escribir HTML directamente, así que si incluso un diagrama interactivo complejo puede expresarse con etiquetas HTML normales, puede trasladarse igual que el resto del contenido
La comodidad de Astro MDX y sus límites de portabilidad
- La integración de MDX en Astro permite renderizar componentes de Astro dentro de Markdown
- Los componentes de Astro aprovechan varias comodidades del sistema de build
- Escribir HTML, CSS y JS en un solo archivo
- Manejo del scope de selectores CSS
- Compilación de TypeScript
- Renderizado condicional de markup
- Otras optimizaciones
- El problema es que todas esas comodidades solo funcionan de manera confiable dentro de Astro
- Si migras a otro generador de sitios, quizá tengas que reescribir los componentes
- Puede que debas separar HTML, CSS y JS
- Puede que necesites configurar un nuevo sistema de build
- Puede que debas encontrar otra forma de manejar el scope de estilos
- Por eso, aunque las funciones exclusivas de Astro sean convenientes, no eran una buena elección para demos que necesitan preservación a largo plazo
Lo que resolvieron los web components
- Web Components es un conjunto de tecnologías estándar de la web para crear elementos HTML reutilizables
- La forma de uso consiste en escribir una clase de elemento personalizado, registrar un nombre de etiqueta y luego usar esa etiqueta en el markup
- La demo de pixel art se incrusta en Markdown con este HTML
<pixelart-demo></pixelart-demo>
- No hace falta una configuración especial aparte ni un procedimiento de colocar primero un elemento específico y luego llamar una función
- El archivo JS debe mantenerse y enlazarse con una etiqueta
<script>, pero la necesidad de referenciar medios desde contenido de texto es la misma que con otros medios
- Cuando se carga el script, el nombre de la etiqueta se registra y funciona en cualquier parte de la página, incluso si el markup ya estaba ahí antes de que se ejecutara JavaScript
Archivo único y Shadow DOM
- Los web components pueden encapsular HTML, CSS y JS en un solo archivo sin necesidad de un sistema de build aparte
- Tener el código del componente reunido en un solo lugar reduce la carga mental, y los componentes de archivo único también tienen ventajas en la experiencia de desarrollo
- El componente de ejemplo
<pixelart-demo> hereda de HTMLElement, adjunta un Shadow DOM con attachShadow({ mode: "closed" }) y luego renderiza el markup y los estilos internos
- El
import PixelEditor from "./PixelEditor.js"; al inicio del archivo es un ES module import, así que no depende de un sistema de build aparte
- Si mantienes juntos los archivos relacionados, el navegador procesa el módulo
- El Shadow DOM aísla el componente de la página que lo rodea
- Puede resultar incómodo cuando necesitas compartir estilos con toda la app
- Es adecuado cuando debe estar completamente aislado
- Como una imagen o un video, puede conservar la misma apariencia y comportamiento sin importar dónde se use
Recibir configuración externa mediante atributos
- Los web components pueden exponer atributos (attributes) configurables desde afuera
- Los atributos pueden usarse como si fueran atributos nativos
- Las variables CSS son uno de los pocos valores que pueden entrar en el Shadow DOM, por eso el range slider de ejemplo define el color de acento así
<range-slider style="--accent: #0085F2"></range-slider>
- La demo de pixel art cambia la resolución a 20 mediante un atributo y muestra información de depuración de cada píxel
<pixelart-demo debug resolution="20"></pixelart-demo>
- Las llamadas
getAttribute y hasAttribute de la clase del componente se usan para leer esos valores de atributo
- Eso fue útil para reutilizar el mismo componente en varias etapas del tutorial y activar funciones específicas según el progreso
Los frameworks y TypeScript también son dependencias
- Existen herramientas como Lit, Stencil y Svelte que compilan a web components, pero al final un framework sigue siendo una dependencia
- El trade-off que más preocupaba aquí era el costo de mantenimiento a largo plazo
- TypeScript tampoco es una base nativa de la web, así que también cuenta como dependencia
- En las últimas 15 versiones de TypeScript hubo breaking changes, y aunque muchos de esos cambios fueron nuevas funciones útiles, exigieron modificar código
- Cuando usas dependencias, vienen con ello nuevas versiones, cambios de API y el costo de verificar compatibilidad
- Como no hay planes de seguir tocando este código, no se quiere llegar al momento en que sea obligatorio modificarlo y tener que procesar de golpe varios años de actualizaciones
La experiencia de recuperar sitios antiguos y la estabilidad de la plataforma web
- Al crear un museo en línea, se recuperó código antiguo guardado en una laptop que no se había encendido en 10 años, y cuantas más dependencias tenía un sitio web, más difícil era revivirlo
- Tras unos 20 años creando para la web, se vio el nacimiento, crecimiento y declive de jQuery, y también la creación de Node.js junto con el fork y posterior reunificación de io.js
- Backbone fue reemplazado rápidamente por AngularJS, AngularJS por React, y React también pasó por varias formas de escribir componentes en aproximadamente la mitad de ese tiempo
- Mientras el ecosistema cambiaba, la propia plataforma web se modificó con cautela para no romper sitios existentes, y por eso se ha mantenido bastante estable
- Los sitios antiguos siguen funcionando en navegadores modernos
- Si quieres que un trabajo siga siendo accesible dentro de 5, 10 o 20 años, conviene reducir las dependencias que no controlas y apegarte a estándares web con más probabilidades de no romperse
- La web tiene defectos, pero cuando se construye teniendo en cuenta sus características y sin capas intermedias, se convierte en una plataforma de cómputo resiliente, portable y orientada al futuro
1 comentarios
Opiniones de Hacker News
htmx no es un framework sino una biblioteca, y como se lo mencionó en el artículo, quiero responder algunas cosas.
Para empezar, el artículo no aborda el problema de sincronizar el estado con el servidor; htmx se enfoca precisamente en eso mediante intercambios hipermedia. Así que, desde este punto de vista, htmx y Web Components son ortogonales entre sí.
Como las dependencias implican nuevas versiones, cambios de API y costos de mantener compatibilidad, htmx no tiene dependencias y se enfoca muchísimo en la compatibilidad hacia atrás. Por ejemplo, es compatible con IE11, e intercooler.js, que vendría a ser algo así como htmx 1.0, salió en 2013 con una sola dependencia de jQuery y todavía tiene soporte.
Muchas bibliotecas de JavaScript tienen una cultura de reescrituras de API y actualizaciones difíciles, pero htmx explícitamente no es así, y no debería quedar incluido en esa generalización. El único cambio incompatible de htmx 2.0 probablemente sea dejar de dar soporte a IE.
Si Web Components durará más que htmx, como es parte del navegador, probablemente sí; pero en la medida de lo posible htmx seguirá siendo muy estable y con pocos cambios. La API básicamente va en la dirección correcta, y no creo que haga falta reescribirlo todo por pureza o por verse moderno.
Por encima de todo, htmx responde una pregunta distinta a la de Web Components: cómo sincronizar el estado con el servidor.
Al usar WebComponent, podés engancharte al ciclo de vida de elementos del DOM que se modifican desde afuera, como con morphdom de Htmx. Cuando cambia un atributo, hay que avisarle al componente de React, y cuando un nodo se elimina del DOM, hay que avisarle a React para que ejecute su limpieza. Sin WebComponents, creo que habría sido difícil hacerlo de forma limpia y eficiente.
Como resultado, podemos mantener el enfoque de Htmx: dejar todo el estado de la lógica de negocio en el servidor, y conservar en el cliente solo una pequeña parte conservadora, como el elemento que está enfocado actualmente o el texto que se está escribiendo. Cuanto más avanzamos en esta dirección, menos bugs y condiciones de carrera tenemos, y la experiencia de usuario mejora mucho, así que vale la pena el esfuerzo.
https://x.com/jlazaroff/status/1717176726248108469
https://htmx.org/essays/hypermedia-friendly-scripting/
La restricción de que solo los formularios puedan hacer POST y que la única forma de procesar una respuesta sea reescribir toda la página parece arbitrariamente limitante. Si htmx se convirtiera en algo como HTML6, duraría más que todos los frameworks de JavaScript.
Como soy un ingeniero que se enfoca principalmente en backend, la mayoría de las bibliotecas JS que uso después implican una gran carga de mantenimiento y cambios importantes de API frecuentes. Estoy pensando en probar htmx en un proyecto personal.
Creo que las plantillas que generan fragmentos de HTML del lado del servidor también se volverían menos complejas.
htmx en sí es un concepto realmente convincente, pero me gustaría que tuviera una comunidad más grande. Es un problema del huevo y la gallina, pero comparado con otros frameworks web no hay tantos recursos como posts de blog o videos. Por ejemplo, cuesta encontrar incluso algo básico sobre cómo integrar autenticación de Auth0 en una app con htmx.
Escribí todo non.io con Web Components puros, y es una SPA completa. Al ir más allá de simples componentes de blog anidados, aprendí algunas cosas.
La gestión de estado sigue sin tener una respuesta clara. Si no usas un framework envoltorio, tienes que hacer muchas cosas por tu cuenta. Una de las cosas buenas de React es que la gestión de estado está mayormente resuelta y existen patrones y expectativas establecidas. En Web Components se siente como el Lejano Oeste y, como no puedes pasar objetos complejos como atributos al estilo JSX, terminas mezclando atributos, eventos y llamadas directas a funciones de clase.
El rendimiento tampoco fue una ganancia tan grande como esperaba. En elementos atómicos pequeños, por ejemplo íconos, cuando hay cientos en una página el costo de crear instancias supera el valor, así que los saqué de Web Components. Para elementos estáticos, aunque aporten legibilidad al código, no vale la pena usarlos como Web Components, lo cual fue bastante decepcionante.
Aun así, hay cosas que realmente me gustan. Como dijo el autor, es muy fácil agregar componentes a una página nueva sin importar qué framework uses. Si están hechos con Web Components, de verdad puedes usarlos en cualquier lado. Al crear sitios auxiliares como la documentación de la API de nonio, fue muy útil poder elegir los componentes que quería aunque el stack fuera distinto. Si necesitas embeber un componente, importas el JS y agregas el HTML, y listo.
La encapsulación de estilos del shadow root también es bastante buena. Me gusta no tener que preocuparme de romper los estilos de algún componente mientras trabajo en una página. Se reduce la necesidad de adivinar y comprobar si vas a sobrescribir estilos de otros elementos, y también disminuye la necesidad de frameworks de pruebas de diferencias visuales para garantizarlo.
También me gusta que el tamaño del paquete sea pequeño. No hay librerías ni cargas adicionales, todo viene integrado. Como es fácil elegir solo los componentes necesarios, también es muy sencillo mantener bajo el tamaño del paquete.
Los componentes fuente de nonio están aquí: https://github.com/jjcm/nonio-frontend/tree/master/component...
En cuanto a la gestión de estado, tampoco hay nada que impida conectar cualquier solución de gestión de estado que quieras. Si el objetivo es minimizar dependencias, también he visto varias soluciones que usan el modelo de eventos integrado del navegador.
Por último, personalmente creo que escribir toda una app con Web Components puros es una idea bastante extrema. Con Lit, por 5 KB, obtienes una experiencia de desarrollo mucho mejor: https://lit.dev
Estos frameworks suelen obtener rendimiento al agrupar las manipulaciones del DOM en toda la página, pero con Web Components no puedes hacer eso.
Solo por esa razón, creo que Web Components es casi un callejón sin salida. Podrían ser útiles cuando solo necesitas pequeñas islas de funcionalidad, en lugar de una página renderizada completamente en el cliente.
Yo también vi límites de rendimiento similares, pero siempre pensé que era porque el shadow DOM era pesado. Ahora me pregunto si lo pesado es la propia creación de instancias.
También sentí el mismo problema con la gestión de estado, y estoy pensando en tomar ideas del desarrollo de escritorio: tener un bus de mensajes a nivel de ventana, que todos los componentes se suscriban y filtren, y publicar todos los cambios de estado en ese bus de mensajes.
Para ver un ejemplo de pasar objetos complejos como atributos, mira zx-listeditor aquí:
https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
El Web Component en sí está aquí:
https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
La pequeña librería de 500 líneas que lo hace posible está aquí:
https://github.com/wisercoder/uibuilder/#jsx-for-web-compone...
El patrón básico de LiveState es “disparar eventos y suscribirse al estado”. Los eventos y las actualizaciones de estado se envían mediante una conexión WebSocket, y las librerías de frontend y backend son una capa delgada sobre Phoenix Channels.
Actualmente las funciones manejadoras de eventos se escriben en Elixir, pero está en marcha el trabajo para permitir escribirlas en cualquier lenguaje que compile a WebAssembly.
No espero que mi framework JS preferido siga siendo el mismo. Sé que tienen una vida corta, y eso me gusta
Avanzar, progresar y mejorar es la razón por la que uso React, Vue, Svelte, etc. No quiero algo estático en este momento. Porque ninguno de los frameworks disponibles hoy, incluidos los Web Components, resuelve el problema de una forma especialmente elegante
Si la afirmación es que un sitio hecho con Web Components dura más que uno hecho con un framework JS, me gustaría apostar. Francamente, creo que en los próximos 20 años es más probable que alguna parte del stack tecnológico de Web Components se elimine de Chrome, Safari y Firefox y rompa la app, que que una app en React 16 escrita hace 2 años se rompa por cambios en JavaScript
Si fijas por completo todas las versiones de las dependencias de una app en React 16, puede que no se rompa, pero eso se parece más a ejecutar un programa viejo en una VM o emulador que a “poder seguir trabajando y hackeando en una app de React 16”
Una apuesta más significativa sería “¿se puede seguir desarrollando en una app de React ZZZ vs en una app hecha con Web Components?”
Si planteara las condiciones de una forma que favorezca mi punto de vista pero siga siendo más significativa, sería: “¿el costo de actualizar una app de React para ponerse al día con N años de actualizaciones de dependencias es mayor o menor que el de una app con Web Components?”. Si no metiste un montón de dependencias raras en la app con Web Components, creo que es muy probable que el costo de actualización sea mayor del lado de React
Básicamente, es apostar a que los proveedores de navegadores romperán menos la compatibilidad hacia atrás que los autores de React
HTML Imports se descartó en favor de una solución solo con JS, y Firefox ya lo había implementado
Custom Elements v0 también se descartó; Chrome lo implementó y YouTube se reescribió a toda prisa con eso. Como YouTube tardó tanto en migrar, hubo que esperar unos 4 años para eliminar la implementación
Para mantener el código seguro hay que actualizar las librerías. Pero las versiones antiguas de las librerías dejan de recibir soporte, y empiezan a acumularse vulnerabilidades conocidas que no se pueden corregir. Es muy probable que los atacantes tengan programas que escanean sitios web con vulnerabilidades conocidas y lanzan ataques automáticos
En cambio, si usas una librería o plataforma que valora la compatibilidad hacia atrás, como Web Components, puedes mantener la seguridad y al mismo tiempo dar soporte al código antiguo por más tiempo
Termino enfocándome más en las personas que en la tecnología
Si empiezo un proyecto nuevo, primero miro qué saben las personas que van a trabajar en él. Si en frontend hay un denominador común o mejores prácticas, como hoy ocurre con React, simplemente uso eso
No me complico mucho con este tipo de decisiones técnicas. La composición del equipo, el mercado de candidatos y los rankings de tecnologías de StackOverflow responden fácilmente esas preguntas
Pero si todo el mundo piensa así, aunque exista una mejor respuesta para crear software, vamos a usar React para siempre. Decidir usar lo popular significa depender, para mejorar el stack tecnológico, de otras personas que encuentren buenas soluciones técnicas y las vuelvan populares
Dicho de otra manera, esta estrategia claramente queda del lado de los adoptantes de etapa media-tardía o tardía en la curva de adopción. Buena parte de este tipo de artículos y discusiones está dirigida a adoptantes más tempranos
Como se puede escribir HTML dentro de Markdown, la idea de que incluso los diagramas interactivos vistosos sean portables como el resto de Markdown si pueden expresarse con etiquetas HTML normales es algo que MDX también puede hacer exactamente igual
La tecnología de Web Components en sí no aporta nada aquí. Lo que le importa al autor es la interfaz entre el marcado y los fragmentos de código que aplican funcionalidad en tiempo de ejecución. Web Components aquí son solo un detalle de implementación. No importa si al escribir un nombre entre corchetes angulares se hace referencia a un componente de React, a algo de Astro o a un Web Component. Lo esencial es que se invoca otra cosa
La desventaja de Web Components aquí es que, en la práctica, solo se ejecutan en el cliente. Como no hay forma de pasar al cliente el contenido de shadow DOM renderizado en el servidor, aunque un Web Component sea completamente no interactivo se necesita JS del cliente para renderizarlo. Con otros frameworks no pasa eso
No digo que el punto del autor sea incorrecto. Como forma de sistematizar la interfaz entre fragmentos de código, Web Components quizá sean la mejor y más duradera. Pero en el servidor, Web Components no ayudan en nada. No se renderizan, la interfaz es engorrosa y es muy probable que el código ya esté escrito de una forma que puede manejarse de manera integral. Si estás usando Astro en el servidor, sigue usando Astro. Lo mismo si es React, Vue o lo que sea
Donde Web Components brillan es en el cliente. Cuando usas componentes de terceros que quizá no estén escritos con el framework que elegiste, no necesitas saber con qué framework se hizo el componente que pones en la página. Escribes una etiqueta HTML y funciona. El mecanismo de interfaz sube al nivel del navegador, así que dos frameworks ya no necesitan conocer los detalles de implementación del otro
En ese sentido, es cierto que se necesita alguna herramienta. Pero esa herramienta puede ser cualquier motor de plantillas del lado del servidor que rellene el HTML inicial para hacer el bootstrap
Pero el shadow DOM renderizado en el servidor sí puede pasarse al cliente. A eso se le llama shadow DOM declarativo, y funciona en todos los navegadores principales excepto Firefox. Firefox también está trabajando en ello y existe un polyfill
https://developer.chrome.com/articles/declarative-shadow-dom...
Como referencia, este polyfill es bastante sencillo, así que podrías usarlo incluso si el navegador actual no lo soporta
Si aparecen Web Components que puedan renderizarse en el servidor, se abrirían muchas posibilidades para mejorar la experiencia de usuario en apps multipágina
El mundo de JS sigue demasiado obsesionado con el cliente grueso que tiene todo el control, y por eso se pierde cosas interesantes que están pasando en el mundo del servidor
Soluciones como Rails Hotwire, Phoenix LiveView y Laravel Livewire pueden resolver el 80% del frontend con un mínimo de JS y casi sin complejidad en la experiencia de desarrollo
También he creado apps frontend ricas en funcionalidades que leen y escriben en bases de datos y almacenamiento de objetos sin servidor. Son muy portables, y la instalación se reduce a subir unos cuantos archivos. Pueden ejecutarse directamente desde un CDN simple y son muy rápidas
La mayoría de los desarrolladores frontend que conozco también conocen la familia de Hotwire, pero para ellos ese tipo de stack suma complejidad a la experiencia de desarrollo. A veces la gente dice “complejidad”, pero en realidad quiere decir “falta de familiaridad”. Tanto el frontend como el backend pueden volverse excesivamente complejos, pero no tienen por qué hacerlo
Cuando algo sale mal, creo que es mejor tener más control. Sobre todo en entornos rápidos donde el cliente exige algo de inmediato
Personalmente, prefiero escribir todo en JavaScript/TypeScript antes que espolvorear azúcar sintáctica sobre HTML
Pasarlo al lado del servidor, como con Livewire, no hace que desaparezcan desventajas fuertes como la latencia
El título probablemente sea correcto, pero no sé si eso importa. La mayoría de las aplicaciones no vive tanto tiempo
Hace poco probé Web Components, y configurar funcionalidades muy simples en cualquier framework resultó realmente confuso y complejo. Si el componente es algo simple quizá no importe, pero si estás creando una aplicación con JS, los slots son una funcionalidad clave porque permiten crear componentes componibles
Depurar shadow DOM en las herramientas de desarrollo también fue realmente confuso
El renderProp de React o el scoped slot de Vue se usan muy comúnmente para inyectar datos desde un componente padre a un componente hijo. Pero reproducir ese comportamiento en un Web Component es prácticamente imposible sin hacks con MutationObserver. La suposición de Web Components de que los componentes hijo y padre no necesitan conocer información del otro no encaja con el mundo real
La suposición de que el componente hijo siempre se carga e inicializa independientemente del padre también es falsa en la mayoría de los frameworks. En la mayoría de los frameworks, el componente padre decide si realmente carga o inicializa al hijo
Web Components puede ser mejor para crear componentes aislados y reutilizables, pero la DOM API que se usa para pegar varias cosas y hacer que una aplicación funcione es simplemente pésima
Se nota que fue creada para editar documentos XML
Hay otra razón para no crear Web Components incluso para componentes aislados y reutilizables: ningún editor detecta bien cosas como el autocompletado de atributos o nombres de eventos, o la revisión de errores tipográficos
Para que sea útil, hay que unir varias dependencias, y probablemente esas dependencias no estén tan bien mantenidas como React
Trabajé en una empresa que seguía esta filosofía. Todos los componentes de UI eran Web Components con su propio paquete npm que había que instalar. Funcionaban bien, pero no tenían ninguna ventaja. Seis años después, siguen sin tener ventajas. Básicamente estás optimizando para un futuro que quizá nunca llegue
Un sitio HTML/CSS durará más que todo lo demás
Porque texto, imágenes y layout están dentro de algo que en realidad es estático, puede vivir para siempre sin tocarse y no requiere ejecutar código complejo ni mantener semiestándares ambiguos que cambian rápido
Si quieres crear sitios duraderos y contar con tecnologías duraderas, aprende a construir sitios web HTML con mejora progresiva. Crear elementos HTML indefinidos con guiones e inútiles como Web Components, para luego ejecutar un montón de JavaScript y definirlos, o usar frameworks de JavaScript que ni siquiera fingen usar HTML, solo durará unos años
Al menos hasta que los navegadores eliminen el soporte para HTTP/1.1