Optimización de inicio de JavaScript

Addy Osmani
Addy Osmani

A medida que creamos sitios que dependen más de JavaScript, a veces pagamos por lo que enviamos de maneras que no siempre podemos ver con facilidad. En este artículo, analizaremos por qué un poco de disciplina puede ser útil si quieres que tu sitio se cargue y sea interactivo rápidamente en dispositivos móviles. Proporcionar menos JavaScript puede implicar 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 consideran el costo de JavaScript, lo hacen en términos del costo de descarga y ejecución. Si se envían más bytes de JavaScript por la red, cuanto más lenta sea la conexión de un usuario, más tiempo se necesitará.

Cuando un navegador solicita un recurso, ese recurso se debe recuperar y descomprimir. En el caso de los recursos como JavaScript, deben analizarse y compilarse antes de la ejecución.

Esto puede ser un problema, ya que es posible que el tipo de conexión de red real que tiene un usuario no sea 3G, 4G o Wi-Fi. Puedes estar en la red Wi-Fi de la cafetería, pero con conexión a un hotspot móvil con velocidades 2G.

Puedes reducir el costo de transferencia de red de JavaScript mediante lo siguiente:

  • Envía solo el código que el usuario necesita.
  • Reducción
  • Compresión
    • Como mínimo, usa gzip para comprimir los recursos basados en texto.
    • Considera usar Brotli con q11. Brotli supera el rendimiento de gzip en el índice de compresión. Ayudó a CertSimple a ahorrar un 17% del tamaño de los bytes de JS comprimidos y a LinkedIn a ahorrar un 4% de sus tiempos de carga.
  • Cómo quitar el código que no se usa.
  • Almacena código en caché para minimizar los viajes de red.
    • Usa el almacenamiento en caché HTTP para garantizar que los navegadores almacenen las respuestas en caché de manera eficaz. Determina la duración óptima de las secuencias de comandos (max-age) y proporciona tokens de validación (ETag) para evitar la transferencia de bytes sin cambios.
    • El almacenamiento en caché de Service Worker puede hacer que la red de tu app sea más resiliente y proporcionarte acceso impecable 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 Hash de nombre de archivo.

Analizar/compilar

Una vez descargado, uno de los costos más grandes de JavaScript es el tiempo necesario para que un motor de JS analice y compile el 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 de rendimiento.

ALT_TEXT_HERE

Las pestañas Bottom-Up y Call Tree muestran los tiempos exactos de análisis y compilación:

ALT_TEXT_HERE
Panel Performance de Chrome DevOps > Bottom-Up. Si habilitas las estadísticas de llamada de tiempo de ejecución de V8, podemos ver el tiempo transcurrido en fases como Parse y Compile.

Pero ¿por qué es importante?

ALT_TEXT_HERE

Si tardas mucho tiempo en analizar y compilar el código, es posible que un usuario demore mucho en interactuar con tu sitio. Cuanto más código JavaScript envíes, más tiempo se necesitará para analizarlo y compilarlo antes de que tu sitio sea interactivo.

Byte por byte, el procesamiento de JavaScript es más costoso para el navegador que el procesamiento de imágenes o fuentes web de tamaño equivalente (Tom Dale)

En comparación con JavaScript, hay numerosos costos relacionados con el procesamiento de imágenes de tamaño equivalente (aún se deben decodificar), pero en hardware móvil promedio, es más probable que el JS afecte negativamente la interactividad de una página.

ALT_TEXT_HERE
Los bytes de imagen y JavaScript tienen costos muy diferentes. Por lo general, las imágenes no bloquean el subproceso principal ni evitan que las interfaces sean interactivas mientras se decodifican y se generan las tramas. Sin embargo, JS puede retrasar la interactividad debido a los costos de análisis, compilación y ejecución.

Cuando decimos que el análisis y la compilación son lentos, es importante el contexto. A continuación, 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 puede tener restricciones de memoria.

Las capacidades de la red y las capacidades del dispositivo no siempre coinciden. Un usuario con una conexión de Fiber 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 es así a la inversa: una conexión de red terrible, pero una CPU increíblemente rápida. Kristofer Baxter, LinkedIn

A continuación, podemos ver el costo de analizar alrededor de 1 MB de JavaScript descomprimido (simple) en hardware de gama baja y de alta gama. Hay una diferencia de 2 a 5 veces en el tiempo para analizar y compilar código entre los teléfonos más rápidos del mercado y los teléfonos promedio.

ALT_TEXT_HERE
En este gráfico, se destacan los tiempos de análisis de un paquete de 1 MB de JavaScript (aproximadamente 250 KB comprimidos) en computadoras de escritorio y dispositivos móviles de diferentes clases. Cuando se observa el costo del análisis, se deben considerar las cifras sin comprimir, p. ej., unos 250 KB de JS comprimidos se descomprimen en alrededor de 1 MB de código.

¿Qué sucede con un sitio del mundo real, como CNN.com?

En el iPhone 8 de alta gama, el análisis y la compilación del código JS de CNN solo toma unos 4 segundos, en comparación con los unos 13 segundos que se necesitan en un teléfono promedio (Moto G4). Esto puede afectar significativamente la rapidez con la que un usuario puede interactuar completamente con este sitio.

ALT_TEXT_HERE
Arriba se muestran los tiempos de análisis de una comparación del rendimiento del chip A11 Bionic de Apple con el Snapdragon 617 en el hardware más promedio de Android.

Esto destaca la importancia de realizar pruebas en hardware promedio (como Moto G4) y no solo en el teléfono que tengas en el bolsillo. El contexto, sin embargo, es importante: realiza optimizaciones en función de las condiciones de red y dispositivo que tengan tus usuarios.

ALT_TEXT_HERE
Google Analytics puede proporcionar estadísticas sobre las clases de dispositivos móviles con las que tus usuarios reales acceden a tu sitio. Esto puede proporcionarte oportunidades para comprender las restricciones reales de CPU/GPU con las que operan.

¿Estamos enviando demasiado código JavaScript? Mmm. Posiblemente :)

A través del archivo HTTP (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 ser interactivos. Estos sitios dedican hasta 4 segundos solo al análisis y la compilación de JS.

ALT_TEXT_HERE

Considera el tiempo que lleva recuperar y procesar código JS y otros recursos, y tal vez no te sorprenda que los usuarios puedan esperar un tiempo antes de sentir que las páginas están listas para usarse. Definitivamente podemos hacerlo mejor aquí.

Si quitas el código JavaScript que no es crítico de tus páginas, puedes reducir los tiempos de transmisión, el análisis y la compilación que hacen uso intensivo de la CPU, y una potencial 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 determinar qué tan pronto un usuario puede interactuar con tu sitio.

ALT_TEXT_HERE

Si la secuencia de comandos se ejecuta durante más de 50 ms, el tiempo de interacción se retrasa toda la cantidad de tiempo que se tarda en descargar, compilar y ejecutar el JS (Alex Russell).

Para solucionar esto, es mejor usar fragmentos pequeños para evitar bloquear el subproceso principal. Explora si puedes reducir la cantidad de trabajo que se realiza durante la ejecución.

Otros costos

JavaScript puede afectar el rendimiento de la página de otras maneras:

  • Memoria. Parece que las páginas se bloquean o pausan con frecuencia debido a la GC (recolección de elementos no utilizados). Cuando un navegador recupera memoria, la ejecución de JS se pausa, de modo que un navegador que recolecta elementos no utilizados con frecuencia puede pausar la ejecución con mayor frecuencia de la que nos gustaría. Evita las fugas de memoria y las pausas de GC frecuentes para impedir que las páginas se bloqueen.
  • Durante el tiempo de ejecución, el código JavaScript de larga duración puede bloquear el subproceso principal, lo que provoca que las páginas no respondan. Dividir el trabajo en partes más pequeñas (con requestAnimationFrame() o requestIdleCallback() para la programación) puede minimizar los problemas de capacidad de respuesta, lo que puede ayudar a mejorar Interaction to Next Paint (INP).

Patrones para reducir el costo de entrega de JavaScript

Cuando intentas mantener lentos los tiempos de análisis, compilación y transmisión de red de JavaScript, hay patrones que pueden ser útiles, como la fragmentación basada en rutas o PRPL.

PRPL

PRPL (push, procesamiento, almacenamiento previo en caché y carga diferida) es un patrón optimizado para la interactividad a través de una división de código y el almacenamiento en caché agresivos:

ALT_TEXT_HERE

Vamos a visualizar el impacto que puede tener.

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

ALT_TEXT_HERE

Wego, un sitio que usa PRPL, logra mantener un tiempo de análisis bajo para sus rutas y se vuelve interactivo muy rápido. Muchos de los otros sitios anteriores adoptaron la división de código y presupuestos de rendimiento para intentar reducir sus costos de JS.

Arranque 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 el procesamiento del servidor y, luego, lo "actualizan" para que conecte controladores de eventos cuando se recupera 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 extraño donde la mitad de la experiencia no puede ser interactiva hasta que JavaScript termine de procesarse.

El arranque progresivo puede ser un mejor enfoque. Envía una página que sea mínimamente funcional (compuesta solo por el código HTML/JS/CSS necesario para la ruta actual). A medida que vayan llegando más recursos, la app puede hacer una carga diferida y desbloquear más funciones.

ALT_TEXT_HERE
Arranque progresivo de Paul Lewis

Lo ideal es cargar código de manera proporcional a lo que se ve. PRPL y el arranque progresivo son patrones que pueden ayudar a lograr esto.

Conclusiones

El tamaño de transmisión es fundamental para las redes de baja gama. El tiempo de análisis es importante para dispositivos vinculados a la CPU. Es importante mantener estos valores bajos.

Los equipos tuvieron éxito al adoptar presupuestos de rendimiento estrictos para mantener bajos los tiempos de transmisión, análisis y compilación de JavaScript. Consulta "Can You Afford It?: Presupuestos reales de rendimiento web" que brinda orientación sobre los presupuestos para dispositivos móviles.

ALT_TEXT_HERE
Es útil considerar cuánto "margen" de JS nos pueden dejar para la lógica de la app las decisiones de arquitectura que tomamos.

Si estás desarrollando un sitio orientado a dispositivos móviles, haz todo lo posible para desarrollar en 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 supervisar sus costos de JavaScript.

Más información