Reduce y comprime las cargas útiles de red con gzip

En este codelab, se explora cómo la minificación y la compresión del paquete de JavaScript para la siguiente aplicación mejoran el rendimiento de la página, ya que reducen el tamaño de la solicitud de la app.

Captura de pantalla de la app

Medir

Antes de comenzar a agregar optimizaciones, siempre es recomendable analizar el estado actual de la aplicación.

  • Para obtener una vista previa del sitio, presiona Ver app y, luego, Pantalla completa pantalla completa.

Esta app, que también se abordó en el codelab "Quita el código sin usar", te permite votar por tu gatito favorito. 🐈

Ahora, observa el tamaño de esta aplicación:

  1. Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir DevTools.
  2. Haga clic en la pestaña Red.
  3. Selecciona la casilla de verificación Inhabilitar la memoria caché.
  4. Vuelve a cargar la app.

Tamaño original del paquete en el panel de red

Si bien se lograron muchos avances en el codelab "Quita el código no utilizado" para reducir el tamaño de este paquete, 225 KB sigue siendo bastante grande.

Reducción

Considera el siguiente bloque de código.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Si esta función se guarda en un archivo propio, el tamaño del archivo es de alrededor de 112 B (bytes).

Si se quitan todos los espacios en blanco, el código resultante se verá de la siguiente manera:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

El tamaño del archivo ahora sería de alrededor de 83 B. Si se distorsiona aún más al reducir la longitud del nombre de la variable y modificar algunas expresiones, el código final podría verse así:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

El tamaño del archivo ahora alcanza los 62 B.

Con cada paso, el código se vuelve más difícil de leer. Sin embargo, el motor de JavaScript del navegador interpreta cada uno de estos de la misma manera. El beneficio de ofuscar el código de esta manera puede ayudar a lograr tamaños de archivo más pequeños. 112 B no era mucho para empezar, pero aun así hubo una reducción del 50% en el tamaño.

En esta aplicación, se usa la versión 4 de webpack como agrupador de módulos. La versión específica se puede ver en package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

La versión 4 ya minimiza el paquete de forma predeterminada durante el modo de producción. Utiliza TerserWebpackPlugin, un complemento para Terser. Terser es una herramienta popular que se usa para comprimir código JavaScript.

Para tener una idea de cómo se ve el código comprimido, haz clic en main.bundle.js mientras aún estás en el panel Red de Herramientas para desarrolladores. Ahora, haz clic en la pestaña Respuesta.

Respuesta comprimida

El código en su forma final, minimizado y con nombres abreviados, se muestra en el cuerpo de la respuesta. Para saber qué tan grande podría haber sido el paquete si no se hubiera minimizado, abre webpack.config.js y actualiza la configuración de mode.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Vuelve a cargar la aplicación y observa el tamaño del paquete en el panel Network de Herramientas para desarrolladores.

Tamaño del paquete de 767 KB

¡Esa es una diferencia bastante grande! 😅

Asegúrate de revertir los cambios aquí antes de continuar.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Incluir un proceso para reducir el código en tu aplicación depende de las herramientas que uses:

  • Si se usa webpack v4 o una versión posterior, no es necesario realizar ningún trabajo adicional, ya que el código se minimiza de forma predeterminada en el modo de producción. 👍
  • Si se usa una versión anterior de webpack, instala e incluye TerserWebpackPlugin en el proceso de compilación de webpack. La documentación explica esto en detalle.
  • También existen otros complementos de reducción que se pueden usar en su lugar, como BabelMinifyWebpackPlugin y ClosureCompilerPlugin.
  • Si no se usa ningún bundler de módulos, usa Terser como herramienta de CLI o inclúyelo directamente como dependencia.

Compresión

Si bien a veces se usa el término "compresión" de manera imprecisa para explicar cómo se reduce el código durante el proceso de minimización, en realidad no se comprime en el sentido literal.

Por lo general, compresión hace referencia al código que se modificó con un algoritmo de compresión de datos. A diferencia de la minificación, que proporciona código perfectamente válido, el código comprimido debe descomprimirse antes de usarse.

Con cada solicitud y respuesta HTTP, los navegadores y los servidores web pueden agregar encabezados para incluir información adicional sobre el activo que se recupera o recibe. Esto se puede ver en la pestaña Headers del panel Network de Herramientas para desarrolladores, en la que se muestran tres tipos:

  • General representa los encabezados generales pertinentes para toda la interacción de solicitud y respuesta.
  • Encabezados de respuesta muestra una lista de encabezados específicos de la respuesta real del servidor.
  • Encabezados de solicitud muestra una lista de los encabezados que el cliente adjuntó a la solicitud.

Echa un vistazo al encabezado accept-encoding en el Request Headers.

Encabezado de codificación de aceptación

El navegador usa accept-encoding para especificar qué formatos de codificación de contenido o algoritmos de compresión admite. Existen muchos algoritmos de compresión de texto, pero solo tres son compatibles con la compresión (y descompresión) de solicitudes de red HTTP:

  • Gzip (gzip): Es el formato de compresión más utilizado para las interacciones entre el servidor y el cliente. Se basa en el algoritmo Deflate y es compatible con todos los navegadores actuales.
  • Deflate (deflate): No se usa con frecuencia.
  • Brotli (br): Es un algoritmo de compresión más reciente que tiene como objetivo mejorar aún más las relaciones de compresión, lo que puede generar cargas de páginas aún más rápidas. Es compatible con las versiones más recientes de la mayoría de los navegadores.

La aplicación de ejemplo de este instructivo es idéntica a la que se completó en el codelab "Quita el código sin usar", excepto por el hecho de que ahora se usa Express como framework de servidor. En las próximas secciones, se explorará la compresión estática y dinámica.

Compresión dinámica

La compresión dinámica implica comprimir los recursos sobre la marcha a medida que el navegador los solicita.

Ventajas

  • No es necesario crear ni actualizar versiones comprimidas guardadas de los recursos.
  • La compresión sobre la marcha funciona especialmente bien para las páginas web que se generan de forma dinámica.

Desventajas

  • Comprimir archivos en niveles más altos para lograr mejores proporciones de compresión lleva más tiempo. Esto puede afectar el rendimiento, ya que el usuario espera a que se compriman los recursos antes de que el servidor los envíe.

Compresión dinámica con Node/Express

El archivo server.js es responsable de configurar el servidor de Node que aloja la aplicación.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

Actualmente, todo lo que hace es importar express y usar el middleware express.static para cargar todos los archivos estáticos HTML, JS y CSS en el directorio public/ (y esos archivos se crean con webpack en cada compilación).

Para asegurarte de que todos los recursos se compriman cada vez que se soliciten, puedes usar la biblioteca de middleware compression. Comienza por agregarlo como un devDependency en package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

E impórtalo en el archivo del servidor, server.js:

const express = require('express');
const compression = require('compression');

Y agrégalo como middleware antes de que se monte express.static:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Ahora vuelve a cargar la app y observa el tamaño del paquete en el panel Network.

Tamaño del paquete con compresión dinámica

De 225 KB a 61.6 KB En el Response Headers, un encabezado content-encoding muestra que el servidor envía este archivo codificado con gzip.

Encabezado de codificación de contenido

Compresión estática

La idea detrás de la compresión estática es tener los recursos comprimidos y guardados con anticipación.

Ventajas

  • La latencia debido a los altos niveles de compresión ya no es un problema. No es necesario que se realice ninguna acción sobre la marcha para comprimir los archivos, ya que ahora se pueden recuperar directamente.

Desventajas

  • Los recursos deben comprimirse con cada compilación. Los tiempos de compilación pueden aumentar significativamente si se usan niveles de compresión altos.

Compresión estática con Node/Express y webpack

Dado que la compresión estática implica comprimir archivos con anticipación, se pueden modificar los parámetros de configuración de webpack para comprimir los recursos como parte del paso de compilación. Para ello, se puede usar CompressionPlugin.

Comienza por agregarlo como un devDependency en package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Al igual que cualquier otro complemento de webpack, impórtalo en el archivo de configuraciones, webpack.config.js:.

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

Luego, inclúyelo en el array plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

De forma predeterminada, el complemento comprime los archivos de compilación con gzip. Consulta la documentación para aprender a agregar opciones para usar un algoritmo diferente o incluir o excluir ciertos archivos.

Cuando la app se vuelve a cargar y compilar, se crea una versión comprimida del paquete principal. Abre la consola de Glitch para ver el contenido del directorio public/ final que entrega el servidor de Node.

  • Haz clic en el botón Herramientas.
  • Haz clic en el botón Consola.
  • En la consola, ejecuta los siguientes comandos para cambiar al directorio public y ver todos sus archivos:
cd public
ls

Archivos de salida finales en el directorio público

La versión comprimida con gzip del paquete, main.bundle.js.gz, también se guarda aquí. CompressionPlugin también comprime index.html de forma predeterminada.

Lo siguiente que se debe hacer es indicarle al servidor que envíe estos archivos comprimidos con gzip cada vez que se soliciten sus versiones originales en JS. Esto se puede hacer definiendo una ruta nueva en server.js antes de que se publiquen los archivos con express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get se usa para indicarle al servidor cómo responder a una solicitud GET para un extremo específico. Luego, se usa una función de devolución de llamada para definir cómo controlar esta solicitud. La ruta funciona de la siguiente manera:

  • Especificar '*.js' como el primer argumento significa que esto funciona para todos los extremos que se activan para recuperar un archivo JS.
  • Dentro de la devolución de llamada, .gz se adjunta a la URL de la solicitud y el encabezado de respuesta Content-Encoding se establece en gzip.
  • Por último, next() garantiza que la secuencia continúe con cualquier devolución de llamada que pueda seguir.

Una vez que se vuelva a cargar la app, vuelve a mirar el panel Network.

Reducción del tamaño del paquete con la compresión estática

Al igual que antes, se produjo una reducción significativa en el tamaño del paquete.

Conclusión

En este codelab, se abordó el proceso de minificación y compresión del código fuente. Ambas técnicas se están convirtiendo en una opción predeterminada en muchas de las herramientas disponibles hoy en día, por lo que es importante averiguar si tu cadena de herramientas ya las admite o si debes comenzar a aplicar ambos procesos por tu cuenta.