No culpes al desarrollador de lo que hicieron los frameworks.

En este post traemos a detalle un punto de gran interés acerca de los frameworks y un enfoque basado principalmente en Qwik. Espero que puedas disfrutar de un artículo enriquecedor para entender no solo código, también sintaxis.

· 10 min de lectura
No culpes al desarrollador de lo que hicieron los frameworks.

Hablemos de las herramientas que tenemos para crear aplicaciones web y de cómo esas herramientas nos engañan. 🥹

Historia común


Estás empezando un nuevo proyecto, y esta vez, ¡te aseguras de que el sitio sea rápido y hábil! La línea de salida parece buena, pero pronto tu aplicación se hace grande, y el rendimiento de arranque empieza a sufrir. Antes de que te des cuenta, tienes una aplicación enorme entre manos, y te sientes impotente para arreglarla. ¿En qué te has equivocado? 🥲

Todas las herramientas/marcos prometen un resultado mejor y más rápido, pero tenemos todo Internet para decirnos que el resultado es cualquier cosa menos una web mejor y más rápida. ¿Y a quién culpar de esto sino a los desarrolladores? 😡

Se equivocaron, tomaron atajos, etc., y por eso acabaron con una web lenta. O eso nos dicen.

No es culpa tuya


Si algunos sitios son más lentos, culpar al desarrollador puede tener sentido. "Mira, los otros sitios son rápidos; claramente, el resto del mundo sabe cómo hacerlo bien". Algo así.

Pero ese no es el mundo en el que vivimos. Todos los sitios son lentos. ¿Cómo se puede culpar al desarrollador cuando nadie tiene éxito? El problema es sistémico. Tal vez no sea culpa del desarrollador.

Esta es una historia de cómo construimos aplicaciones, las herramientas que usamos, las promesas que hacen las herramientas y el mundo de lentitud en el que acabamos. Sólo hay una conclusión. Las herramientas han prometido más de la cuenta, y esto es un problema sistémico en la industria. No se trata sólo de unas pocas manzanas podridas, sino de toda la red mundial.

Demasiado código


Todos sabemos cuál es el problema: ¡demasiado código! Nos hemos vuelto tan buenos creando código que los navegadores no pueden seguirnos el ritmo. Mires donde mires, todo el mundo te dice que tu sitio tiene demasiado JavaScript, pero nadie sabe cómo reducirlo.

Es como si un canal de fitness de YouTube te dijera que todo lo que tienes que hacer para perder peso es comer menos calorías. 🤣  Es un consejo fácil, pero el porcentaje de éxito es desastroso.

La razón es que el consejo ignora las hormonas del apetito. ¿Cómo puedes mantener la voluntad de comer menos calorías cuando tienes hambre, estás débil y sólo piensas en comida? La situación está en tu contra. Puede que el secreto para perder peso no sea reducir las calorías, sino controlar el apetito. Pero, ¿cuándo fue la última vez que escuchó a alguien hablar del control del apetito?

Eso es lo que ocurre con la hinchazón de JavaScript. Sabemos que necesitamos menos JavaScript, pero todas las herramientas siguen añadiendo más y más código a tu aplicación.

Evolución de la agrupación


Veamos primero cómo hemos llegado a esta situación y, a continuación, analicemos el camino a seguir.

Si tu quieres aprender más acerca de Qwik, recuerda que puedes ir al siguiente vídeo 👉🏼

Generación 0: concatenación


Antes de los módulos ECMAScript, no había nada. Sólo archivos. La concatenación era sencilla. Los archivos se concatenaban y se envolvían en IIF. Era lo más sencillo que se podía conseguir.

La ventaja era que resultaba difícil añadir más código a la aplicación, por lo que los paquetes seguían siendo pequeños. La reutilización de código simplemente no era una cosa con este modelo. Todo el mundo concatenaba el código de forma ligeramente diferente.

Generación 1: Bundlers


Aparecieron los módulos ECMAScript, una sintaxis adecuada para importar y exportar código. Esto era increíble, pero entregar miles de archivos al navegador era un problema. Así surgió toda una industria artesanal de empaquetadores: WebPack, Rollup, CommonJS, etc.

Ahora, la reutilización de código es posible. Sin embargo, era demasiado fácil instalar con npm una dependencia y empaquetarla. Rápidamente el tamaño se convirtió en un problema.

En su haber, los bundlers saben cómo hacer tree-shaking y eliminación de código muerto. Estas características aseguran que sólo el código accesible es empaquetado.

Gen 2: Carga lenta


Reconociendo el problema de los bundles grandes, los bundlers empezaron a ofrecer lazy loading. La carga perezosa es fantástica, ya que permite dividir el código en trozos y entregarlo al navegador según sea necesario. Esto es genial porque permite que la aplicación se entregue por partes, empezando por la parte más necesaria. También permite paralizar la descarga, el análisis y la ejecución.

El problema es que, en la práctica, construimos aplicaciones utilizando frameworks, y los frameworks tienen mucha influencia en cómo los bundlers dividen nuestro código en trozos cargados perezosamente. El problema es que la carga perezosa de un fragmento introduce una llamada asíncrona a la API: una PROMESA. Y si el framework espera una referencia síncrona a tu código, el bundler no puede introducir un chunk cargado perezosamente.

Estamos en una pequeña falacia. Los empaquetadores afirman sinceramente que pueden cargar código de forma perezosa, pero a menos que el marco de trabajo pueda acomodar una PROMESA como desarrollador, puede que no tengas muchas opciones.

Gen 3: Carga perezosa de componentes que no están en el árbol de renderizado


Los frameworks se apresuraron a aprovechar la capacidad de carga lenta de los bundlers, y hoy en día casi todos saben cómo hacerlo. ¡Pero hay una GRAN advertencia! Los frameworks sólo pueden cargar componentes que no estén en el árbol de renderizado actual. 😖

¿Qué es el árbol de renderizado?

Es un conjunto de componentes que forman la página actual. Una aplicación suele tener muchos más componentes de los que hay en la página actual. Una parte del árbol de renderizado es la vista. Es lo que se ve actualmente en la ventana del navegador. Pero un árbol de renderizado abarca todo el DOM, incluso los componentes fuera de la vista actual.

Supongamos que un componente está en el árbol de renderizado. En ese caso, el framework debe descargar el componente porque el framework necesita reconstruir el árbol de renderizado de componentes como parte de la hidratación o actualización. Los frameworks sólo pueden hacer lazy load de componentes que actualmente NO están en el árbol de render.

Otro punto es que los frameworks pueden lazy load componentes pero eso siempre incluye el comportamiento. La unidad de componente es demasiado grande porque abarca el comportamiento. Sería mejor si la unidad de carga perezosa fuera más pequeña.

Renderizar un componente no debería requerir la descarga de los manejadores de eventos del componente. El framework sólo debería descargar los manejadores de eventos en la interacción con el usuario y no como parte del método de renderizado del componente.

Dependiendo del tipo de aplicación que estés construyendo, los manejadores de eventos pueden representar la mayor parte de tu código. Por lo tanto, el acoplamiento de la descarga de la representación del componente y el comportamiento es subóptima.

El núcleo del problema


Sería genial poder cargar de forma perezosa la función de renderizado del componente sólo cuando el componente necesite ser renderizado y cargar de forma perezosa los manejadores de eventos sólo si el usuario está interactuando con ellos. Por defecto, todo debería cargarse perezosamente.

Pero hay un gran problema con este enfoque. El problema es que los frameworks necesitan reconciliar su estado interno con el DOM. Y eso significa que al menos una vez en la hidratación de la aplicación, tienen que ser capaces de hacer un render completo para reconstruir el estado interno del framework.

Después de la primera renderización, los frameworks pueden ser más quirúrgicos con sus actualizaciones, pero el daño ya está hecho, el código se ha descargado. Así que tenemos dos problemas:

Los frameworks necesitan descargar y ejecutar componentes para reconstruir el árbol de renderizado en el arranque. (Ver la hidratación es pura sobrecarga) Esto obliga a una ansiosa descarga y ejecución de todos los componentes en el árbol de render.


Los manejadores de eventos vienen con los componentes aunque no sean necesarios en el momento del renderizado. La inclusión de manejadores de eventos fuerza la descarga de código innecesario.


Así que el status quo de los frameworks actuales es que cada componente (y sus manejadores) en el árbol de renderizado SSR/SSG debe ser descargado y ejecutado ansiosamente. La carga perezosa con los frameworks actuales es un poco mentira porque no se puede hacer carga perezosa en el renderizado inicial de la página.

Vale la pena señalar que incluso si un desarrollador introduce límites de carga perezosa en la página inicial SSR/SSG, esto no ayuda. El framework todavía tendrá que descargar y ejecutar todos los componentes en la respuesta SSR/SSG; por lo tanto, mientras el componente esté en el árbol de renderizado, el framework tendrá que cargar ansiosamente los componentes que el desarrollador intentó cargar perezosamente.

La descarga ansiosa de los componentes en el árbol de renderizado es el núcleo del problema, y no hay nada que el desarrollador pueda hacer al respecto. Aún así, eso no impide que se culpe al desarrollador de la lentitud del sitio. ¡Qué divertido! 🤪

Nueva generación: Carga diferida detallada


¿Y ahora qué hacemos? La respuesta obvia es que tenemos que ser más precisos. La solución es tan obvia como difícil de implementar.

  1. Tendríamos que cambiar los frameworks para que no carguen ansiosamente el árbol de renderizado en la hidratación. 😂
  2. Permitir que la función de renderizado del componente se cargue independientemente de los manejadores de eventos del componente.


Si tu framework puede hacer las dos partes anteriores, el usuario vería grandes beneficios. La aplicación tendría requisitos de arranque ligeros porque no sería necesario renderizar al arrancar (el contenido ya está renderizado en SSR/SSG). Se descarga menos código. Y cuando el framework determina que un componente en particular necesita ser renderizado, el framework puede hacerlo descargando la función de renderizado sin descargar todos los manejadores de eventos.

La carga diferida detallada sería una gran ventaja para el rendimiento de inicio del sitio. Es mucho más rápido porque la cantidad de código descargado sería proporcional a la interactividad del usuario en lugar de ser proporcional a la complejidad del árbol de renderizado inicial.

Tus sitios se volverían más rápidos, no porque mejoráramos en hacer el código pequeño, sino porque mejoraríamos en descargar sólo lo que necesitamos en lugar de descargar todo por adelantado.

Puntos de entrada


No es suficiente tener un framework que pueda hacer lazy loading de grano fino. Porque, para aprovechar las ventajas de la carga diferida fina, primero debes tener paquetes para la carga diferida.🫣

Para que los bundlers creen chunks cargables perezosamente, los bundlers necesitan un punto de entrada para cada chunk. Si todo lo que tienes es un único punto de entrada a tu aplicación, los bundlers no pueden crear chunks. Y si eso es cierto, incluso si tu framework pudiera hacer una carga diferida detallada, no tendría nada que cargar diferidamente.

Crear puntos de entrada es engorroso hoy en día porque requiere que el desarrollador escriba código extra. Cuando desarrollamos la aplicación, realmente sólo podemos pensar en una cosa, que es la funcionalidad.

Es simplemente injusto para los desarrolladores hacerles pensar en la característica que están construyendo y en la carga perezosa al mismo tiempo. Así que en la práctica, la creación de puntos de entrada para bundlers no es una cosa.

Lo que se necesita es un marco que cree los puntos de entrada sin que el desarrollador piense en ello. Crear puntos de entrada para los bundlers es responsabilidad del framework, NO del desarrollador. La responsabilidad del desarrollador es crear funcionalidades. La responsabilidad del marco de trabajo es pensar en las implicaciones de bajo nivel de cómo se debe lograr la característica. Si el marco no hace eso, entonces no está sirviendo plenamente a las necesidades del desarrollador.

Sobreproducción de puntos de entrada


El objetivo debe ser crear tantos puntos de entrada como sea posible. Pero, se preguntarán algunos, ¿no provocaría eso la descarga de muchos trozos pequeños en lugar de unos pocos grandes? La respuesta es un rotundo "no".

Sin un punto de entrada, un empaquetador no puede crear un nuevo trozo. Pero nada impide que el empaquetador ponga varios puntos de entrada en un solo trozo. Cuantos más puntos de entrada tenga, más libertad tendrá para montar los bundles de la forma más óptima. Los puntos de entrada te dan libertad para optimizar tus bundles. Cuantos más haya, mejor.

El marco del futuro


Los marcos de la próxima generación tendrán que resolver estos problemas:

  1. Tener un DX que la gente adore (los frameworks existentes ya existen).
  2. Realizar una carga diferida del código.
  3. Generar automáticamente muchos puntos de entrada para soportar la carga diferida de grano fino.
Los desarrolladores construirán sus sitios, tal como lo hacen hoy, pero los sitios no abrumarán al navegador con enormes paquetes y ejecución al inicio de la aplicación.

Qwik es un framework diseñado teniendo en cuenta estos principios. Qwik fine-grained lazy loading es por manejador de eventos, función de renderizado y efecto.

Conclusión


Nuestros sitios web son cada vez más grandes, sin fin a la vista. Son grandes porque hoy hacen más que antes: más funciones, animaciones, etc. Podemos suponer que la tendencia continuará.

La solución a este problema es realizar una carga lenta y precisa del código para que el navegador no se sature en la carga inicial de la página.  🤷🏻‍♀️

Nuestras herramientas de empaquetado soportan la carga diferida, pero nuestros frameworks no. La hidratación de frameworks obliga a que todos los componentes del árbol de renderizado se carguen en la hidratación. (El único tipo de carga perezosa que soportan los frameworks hoy en día es para componentes que actualmente no están en el árbol de renderizado).

Los frameworks también descargan los manejadores de eventos con el componente a pesar de que los manejadores de eventos pueden ser la mayor parte del código y no serán necesarios para la hidratación inicial.

Debido a que los bundlers pueden afinar lazyload, pero nuestros frameworks no, fallamos en reconocer la sutileza en esto. El resultado es que culpamos de la lentitud del arranque del sitio a los desarrolladores porque creemos erróneamente que podrían haber hecho algo para evitar la situación, aunque la realidad es que han tenido muy poco que decir en este asunto.

Necesitamos nuevos tipos de frameworks que se diseñen con la carga perezosa de grano fino como característica central de los frameworks (como Qwik). No podemos esperar que los desarrolladores asuman esta responsabilidad; ya están abrumados de funciones.

Los frameworks deben pensar tanto en la carga diferida de los tiempos de ejecución como en la creación de los puntos de entrada para que los bundlers puedan crear los trozos para la carga diferida. Los beneficios de los frameworks de nueva generación compensarían el coste de cambiar a ellos.

Fuente

Plataforma de cursos gratis sobre programación