Fatiga de Javascript: HTMX es todo lo que necesita para crear ChatGPT – Parte 1

Había una época, hace mucho tiempo, en la que crear sitios web era fácil. HTML y CSS. Se sintió simple. Hoy en día, los frameworks Javascript están en todas partes. Cambio implacable, complejidad creciente. Este fenómeno se llama “fatiga de Javascript” y se trata de desarrolladores agotados por buscar los últimos marcos, herramientas de creación y bibliotecas y tratar de mantener el ritmo. Con HTMX, los desarrolladores ahora tienen una manera de crear aplicaciones web atractivas con mayor simplicidad y menos agotamiento, y sin todas las molestias de JS.

Una aplicación web atractiva como ChatGPT, en menos de 200 líneas de código, puro Python y HTML. Como este:

Un repaso rápido sobre cómo funcionaba la Web

Cuando Tim Berners-Lee creó la primera página web en 1990, el sistema que diseñó era principalmente un sistema de “sólo lectura”, que conducía a páginas conectadas entre sí mediante hipervínculos, que todos conocemos como etiquetas de anclaje en HTML. Por lo tanto, HTML 1.0 se basaba en una única etiqueta y ofrecía una navegación sencilla entre páginas.

Sobre nosotros

La etiqueta de anclaje es un control hipermedia que realiza el siguiente proceso:

mostrar al usuario que se trata de un enlace (en el que se puede hacer clic) emitir una solicitud GET a la URL del hipervínculo

Cuando el servidor responde con una nueva página, el navegador reemplazará la página actual con la nueva página (navegación)

Luego vino la Web 2.0 que introdujo una nueva etiqueta, la etiqueta de formulario. Esta etiqueta permitía actualizar recursos además de leerlos a través de la etiqueta. Poder actualizar los recursos significó que realmente podíamos comenzar a crear aplicaciones web. Todo ello con sólo dos controles: y .

El proceso al enviar un formulario es bastante similar al de la etiqueta de anclaje, excepto que podemos:

elegir qué tipo de solicitud queremos realizar (GET o POST) adjuntar información del usuario como correo electrónico, contraseña, etc. para pasar con la solicitud

Las dos etiquetas son los únicos elementos, en HTML puro, que pueden interactuar con un servidor.

Y luego vino Javascript.

JavaScript se creó originalmente para agregar interacciones simples a las páginas web: validación de formularios, obtención de datos y animaciones básicas. Pero con la introducción de XMLHttpRequest (más tarde conocido como AJAX), JavaScript evolucionó hasta convertirse en algo mucho más potente y complejo.

Con Javascript, los desarrolladores ahora pueden activar solicitudes HTTP sin las dos etiquetas, usando algo llamado AJAX. AJAX permite recuperar datos del servidor y, aunque XHR puede recuperar cualquier tipo de datos, incluidos fragmentos HTML sin formato, texto o XML, JSON se convirtió en el formato de intercambio de datos de facto.

Esto significa que es necesario que haya un paso adicional en el que JSON se convierta a HTML, mediante una función que represente HTML a partir de JSON. Como se muestra en el siguiente ejemplo, procedemos de la siguiente manera:

obtener datos JSON de los puntos finales /api/users (la parte respuesta => respuesta.json()) insertar estos datos en plantillas HTML (la parte html constante) que luego se agregarán al DOM (la parte document.getElementById()) // La forma de JavaScript: conversión JSON → HTML fetch(‘/api/users’) .then(response => Response.json()) .then(users => { const html = usuarios.map(usuario => `

${nombre.usuario}

` ).unirse(”); document.getElementById(‘usuarios’).innerHTML = html; });

Esta representación implica un estrecho acoplamiento entre el formato de datos JSON y la función misma: si el formato de datos JSON cambia, podría interrumpir la función de representación HTML. Ya ve un problema potencial aquí, y este punto suele ser un punto de fricción entre los desarrolladores frontend y backend: el desarrollador frontend crea una interfaz de usuario basada en un formato JSON esperado, el desarrollador backend decide cambiar el formato, el desarrollador frontend necesita actualizar la interfaz de usuario, el desarrollador backend cambia nuevamente, el desarrollador frontend cambia nuevamente, etc.

Por alguna razón, los desarrolladores web empezaron a colocar JSON en todas partes y gestionaron todo con JS. Esto llevó a lo que llamamos aplicaciones de una sola página (SPA): a diferencia del HTML 2.0 tradicional, ya no navegamos entre páginas. Todo el contenido permanece en una página y se actualiza con la representación JS y UI. Así funcionan marcos como React, Angular, Vue.js.

“La norma emergente para el desarrollo web es construir una aplicación React de una sola página, con renderizado de servidor. Los dos elementos clave de esta arquitectura son algo así como:
– La interfaz de usuario principal está construida y actualizada en JavaScript usando React o algo similar.
– El backend es una API contra la que esa aplicación realiza solicitudes.
Esta idea realmente ha arrasado en Internet. Comenzó con algunos sitios web importantes y populares y se ha extendido a rincones como blogs y sitios de marketing”.

(Tom MacWright, https://macwright.com/2020/05/10/spa-fatigue)

La mayoría de las arquitecturas SPA actuales son aplicaciones de “cliente pesado” donde la mayor parte del trabajo ocurre en el lado del cliente y donde el backend es simplemente una API que devuelve JSON. Esta configuración es conocida por brindar experiencias de usuario ágiles y fluidas, pero ¿realmente necesitamos esa complejidad en todo momento?

“(…) también hay muchos problemas para los cuales no veo ningún beneficio concreto al usar React. Esas son cosas como blogs, sitios web de carritos de compras, en su mayoría sitios web CRUD y formularios”.

(Tom MacWright, https://macwright.com/2020/05/10/spa-fatigue)

La fatiga de Javascript es real

La “fatiga de Javascript” es cada vez más fuerte. Se refiere a los principales inconvenientes del desarrollo de SPA:

Complejidad creciente: las bibliotecas y los marcos se han vuelto cada vez más pesados ​​y complejos, y requieren grandes equipos para administrarlos. Algunos marcos obstinados también significan que los desarrolladores de JS deben especializarse en una tecnología. Ningún desarrollador de Python se llamó a sí mismo “un desarrollador de Python de Tensorflow”. Son solo desarrolladores de Python, y cambiar de TF a Pytorch aún significa que puedes leer y usar los dos. Estrecho acoplamiento: el acoplamiento entre las API de datos y la interfaz de usuario crea fricciones dentro de los equipos. Todos los días se producen cambios importantes y no hay forma de resolver esto mientras los equipos utilicen JSON como interfaz de intercambio. Proliferación de frameworks: el número de frameworks sigue aumentando, lo que genera una sensación real de “fatiga” entre los desarrolladores de JS. Sobreingeniería: no necesita marcos con mucho JS el 90% del tiempo. Y en algunos casos (aplicaciones con mucho contenido), es incluso una mala idea.

Excepto en el caso de las UI altamente interactivas/colaborativas, un HTML simple con aplicaciones de varias páginas suele ser suficiente.

Entonces, ¿qué es HTMX?

HTMX es una biblioteca JS muy liviana (14k) que ofrece un enfoque centrado en HTML para crear aplicaciones web dinámicas. Extiende HTML al permitir que cualquier elemento realice solicitudes AJAX y actualice cualquier parte del DOM. A diferencia de los marcos JS que hacen todo el renderizado en el lado del cliente, el trabajo pesado lo realiza el servidor devolviendo fragmentos HTML para insertarlos en el DOM. Esto también significa que si ya conoce los motores de plantillas y HTML, la curva de aprendizaje será mucho más fácil en comparación con aprender React o Angular.

En lugar de abandonar el hipermedia por las API JSON, HTMX hace que HTML sea más capaz con lo siguiente:

Cualquier elemento puede realizar solicitudes HTTP (no solo y) Cualquier método HTTP (GET, POST, PUT, DELETE, PATCH) Cualquier elemento puede ser objeto de actualizaciones Cualquier evento puede desencadenar solicitudes (hacer clic, enviar, cargar, etc.)

De hecho, ¡puedes escribir tu propia interfaz de usuario tipo GPT con HTMX y solo unas pocas líneas de Python!

Una demostración real: una aplicación ChatGPT con HTMX y FastAPI

Para este artículo, crearemos un pequeño chat con menos de 100 líneas de Python y HTML. Comenzaremos con demostraciones muy simples para mostrar cómo funciona HTMX, luego agregaremos una interfaz de usuario de chat simple y luego agregaremos una capacidad de transmisión a nuestro chat. Para hacer las cosas aún más atractivas, usaremos el kit de herramientas de desarrollo de agentes de Google, para que podamos aprovechar los agentes en nuestro chat.

Demostraciones HTML simples

Supongamos que tenemos una API que devuelve una lista de usuarios. Queremos hacer clic en un botón para recuperar los datos y mostrar una lista.

La forma JS tradicional:

Demostración

Y así es como lo harías con HTMX.

Primero crea tu backend:

from fastapi import FastAPI, Solicitud de fastapi.responses import HTMLResponse de fastapi.templating import Jinja2Templates importación solicitudes aplicación = FastAPI() plantillas = Jinja2Templates(directory=”templates”) @app.get(“/”, respuesta_class=HTMLResponse) async def home(solicitud: Solicitud): return templates.TemplateResponse(“demo.html”, {“solicitud”: request}) @app.get(“/users”) async def get_users(): r = request.get(“https://dummyjson.com/users”) data = r.json() html = “” para la fila de datos[‘users’]: html += f”{fila[‘firstName’]} {fila[‘lastName’]}\n” devuelve HTMLResponse(html)

Y luego el HTML:

Demostración

¡Y obtienes exactamente el mismo resultado! ¿Qué pasó justo aquí? Mira el elemento. Vemos 3 atributos que comienzan con hx-. ¿Para qué están aquí?

hx-get: al hacer clic en este botón se activará una solicitud GET al punto final /users hx-target: le indica al navegador que reemplace el contenido del elemento que tiene la identificación de la lista de usuarios con los datos HTML recibidos del servidor hx-swap: le indica al navegador que inserte el HTML dentro del elemento de destino

Con eso ya sabes cómo usar HTMX. Lo hermoso de esta forma de hacerlo es que si decides cambiar tu HTML, no dañará nada en tu página.

Por supuesto, existen ventajas y desventajas al utilizar HTMX. Pero como desarrollador de Python, es muy agradable jugar con mi backend FastAPI y no preocuparme mucho por renderizar HTML. Simplemente agregue plantillas de Jinja, una dosis de Tailwind CSS y ¡listo!

Nuestro primer chat con HTMX y FastAPI

Así que ahora es el momento en que las cosas se están poniendo serias. Lo que haremos, como primer paso, será construir un chatbot tonto que tome la consulta de los usuarios y la escupe al revés. Para eso construiremos una página con:

una lista de mensajes un área de texto para la entrada del usuario

¡Y adivina qué, HTMX se encargará de enviar/recibir los mensajes! Así es como se verá el resultado:

Descripción general

El flujo es el siguiente:

El usuario ingresa una consulta en un área de texto. Esta área de texto está envuelta en un formulario, que enviará una solicitud POST al servidor con el parámetro de consulta. El backend recibe la solicitud, hace algo con la consulta (en la vida real, podemos usar un LLM para responder la consulta). En nuestro caso, a modo de demostración, simplemente responderemos revirtiendo la consulta letra por letra. El backend envuelve la respuesta en una HTMLResponse (¡no en JSON!). En nuestro formulario, HTMX le dice al navegador dónde insertar la respuesta, como se muestra en hx-target, y cómo intercambiarla con el DOM actual.

Y esto es todo. ¡Así que comencemos!

backend

Definiremos una ruta /send que espera una cadena de consulta desde la interfaz, la invierte y la envía de vuelta en una etiqueta.

de fastapi importar FastAPI, solicitud, formulario de fastapi.templating importar Jinja2Templates de fastapi.responses importar HTMLResponse importar asyncio importar tiempo aplicación = FastAPI() plantillas = Jinja2Templates(“plantillas”) @app.get(“/”) async def root(solicitud: Solicitud): devolver plantillas.TemplateResponse(solicitud, “simple_chat_sync.html”) @app.post(“/enviar”) async def send_message(solicitud: Solicitud, consulta: str=Form(…)): mensaje = “”.join(lista(consulta)[::-1]) html = f”” devuelve HTMLResponse(html)

Interfaz

En el lado del frontend, definimos una página HTML simple usando Tailwind CSS y HTMX:

[email protected]&display=swap” rel=”hoja de estilo”> // ZeChat

Echemos un vistazo más de cerca a la etiqueta. Esta etiqueta tiene varios atributos, así que tomemos un minuto para revisarlos:

hx-post=”/send”: realizará una solicitud POST al punto final /send. hx-trigger=”click from:#submitButton”: Esto significa que la solicitud se activará cuando se haga clic en el botón de envío hx-target=”#chat”: Esto le indica al navegador dónde colocar la respuesta HTML. En ese caso, queremos que la respuesta se agregue a la lista. hx-swap=”beforeend”: hx-target indica dónde colocar el contenido, hx-swap indica CÓMO. En ese caso, queremos que el contenido se agregue antes del final (es decir, después del último hijo)

El hx-on::before-request es un poco más complejo, pero se puede explicar fácilmente. Básicamente ocurre entre el clic y el momento en que se envía la solicitud. Agregará la entrada del usuario al final de la lista y borrará la entrada del usuario. ¡De esta manera, obtenemos una experiencia de usuario ágil!

Un chat mejor (streaming + LLM)

Lo que creamos es un chat muy simple pero funcional; sin embargo, si queremos conectar un LLM, es posible que en ocasiones la respuesta del servidor demore mucho tiempo. La forma en que se construye nuestro chat actual es sincrónica, lo que significa que no sucederá nada hasta que el LLM termine de escribirse. No es una gran experiencia de usuario.

Lo que necesitamos ahora es streaming y un LLM real con quien conversar. Y esta es la Parte 2.