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á.
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.
- Usa la división de código para dividir tu JavaScript entre lo que es crítico y lo que no. Los agrupadores de módulos, como webpack, admiten la división de código.
- Carga de forma diferida el código que no es crítico.
- Reducción
- Usa UglifyJS para minificar el código de ES5.
- Usa babel-minify o uglify-es para reducir ES2015+.
- Compresión
- Cómo quitar el código que no se usa.
- Identifica oportunidades de código que se puede quitar o cargar de forma diferida con la cobertura de código de Herramientas de desarrolladores.
- Usa babel-preset-env y browserlist para evitar funciones de transpilación que ya están incluidas en los navegadores modernos. Los desarrolladores avanzados pueden encontrar que un análisis cuidadoso de sus paquetes de webpack ayuda a identificar oportunidades para cortar las dependencias innecesarias.
- Para quitar código, consulta las optimizaciones avanzadas de Closure Compiler y los complementos de corte de bibliotecas, como lodash-babel-plugin o ContextReplacementPlugin de webpack, para bibliotecas como Moment.js.
- 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.
Las pestañas Bottom-Up y Call Tree muestran los tiempos exactos de análisis y compilación:
![ALT_TEXT_HERE](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-cfd35a90562dd.png?authuser=7&hl=es-419)
Pero ¿por qué es importante?
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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-e3dc86ddc7603.png?authuser=7&hl=es-419)
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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-52af247f207f5.png?authuser=7&hl=es-419)
¿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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-7cafa788a7bdf.png?authuser=7&hl=es-419)
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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-6de3a79335e9b.png?authuser=7&hl=es-419)
¿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.
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.
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()
orequestIdleCallback()
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:
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:
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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-bc9d1b36573cd.png?authuser=7&hl=es-419)
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](https://web.developers.google.cn/static/articles/optimizing-content-efficiency-javascript-startup-optimization/image/alttexthere-72d5f9f9722e.png?authuser=7&hl=es-419)
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
- Chrome Dev Summit 2017: Prácticas recomendadas sobre la carga moderna
- Rendimiento de JavaScript en el inicio
- Cómo solucionar la crisis de rendimiento web: Nolan Lawson
- ¿Puedes costearlo? Presupuestos de rendimiento en el mundo real — Alex Russell
- Evaluación de frameworks y bibliotecas web: Kristofer Baxter
- Resultados de la experimentación con Brotli de Cloudflare 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, por lo que debes evaluar con cuidado. Es probable que, en su lugar, quieras comprimir de forma estática).
- Performance Futures: Sam Saccone