No todo es Hidratación Qwik

La hidratación es una solución para añadir interactividad al HTML renderizado en el servidor. Así es como la Wikipedia define la hidratación

· 8 min de lectura
No todo es Hidratación Qwik

En el desarrollo web, la hidratación o rehidratación es una técnica en la que el JavaScript del lado del cliente convierte una página web HTML estática, entregada a través del alojamiento estático o del renderizado del lado del servidor, en una página web dinámica adjuntando manejadores de eventos a los elementos HTML.

La definición anterior habla de la hidratación en términos de adjuntar manejadores de eventos al HTML estático. Sin embargo, adjuntar manejadores de eventos al DOM no es la parte desafiante o costosa de la hidratación, y por lo tanto se pierde el punto de por qué alguien llamaría a la hidratación una sobrecarga. Para este artículo, la sobrecarga es un trabajo que puede evitarse y que sigue conduciendo al mismo resultado final. Si se puede eliminar y el resultado es el mismo, es una sobrecarga.

Profundizar en la hidratación


Lo difícil de la hidratación es saber QUÉ manejadores de eventos necesitamos y DÓNDE hay que adjuntarlos.

QUÉ: El manejador de eventos es un cierre que contiene el comportamiento del manejador de eventos. Es lo que debe ocurrir si un usuario activa este evento.

DÓNDE: La ubicación del elemento DOM al que se debe adjuntar el QUÉ (incluye el tipo de evento.)

La complicación añadida es que WHAT es un cierre que se cierra sobre APP_STATE y FRAMEWORK_STATE:

APP_STATE: el estado de la aplicación. APP_STATE es lo que la mayoría de la gente piensa que es el estado. Sin APP_STATE, tu aplicación no tiene nada dinámico que mostrar al usuario.

FRAMEWORK_STATE: el estado interno del framework. Sin FRAMEWORK_STATE, el framework no sabe qué nodos del DOM debe actualizar ni cuándo debe hacerlo. Algunos ejemplos son el árbol de componentes y las referencias a las funciones de renderización.

Entonces, ¿cómo recuperamos WHAT (APP_STATE + FRAMEWORK_STATE) y WHERE? Descargando y ejecutando los componentes actualmente en el HTML. La descarga y ejecución de los componentes renderizados en el HTML es la parte costosa.

En otras palabras, la hidratación es un hack para recuperar el APP_STATE y el FRAMEWORK_STATE ejecutando  el código de la aplicación en el navegador e implica:

  1. Descargar el código del componente
  2. Ejecución del código del componente
  3. recuperando el WHAT(APP_STATE + FRAMEWORK_STATE) y WHERE para obtener el cierre del manejador de eventos
  4. Adjuntando WHAT (el cierre del manejador de eventos) a WHERE (un elemento del DOM)

Llamemos a los tres primeros pasos la fase de RECOVERY. El  RECOVERY es cuando el framework intenta reconstruir la aplicación. La reconstrucción es costosa porque requiere descargar y ejecutar el código de la aplicación.

RECOVERY es directamente proporcional a la complejidad de la página que se está hidratando y puede tardar fácilmente 10 segundos en un dispositivo móvil. Dado que  RECOVERY es la parte más cara, la mayoría de las aplicaciones tienen un rendimiento de arranque subóptimo, especialmente en los móviles.

RECOVERY también es un puro gasto general. Los gastos generales son el trabajo realizado que no aporta valor directamente. En el contexto de la hidratación RECOVERY  es una sobrecarga porque reconstruye la información que el servidor ya recogió como parte de SSR/SSG. En lugar de enviar la información al cliente, ésta se descarta. Como resultado, el cliente debe realizar costosas.

RECOVERY para reconstruir lo que el servidor ya tenía. Si el servidor hubiera serializado la información y la hubiera enviado al cliente junto con el HTML, el  RECOVERY podría haberse evitado. La información serializada ahorraría al cliente la descarga y ejecución ansiosa de todos los componentes del HTML.

La reejecución de código en el cliente que el servidor ya ejecutó como parte de SSR/SSG es lo que hace que la hidratación sea pura sobrecarga: es decir, una duplicación del trabajo del cliente que el servidor ya hizo. El marco podría haber evitado el coste transfiriendo la información del servidor al cliente, pero en lugar de eso, tiró la información.

En resumen, la hidratación consiste en recuperar los manejadores de eventos descargando y reejecutando todos los componentes del HTML renderizado por SSR/SSG. El sitio se envía al cliente dos veces, una como HTML y otra como JavaScript. Además, el framework debe ejecutar con avidez el JavaScript para recuperar WHAT, WHERE, APP_STATE y FRAMEWORK_STATE.

¡¡Todo este trabajo sólo para recuperar algo que el servidor ya tenía pero que descartó!!

Para apreciar por qué la hidratación obliga a duplicar el trabajo en el cliente, veamos un ejemplo con unos pocos componentes simples.

Utilizaremos una sintaxis popular entendida por mucha gente, pero ten en cuenta que se trata de un problema general no específico de ningún framework.

export const Main = () => <>
   <Greeter />
   <Counter value={10}/>
</>

export const Greeter = () => {
  return (
    <button onClick={() => alert('Hello World!'))}>
      Greet
    </button>
  )
}

export const Counter = (props: { value: number }) => {
  const store = useStore({ count: props.number || 0 });
  return (
    <button onClick={() => store.count++)}>
      {count}
    </button>
  )
}

Lo anterior dará como resultado este HTML después del SSR/SSG:

<button>Greet</button>
<button>10</button>

El HTML no lleva ninguna indicación de dónde están los manejadores de eventos o los límites de los componentes. El HTML resultante no contiene WHAT(APP_STATE, FRAMEWORK_STATE) o WHERE.

La información existía cuando el servidor generó el HTML, pero el servidor no la serializó. Lo único que puede hacer el cliente para que la aplicación sea interactiva es recuperar la información descargando y ejecutando el código. Esto lo hacemos para recuperar los cierres de los manejadores de eventos que se cierran sobre el estado.

El punto aquí es que el código debe ser descargado y ejecutado antes de que cualquier manejador de eventos pueda ser adjuntado y los eventos procesados. La ejecución del código instala los componentes y recrea el estado (WHAT(APP_STATE, FRAMEWORK_STATE) y WHERE.

Una vez completada la hidratación, la aplicación puede ejecutarse. Al hacer clic en los botones se actualizará la interfaz de usuario como se espera.

Resumiendo: una alternativa a la hidratación sin gastos generales
Entonces, ¿cómo se diseña un sistema sin hidratación y, por tanto, sin gastos generales?

Para eliminar la sobrecarga, el marco no sólo debe evitar  RECOVERY  pero también el paso cuatro de arriba. El cuarto paso consiste en fijar el WHAT a WHERE, y es un coste que puede evitarse.

Para evitar este coste, necesitas tres cosas:

  1. Serializar toda la información requerida como parte del HTML. La información serializada debe incluir WHAT, WHERE,APP_STATE y  FRAMEWORK_STATE.
  2. Un manejador de eventos global que se basa en el burbujeo de eventos para interceptar todos los eventos. El manejador de eventos necesita ser global para que no nos veamos forzados a registrar  todos los eventos individualmente en elementos específicos del DOM.
  3. Una función de fábrica que puede recuperar perezosamente el manejador de eventos (WHAT).

¡Una función de fábrica es la clave! La hidratación crea el WHAT  porque necesita el WHAT para adjuntarlo a WHERE En cambio, podemos evitar hacer un trabajo innecesario creando el WHAT como respuesta a un evento del usuario.

La configuración anterior es reanudable porque puede reanudar la ejecución donde el servidor la dejó sin rehacer ningún trabajo que el servidor ya hizo. Y lo que es más importante, la configuración no tiene sobrecarga porque todo el trabajo es necesario y ninguno de los trabajos es rehacer lo que el servidor ya hizo.

Una buena forma de pensar en la diferencia es considerar los sistemas push y pull.

Push (hidratación): Descarga y ejecuta código para registrar los manejadores de eventos, justo en el caso de la interacción del usuario.

Pull (resumir): No hacer nada, esperar a que un usuario desencadene un evento, luego crear perezosamente el manejador para procesar el evento.

En la hidratación, la creación del manejador de eventos ocurre antes de que el evento sea disparado y por lo tanto es ansioso. La hidratación también requiere que todos los posibles manejadores de eventos sean creados y registrados, por si acaso el usuario dispara el evento (trabajo potencialmente innecesario). Así que la creación de manejadores de eventos es especulativa. Es un trabajo extra que puede no ser necesario. (El manejador de eventos también se crea rehaciendo el mismo trabajo que el servidor ya ha hecho; por lo tanto es una sobrecarga)

En un sistema resumible, la creación del manejador de eventos es perezosa. Por lo tanto, la creación ocurre después de que el evento es disparado y es estrictamente sobre una base de necesidad. El framework crea el manejador de eventos al deserializarlo, y por lo tanto el cliente no rehace ningún trabajo que el servidor ya haya hecho.

La creación perezosa de manejadores de eventos es la forma en que funciona Qwik, lo que le permite crear tiempos de inicio de la aplicación más rápidos.

La resumibilidad requiere que serialicemos WHAT(APP_STATE, FRAMEWORK_STATE) y WHERE. Un sistema resumible puede generar el siguiente HTML como posible solución para almacenar  WHAT(APP_STATE, FRAMEWORK_STATE) y WHERE.Los detalles exactos no son importantes, sólo que toda la información esté presente.

<div q:host>
  <div q:host>
    <button on:click="./chunk-a.js#greet">Greet</button>
  </div>
  <div q:host>
    <button q:obj="1" on:click="./chunk-b.js#count[0]">10</button>
  </div>
</div>
<script>/* code that sets up global listeners */</script>
<script type="text/qwik">/* JSON representing APP_STATE, FRAMEWORK_STATE */</script>

Cuando el HTML anterior se cargue en el navegador, se ejecutará inmediatamente el script inline que configura el listener global. La aplicación está lista para aceptar eventos, pero el navegador no ha ejecutado ningún código de la aplicación. Esto es lo más cercano a cero-JS que se puede conseguir.

El HTML contiene el WHERE codificados como atributos en el elemento. Cuando el usuario desencadena un evento, el framework puede utilizar la información del DOM para crear perezosamente el manejador de eventos. La creación implica la deserialización perezosa de APP_STATE y FRAMEWORK_STATE  para completar el WHAT Una vez que el framework crea perezosamente el manejador de eventos, éste puede procesar el evento. Observa que el cliente no está rehaciendo ningún trabajo que el servidor ya haya hecho.

Una nota sobre el uso de la memoria


Los elementos del DOM retienen los manejadores de eventos durante el tiempo de vida del elemento. La hidratación crea todos los oyentes. Por lo tanto, la hidratación requiere asignar una memoria al inicio.

Los frameworks resumibles no crean los manejadores de eventos hasta que el evento se dispara. Por lo tanto, los frameworks resumibles consumirán menos memoria que la hidratación.

Además, el enfoque resumible no retiene el manejador de eventos después de la ejecución. El manejador de eventos es liberado después de su ejecución, devolviendo la memoria.

En cierto modo, liberar la memoria es lo contrario de la hidratación. Es como si el marco hidratara perezosamente una WHAT, lo ejecuta y luego lo deshace. No hay mucha diferencia entre la primera y la enésima ejecución del manejador. La creación y liberación perezosa de los manejadores de eventos no se ajusta al modelo mental de hidratación.

Conclusión


La hidratación es una sobrecarga porque duplica el trabajo. El servidor acumula el WHERE y WHAT (APP_STATE and FRAMEWORK_STATE), pero la información es descartada en lugar de ser serializada para el cliente.

El cliente recibe entonces un HTML que no tiene suficiente información para reconstruir la aplicación. La falta de información obliga al cliente a descargar ansiosamente la aplicación y ejecutarla para recuperar el WHERE y WHAT (APP_STATE y FRAMEWORK_STATE).

Un enfoque alternativo es la resumibilidad. La resumibilidad se centra en transferir toda la información del servidor al cliente. La información contiene WHERE y WHAT (APP_STATE y  FRAMEWORK_STATE). La información adicional permite al cliente razonar sobre la aplicación sin necesidad de descargar el código de la misma de forma ansiosa. Sólo una interacción del usuario obliga al cliente a descargar el código para manejar esa interacción específica. El cliente no está duplicando ningún trabajo del servidor; por lo tanto, no hay sobrecarga.

Qwik, es  un marco de trabajo diseñado en torno a la resumibilidad y que consigue un excelente rendimiento de arranque.

Fuente

Plataforma de cursos gratis sobre programación