JavaScript Responsable: Parte I

Jesús Ricarte
A List Apart en Español
10 min readMar 11, 2020
Una ilustración de un extintor de incendios junto a fila de cerillas ardiendo

Por Jeremy Wagner. Original en inglés, traducido al español por Jesús Ricarte.

Según los números, JavaScript es responsabilidad de rendimiento. Si la tendencia persiste, la página media enviará al menos 400 KB de ella antes que pase demasiado tiempo, y eso es simplemente lo que se transfiere. Como otros recursos basados en texto, JavaScript se sirve casi siempre comprimido — pero eso puede ser lo único que estamos consiguiendo, de manera consistente, en su entrega.

Desafortunadamente, mientras que la reducción del tiempo de transferencia de recursos es una gran parte de todo el asunto del rendimiento, la compresión no tiene efecto sobre cuánto tiempo toman los navegadores para procesar un script una vez que llega en su totalidad. Si un servidor envía 400 KB de JavaScript comprimido, la cantidad real que los navegadores tendrán que procesar, tras la descompresión, está por encima de un megabyte. La capacidad de los dispositivos para hacer frente a estas pesadas cargas de trabajo depende, claro, del dispositivo. Se ha escrito mucho sobre lo hábiles que son diversos dispositivos para procesar grandes cantidades de JavaScript, pero la verdad es que la cantidad de tiempo que toma procesar, incluso una cantidad trivial, varía mucho entre dispositivos.

Tomemos, por ejemplo, este proyecto descartable mío, el cual sirve alrededor de 23 KB de JavaScript descomprimido. En un Macbook Pro de mediados de 2017, Chrome mastica esta comparativamente pequeña carga en unos 25 milisegundos. En un teléfono Android Nokia 2, sin embargo, esta cifra se eleva hasta unos 190 milisegundos. No es una cantidad insignificante de tiempo, pero en cualquier caso, la página se vuelve interactiva razonablemente rápido.

Ahora, la gran pregunta: ¿cómo crees que ese pequeño Nokia 2 se desempeña en una página promedio? Se ahoga. Incluso en una conexión rápida, navegar por la web es un ejercicio de paciencia, ya que las páginas cargadas de JavaScript se bloquean durante largos períodos de tiempo.

Figura 1. Vista general de un cronograma de rendimiento en un teléfono Android Nokia 2, navegando en una página en la que un JavaScript excesivo monopoliza el hilo principal.

Mientras que los dispositivos y las redes por las que navegan en la web, están mejorando enormemente, nos estamos comiendo esas ganancias, tal como sugieren las tendencias. Necesitamos usar JavaScript responsablemente. Esto empieza por entender qué estamos construyendo, además de cómo lo estamos construyendo.

La mentalidad de “sitios” versus “aplicaciones”

La nomenclatura puede resultar extraña, en tanto que identificamos vagamente las cosas con términos que son inexactos, pero sus significados son entendidos implícitamente por todos. A veces sobrecargamos el término “abeja” para que signifique también “avispa”, aunque las diferencias entre abejas y avispas son sustanciales. Esas diferencias pueden motivarte a tratar cada una de manera diferente. Por ejemplo, querremos acabar con un nido de avispas, pero como las abejas son insectos muy beneficiosos y vulnerables, podríamos optar por reubicarlas.

Podemos ser igual de rápidos e imprecisos al intercambiar los términos “sitio web” y “aplicación web”. Las diferencias entre ellos son menos claras que entre avispas y abejas, pero combinarlos puede provocar resultados dolorosos. El dolor viene de las comodidades que nos permitimos cuando algo es simplemente un “sitio web” versus una “aplicación web” repleta de funcionalidades. Si estás haciendo un sitio web informativo para un negocio, estarás menos inclinado a apoyarte en un framework potente para manejar los cambios en el DOM o implementar el enrutamiento del lado del cliente — al menos, eso espero. Usar herramientas tan poco adecuadas para la tarea, no solo iría en detrimento de la gente que usa ese sitio, sino que podría decirse que es menos productivo.

Cuando construimos una aplicación web, sin embargo, cuidado. Estamos instalando paquetes que introducen centenares — si no miles— de dependencias, algunas de las cuales no estamos seguros de que sean seguras. Además estamos escribiendo complicadas configuraciones para paquetes de módulos. En este frenético, aunque ubicuo, tipo de entorno de desarrollo, se necesita conocimiento y vigilancia para asegurar que lo que se construye es rápido y accesible. Si tienes dudas sobre esto, ejecuta npm ls--proden el directorio raíz de tu proyecto y observa si reconoces todo lo que hay en esa lista. Incluso si lo haces, esto no tiene en cuenta scripts de terceros — de los que estoy seguro que tu sitio tiene al menos unos pocos.

Lo que tendemos a olvidar es que el entorno que ocupan los sitios webs y las aplicaciones web es uno y el mismo. Ambas están sujetas a las mismas presiones ambientales que el numeroso gradiente de redes y dispositivos impone. Esas restricciones no desaparecen de repente cuando decidimos llamar “aplicaciones” a lo que construimos, ni los teléfonos de nuestros usuarios adquieren nuevos poderes mágicos cuando lo hacemos.

Es nuestra responsabilidad evaluar quién usa lo que hacemos, y aceptar que las condiciones bajo las que acceden a internet pueden ser diferentes a las que hemos asumido. Necesitamos conocer el propósito al que estamos intentando servir, y sólo entonces podremos construir algo que sirva, admirablemente, a ese propósito — incluso si no es emocionante construirlo.

Eso significa reevaluar nuestra dependencia en el JavaScript y cómo su uso — particularmente con la exclusión de HTML y CSS — puede tentarnos a adoptar patrones insostenibles que perjudican el rendimiento y la accesibilidad.

No dejes que los frameworks te fuercen a patrones insostenibles

He sido testigo de algunos descubrimientos extraños en las bases de datos de código, al trabajar con equipos que dependían de frameworks para ayudarlos a ser altamente productivos. Una característica común entre muchos de ellos es que a menudo se producen patrones de accesibilidad y rendimiento deficientes. Tomemos el siguiente componente de React, como ejemplo:

Aquí hay algunos problemas notables de accesibilidad:

  1. Un formulario que no usa un elemento <form>, no es un formulario. De hecho, puedes disimularlo especificando role="form" en el <div> padre, pero si estás construyendo un formulario — y esto seguro que se parece a uno— usa un elemento <form> con los atributos action y method apropiados. El atributo actiones crucial, ya que se asegura de que el formulario haga algo aún en ausencia de JavaScript — siempre que el componente sea generado en un servidor, por supuesto.
  2. Un <span> no es sustituto para un elemento <label>, el cual proporciona beneficios en la accesibilidad que los <span> no proporcionan.
  3. Si tenemos la intención de hacer algo en el lado del cliente antes de enviar un formulario, entonces deberemos mover la acción vinculada al manejador del evento onClick del elemento <button> al evento onSubmit del elemento <form>.
  4. Ya de paso, ¿por qué usar JavaScript para validar una dirección de e-mail cuando HTML5 ofrece controles de validación de formularios en casi cualquier navegador, de IE10 en adelante? Aquí tenemos una oportunidad de confiar en el navegador y utilizar un tipo de input apropiado, así como el atributo required— pero sé consciente que hacer que todo esto funcione correctamente en lectores de pantalla require un poco de conocimiento.
  5. Aún no siendo un problema de accesibilidad, este componente no depende de ningún estado o método de ciclo de vida, lo que significa que puede ser refactorizado en un componente funcional sin estado, que utilizaría menos JavaScript que un componente de React completamente desarrollado.

Sabiendo todo esto, podemos refactorizar este componente:

Este componente no solo es más accesible ahora, si no que además utilizar menos JavaScript. En un mundo que se está ahogando en JavaScript, borrar líneas debería sentirse francamente terapéutico. El navegador te da mucho de manera gratuita, y deberíamos intentar tomar ventaja de ello tan seguido como sea posible.

Esto no quiere decir que los patrones inaccesibles se producen solo cuando se usan frameworks, sino más bien que una preferencia exclusiva por JavaScript hará surgir, eventualmente, lagunas en nuestra comprensión de HTML y CSS. Estas lagunas de conocimiento suelen resultar en errores de los puede que ni siquiera somos conscientes. Los frameworks pueden ser herramientas útiles para incrementar nuestra productividad, pero la educación continua en las tecnologías centrales de la web es esencial para crear experiencias usables, sin importar qué herramientas decidamos utilizar.

Confía en la plataforma web y llegarás lejos, y rápido

Ya que estamos en el tema de los frameworks, hay que decir que la plataforma web es un framework formidable en sí misma. Como se mostraba en la sección anterior, estamos mejor cuando podemos confiar en los patrones de marcado establecidos y en las características del navegador. La alternativa es reinventarlos, e invitar a todo el dolor que tales esfuerzos nos garantizan, o aún peor: simplemente asumir que el autor de cada paquete JavaScript que instalamos ha solucionado el problema de forma exhaustiva y cuidadosa.

Aplicaciones de página unica

Una de las concesiones que los desarrolladores hacen rápidamente es adoptar el modelo de aplicación de página única (SPA), incluso si no se ajusta al proyecto. Sí, ganas mejor rendimiento percibido con el enrutado del lado de cliente de una SPA, ¿pero qué pierdes?. La funcionalidad de navegación propia del navegador — aunque sincrónica — proporciona un montón de beneficios. Por ejemplo, el historial es gestionado de acuerdo a una especificación compleja. Los usuarios sin JavaScript — ya sea por decisión propia o no — no perderán el acceso por completo. Para que las SPAs sigan disponibles sin JavaScript, el renderizado en el lado del servidor se convierte en algo que hay que considerar.

Figura 2. Una comparativa de una aplicación de ejemplo, cargando sobre una conexión lenta. La aplicación a la izquierda depende enteramente de JavaScript para renderizar una página. La aplicación a la derecha renderiza una respuesta en el servidor, y después usa hidratación en cliente para añadir componentes al marcado existente, renderizado por el servidor.

La accesibilidad también se ve perjudicada si el enrutador del lado del cliente falla al dar a conocer que la página ha cambiado. Esto puede dejar a quienes dependen de tecnologías asistivas, la tarea de averiguar qué cambios se han producido en la página, lo que puede ser una tarea ardua.

Luego está nuestro viejo némesis: los gastos generales. Algunos enrutadores en el lado de cliente son muy pequeños, pero cuando empiezas con React, un enrutador compatible, y posiblemente una librería de gestión de estado, estás aceptando que hay una cierta cantidad de código que no podrás optimizar — aproximadamente 135KB en este caso. Considera cuidadosamente qué estás construyendo y si un enrutador del lado del cliente merece las concesiones que inevitablemente harás. Normalmente, estarás mejor sin él.

Si te preocupa el rendimiento percibido de la navegación, podrías inclinarte por rel=prefetch para, especulativamente, pre-cargar documentos desde el mismo origen. Esto tiene un efecto dramático en la mejora del rendimiento percibido en la carga de las páginas, ya que el documento está disponible inmediatamente en la caché. Como estas pre-cargas están hechas con prioridad baja, es menos probable que compitan por recursos críticos para el ancho de banda.

Figura 3. El HTML para la URL writing/ se pre-carga desde la página inicial. Cuando la URL writing/ es requerida por el usuario, su HTML se carga instantáneamente desde la caché del navegador.

El principal inconveniente con la pre-carga de enlaces es que necesitas ser consciente de que puede ser potencialmente un derroche. Quicklink, una pequeña librería de Google para la pre-carga de enlaces, lo mitiga un poco comprobando si el cliente se encuentra en una conexión lenta — o tiene habilitado el modo de ahorro de datos — y evita pre-cargar enlaces de orígenes cruzados por defecto.

Los service workers también son muy beneficiosos para el rendimiento percibido por usuarios recurrentes, tanto si usamos enrutado en el lado del cliente o no — siempre que sepas cómo funciona. Cuando pre-cacheamos rutas con un service worker, obtenemos algunos de los mismos beneficios que con la pre-carga de enlaces, pero con un grado de control mucho mayor sobre las solicitudes y respuestas. Tanto si piensas en tu sitio como una “app”, como si no, añadir un service worker es quizás uno de los usos más responsables de JavaScript que existe hoy en día.

JavaScript no es la solución a tus problemas de maquetación

Si estamos instalando un paquete para solucionar un problema de maquetación, procede con cautela y pregúntate “¿qué estoy intentado conseguir?” CSS está diseñado para hacer este trabajo, y no requiere de abstracciones para ser usado efectivamente. Muchos de los problemas de maquetación que los paquetes de JavaScript intentan resolver, como ubicación de elementos, alineación, dimensionamiento, gestión de desbordamiento de texto, e incluso sistemas de maquetación completos, se pueden solucionar con CSS hoy en día. Los motores modernos de disposición de elementos como Flexbox o Grid están lo suficientemente bien soportados como para que no necesitemos empezar un proyecto con un framework de maquetación. CSS es el framework. Cuando tenemos feature queries, mejorar progresivamente las maquetaciones para adoptar nuevos motores de maquetación, no resulta tan difícil.

Usar soluciones de JavaScript para problemas de maquetación y presentaciones no es nuevo. Es algo que hacíamos cuando nos mentíamosen 2009, creyendo que todos los sitios web se tenían que ver en IE6 exactamente igual que en los navegadores más capaces de esa época. Si todavía estamos desarrollando sitios web para que se vean igual en cada navegador en 2019, deberíamos reevaluar nuestros objetivos de desarrollo. Siempre habrá algún navegador al que tengamos de dar soporte que no pueda hacer todo lo que los navegadores modernos, siempre actualizados, sí pueden hacer. Una paridad visual total en todas las plataformas no solo es una búsqueda en vano, es el principal enemigo de la mejora progresiva.

No estoy aquí para matar a JavaScript

No te equivoques, no tengo mala voluntad hacia JavaScript. Me ha dado una carrera y — si soy honesto conmigo mismo — una fuente de disfrute durante más de una década. Como en cualquier relación a largo plazo, he aprendido más sobre él cuanto más tiempo he invertido. Es un lenguaje maduro y rico en características que solo se vuelve más capaz y elegante con cada año que pasa.

Aun así, hay momentos en los que siento que JavaScript y yo estamos en desacuerdo. Soy crítico con JavaScript. O tal vez con mayor precisión, soy crítico sobre cómo hemos desarrollado una tendencia a verlo como el primer recurso, a la hora de construir para la web. Mientras separo otro paquete, de modo no muy diferente a una bola enredada de luces de Navidad, queda claro que la web está borracha de JavaScript. Lo utilizamos para casi cualquier cosa, incluso cuando la ocasión no lo requiere. Algunas veces me pregunto qué tan atroz será la resaca.

En la serie de artículos que vendrá a continuación, daré más consejos prácticos a seguir para detener la marea invasora del exceso de JavaScript y cómo podemos arreglarlo para que lo que construyamos para la web sea usable — o al menos más — para todos en todas partes. Algunos consejos serán preventivos. Otros serán medidas mitigadoras. En cualquier caso y con suerte, los resultados serán los mismos. Creo que todos amamos la web y queremos hacer lo correcto por ella, pero quiero que pensemos en cómo hacerla más flexible e inclusiva para todos.

Si te gusta lo que hace A List Apart, ¡haz una inversión en apoyarnos! O síguenos en Twitter y Facebook. ¡Gracias!

--

--

Jesús Ricarte
A List Apart en Español

Front-end developer: UX, performance, clean code, cross-browser, responsive. @alistapartES volunteer translator. I like neither drop-downs nor magic numbers.