A medida que creamos sitios que dependen más de JavaScript, a veces pagamos por lo que enviamos de formas que no siempre podemos ver con facilidad. En este artículo, analizaremos por qué un poco de disciplina puede ayudar si quieres que tu sitio se cargue y sea interactivo rápidamente en dispositivos móviles. Entregar menos JavaScript puede significar menos tiempo en la transmisión de red, menos tiempo dedicado a descomprimir el código y menos tiempo para analizar y compilar este JavaScript.
Red
Cuando la mayoría de los desarrolladores piensan en el costo de JavaScript, lo hacen en términos del costo de descarga y ejecución. Enviar más bytes de JavaScript por cable lleva más tiempo cuanto más lenta sea la conexión del usuario.

Esto puede ser un problema, ya que el tipo de conexión de red efectivo que tiene un usuario podría no ser 3G, 4G o Wi-Fi. Puedes estar en la red Wi-Fi de una cafetería, pero conectado a un hotspot móvil con velocidades de 2G.
Puedes reducir el costo de transferencia de red de JavaScript de las siguientes maneras:
- Solo se envía el código que necesita un usuario.
- Usa la división de código para separar el código de JavaScript en lo que es crítico y lo que no. Los empaquetadores de módulos, como webpack, admiten la división de código.
- Carga diferida en código que no es crítico.
- Reducción
- Usa UglifyJS para reducir el código ES5.
- Usa babel-minify o uglify-es para reducir ES2015+.
- Compresión
- Quita el código sin usar.
- Identifica oportunidades para el código que se puede quitar o cargar de forma diferida con la cobertura de código de las Herramientas para desarrolladores.
- Usa babel-preset-env y browserlist para evitar transpilar funciones que ya están en los navegadores actualizados. Los desarrolladores avanzados pueden encontrar que un análisis cuidadoso de sus paquetes de webpack ayuda a identificar oportunidades para recortar dependencias innecesarias.
- Para quitar código, consulta tree-shaking, las optimizaciones avanzadas de Closure Compiler y los complementos de recorte de bibliotecas, como lodash-babel-plugin o ContextReplacementPlugin de webpack para bibliotecas como Moment.js.
- Almacenar en caché el código para minimizar los viajes de red
- Usa el almacenamiento en caché de HTTP para asegurarte de que los navegadores almacenen en caché las respuestas de manera eficaz. Determina la vida útil óptima para las secuencias de comandos (max-age) y proporciona tokens de validación (ETag) para evitar transferir bytes sin cambios.
- El almacenamiento en caché de Service Worker puede hacer que tu app sea resiliente a la red y darte acceso inmediato a funciones como la caché de código de V8.
- Usa el almacenamiento en caché a largo plazo para evitar tener que volver a recuperar los recursos que no cambiaron. Si usas Webpack, consulta filename hashing.
Analizar y compilar
Una vez descargado, uno de los costos más altos de JavaScript es el tiempo que tarda un motor de JS en analizar o compilar este código. En las Herramientas para desarrolladores de Chrome, el análisis y la compilación forman parte del tiempo amarillo de "Secuencias de comandos" en el panel Rendimiento.

En las pestañas Bottom-Up y Call Tree, se muestran los tiempos exactos de análisis y compilación:
Pero ¿por qué es importante?

Dedicar mucho tiempo al análisis o la compilación del código puede retrasar en gran medida el momento en que un usuario puede interactuar con tu sitio. Cuanto más JavaScript envíes, más tiempo tardará en analizarse y compilarse antes de que tu sitio sea interactivo.
Byte por byte, el navegador tarda más en procesar JavaScript que una imagen o una fuente web del mismo tamaño. Tom Dale
En comparación con JavaScript, existen numerosos costos relacionados con el procesamiento de imágenes de tamaño equivalente (¡todavía deben decodificarse!), pero en el hardware móvil promedio, es más probable que JS afecte de forma negativa la interactividad de una página.
Cuando decimos que el análisis y la compilación son lentos, el contexto es importante: nos referimos a los teléfonos celulares promedio. Los usuarios promedio pueden tener teléfonos con CPU y GPU lentas, sin caché L2/L3 y que incluso pueden tener limitaciones de memoria.
Las capacidades de la red y las del dispositivo no siempre coinciden. Un usuario con una conexión de fibra increíble no necesariamente tiene la mejor CPU para analizar y evaluar el código JavaScript que se envía a su dispositivo. Esto también se aplica a la inversa: una conexión de red terrible, pero una CPU ultrarrápida. — Kristofer Baxter, LinkedIn
A continuación, podemos ver el costo de analizar ~1 MB de JavaScript descomprimido (simple) en hardware de gama baja y alta. Existe una diferencia de 2 a 5 veces en el tiempo para analizar o compilar código entre los teléfonos más rápidos del mercado y los teléfonos promedio.
¿Qué tal un sitio del mundo real, como CNN.com?
En el iPhone 8 de alta gama, se tarda solo ~4 s en analizar/compilar el JS de CNN, en comparación con ~13 s en un teléfono promedio (Moto G4). Esto puede afectar significativamente la rapidez con la que un usuario puede interactuar por completo con este sitio.
Esto destaca la importancia de realizar pruebas en hardware promedio (como el Moto G4) en lugar de solo en el teléfono que tienes en el bolsillo. Sin embargo, el contexto es importante: optimiza para el dispositivo y las condiciones de red que tienen tus usuarios.
¿Realmente estamos enviando demasiado JavaScript? Este… tal vez :)
Si usamos HTTP Archive (los 500,000 sitios principales) para analizar el estado de JavaScript en dispositivos móviles, podemos ver que el 50% de los sitios tardan más de 14 segundos en volverse interactivos. Estos sitios dedican hasta 4 segundos solo a analizar y compilar JS.

Si se tiene en cuenta el tiempo que se tarda en recuperar y procesar JS y otros recursos, tal vez no sea sorprendente que los usuarios tengan que esperar un tiempo antes de sentir que las páginas están listas para usarse. Definitivamente, podemos mejorar en este aspecto.
Quitar el código JavaScript no esencial de tus páginas puede reducir los tiempos de transmisión, el análisis y la compilación que consumen mucha CPU, y la posible sobrecarga de memoria. Esto también ayuda a que tus páginas sean interactivas más rápido.
Tiempo de ejecución
No solo el análisis y la compilación pueden tener un costo. La ejecución de JavaScript (ejecutar código una vez analizado o compilado) es una de las operaciones que debe realizarse en el subproceso principal. Los tiempos de ejecución prolongados también pueden retrasar el momento en que un usuario puede interactuar con tu sitio.

Si la secuencia de comandos se ejecuta durante más de 50 ms, el tiempo de interactividad se retrasa por la cantidad de tiempo total que se tarda en descargar, compilar y ejecutar el JS. Alex Russell
Para abordar esto, JavaScript se beneficia de estar en fragmentos pequeños para evitar que se bloquee el subproceso principal. Explora si puedes reducir la cantidad de trabajo que se realiza durante la ejecución.
Costos adicionales
JavaScript puede afectar el rendimiento de la página de otras maneras:
- Memoria Las páginas pueden parecer lentas o pausarse con frecuencia debido a la GC (recolección de elementos no utilizados). Cuando un navegador recupera memoria, se pausa la ejecución de JS, por lo que un navegador que recopila elementos no utilizados con frecuencia puede pausar la ejecución con más frecuencia de la que nos gustaría. Evita las fugas de memoria y las pausas frecuentes de la recolección de elementos no utilizados para que las páginas no tengan bloqueos.
- Durante el tiempo de ejecución, el código JavaScript de larga duración puede bloquear el subproceso principal, lo que hace que las páginas no respondan. Dividir el trabajo en partes más pequeñas (con
requestAnimationFrame()orequestIdleCallback()para la programación) puede minimizar los problemas de capacidad de respuesta, lo que puede ayudar a mejorar la Interaction to Next Paint (INP).
Patrones para reducir el costo de entrega de JavaScript
Cuando intentas mantener bajos los tiempos de análisis, compilación y transmisión de red para JavaScript, existen patrones que pueden ayudar, como la división basada en rutas o PRPL.
PRPL
PRPL (Push, Render, almacenamiento previo en caché, Lazy-load) es un patrón que optimiza la interactividad a través de la división de código y el almacenamiento en caché agresivos:

Visualicemos el impacto que puede tener.
Analizamos el tiempo de carga de sitios para dispositivos móviles y apps web progresivas populares con las estadísticas de llamadas del entorno de ejecución de V8. Como podemos ver, el tiempo de análisis (que se muestra en naranja) es una parte significativa de donde muchos de estos sitios pasan su tiempo:

Wego, un sitio que usa PRPL, logra mantener un tiempo de análisis bajo para sus rutas, lo que permite que sea interactivo muy rápidamente. Muchos de los otros sitios mencionados anteriormente adoptaron la división de código y los presupuestos de rendimiento para intentar reducir sus costos de JS.
Bootstrapping progresivo
Muchos sitios optimizan la visibilidad del contenido a expensas de la interactividad. Para obtener un primer procesamiento de imagen rápido cuando tienes grandes paquetes de JavaScript, los desarrolladores a veces emplean la renderización del servidor y, luego, la "actualizan" para adjuntar controladores de eventos cuando finalmente se recupera el JavaScript.
Ten cuidado, ya que esto tiene sus propios costos. 1) Por lo general, envías una respuesta HTML más grande que puede impulsar nuestra interactividad, 2) puedes dejar al usuario en un valle inquietante en el que la mitad de la experiencia no puede ser interactiva hasta que JavaScript termine de procesarse.
La inicialización progresiva puede ser un mejor enfoque. Envía una página con un funcionamiento mínimo (compuesta solo por el HTML, JS y CSS necesarios para la ruta actual). A medida que llegan más recursos, la app puede cargar de forma diferida y desbloquear más funciones.
Cargar el código de forma proporcional a lo que se ve es el santo grial. PRPL y el arranque progresivo son patrones que pueden ayudar a lograrlo.
Conclusiones
El tamaño de la transmisión es fundamental para las redes de gama baja. El tiempo de análisis es importante para los dispositivos vinculados a la CPU. Mantener estos valores bajos es importante.
Los equipos lograron adoptar presupuestos de rendimiento estrictos para mantener bajos los tiempos de transmisión y análisis/compilación de JavaScript. Consulta el artículo de Alex Russell "Can You Afford It?: Real-world Web Performance Budgets" para obtener orientación sobre los presupuestos para dispositivos móviles.
Si estás creando un sitio orientado a dispositivos móviles, haz todo lo posible para desarrollarlo en un hardware representativo, mantén bajos los tiempos de análisis y compilación de JavaScript, y adopta un presupuesto de rendimiento para asegurarte de que tu equipo pueda controlar los costos de JavaScript.
Más información
- Chrome Dev Summit 2017: Prácticas recomendadas para la carga moderna
- Rendimiento de inicio de JavaScript
- Solving the web performance crisis, de Nolan Lawson
- ¿Puedes pagarlo? Presupuestos de rendimiento del mundo real — Alex Russell
- Evaluación de frameworks y bibliotecas web, Kristofer Baxter
- Resultados de Cloudflare de experimentar con Brotli para la compresión (ten en cuenta que Brotli dinámico con una calidad más alta puede retrasar la renderización inicial de la página, así que evalúa con cuidado. En su lugar, es probable que quieras realizar una compresión estática).
- Performance Futures — Sam Saccone