Reduce y comprime las cargas útiles de red con gzip

En este codelab, se explora cómo reducir y comprimir el paquete de JavaScript para la siguiente aplicación mejora el rendimiento de la página reduciendo el tamaño de la solicitud de la app.

Captura de pantalla de la aplicación

Medir

Antes de comenzar a agregar optimizaciones, siempre es una buena idea analizar primero el estado actual de la aplicación.

  • Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona 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ús + J" (o bien "Comando + Opción + J" en Mac) para abrir Herramientas para desarrolladores.
  2. Haga clic en la pestaña Red.
  3. Selecciona la casilla de verificación Inhabilitar caché.
  4. Vuelve a cargar la app.

Tamaño original del paquete en el panel de red

Si bien se realizó un gran progreso en el codelab "Cómo quitar código sin usar" 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 se guarda esta función en un archivo propio, el tamaño del archivo es de aproximadamente 112 B (bytes).

Si se quitan todos los espacios en blanco, el código resultante se ve 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 aproximadamente 83 B. Si se altera aún más mediante la reducción de la longitud del nombre de la variable y la modificación de 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 JavaScript del navegador los interpreta de la misma manera. El beneficio de ofuscar el código de esta manera puede ayudar a reducir el tamaño de los archivos. 112 B no era mucho para empezar, pero el tamaño aún había una reducción del 50%.

En esta aplicación, webpack versión 4 se usa 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 reduce el paquete de forma predeterminada durante el modo de producción. Usa 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 reducido, haz clic en main.bundle.js en el panel Red de Herramientas para desarrolladores. Ahora, haz clic en la pestaña Respuesta.

Respuesta reducida

El código en su forma final, reducido y alterado, se muestra en el cuerpo de la respuesta. Para saber qué tan grande podría haber sido el paquete si no se hubiera reducido, 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 nuevamente en el panel Network de las Herramientas para desarrolladores.

Tamaño del paquete de 767 KB

Esa es una gran diferencia. 😅

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

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

La inclusión de un proceso para reducir el código en tu aplicación depende de las herramientas que usas:

  • Si se usa webpack v4 o una versión posterior, no es necesario hacer nada más, ya que el código se reduce de forma predeterminada en el modo de producción. 👍
  • Si se usa una versión anterior de webpack, instala y, luego, incluye TerserWebpackPlugin en el proceso de compilación de webpack. En la documentación, se explica esto en detalle.
  • También existen otros complementos de reducción que se pueden usar en su lugar, como BabelMinifyWebpackPlugin y ClosureCompilerPlugin.
  • Si un agrupador de módulos no se usa en absoluto, usa Terser como una herramienta de CLI o inclúyelo directamente como una dependencia.

Compresión

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

Por lo general, la compresión se refiere al código que se modificó mediante un algoritmo de compresión de datos. A diferencia de la reducción que proporciona un código perfectamente válido, el código comprimido se debe descomprimir antes de usarlo.

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

  • General representa los encabezados generales que son relevantes para toda la interacción de solicitud-respuesta.
  • En Response Headers, se muestra una lista de encabezados específicos para la respuesta real del servidor.
  • En Request Headers, se muestra una lista de encabezados que adjunta el cliente a la solicitud.

Observa el encabezado accept-encoding en Request Headers.

Aceptar encabezado de codificació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 hay tres que se admiten aquí para la compresión (y descompresión) de las solicitudes de red HTTP:

  • Gzip (gzip): Es el formato de compresión más usado para las interacciones de servidores y clientes. Se basa en el algoritmo de Deflate y es compatible con todos los navegadores actuales.
  • Reducir deflación (deflate): No se usa comúnmente.
  • Brotli (br): Es un algoritmo de compresión más nuevo que apunta a mejorar aún más el índice de compresión, lo que puede generar que la página se cargue aún más rápido. 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 Express ahora se usa como framework del servidor. En las siguientes secciones, se explora la compresión estática y dinámica.

Compresión dinámica

La compresión dinámica consiste en comprimir elementos sobre la marcha a medida que el navegador los solicita.

Ventajas

  • No es necesario crear y actualizar las versiones comprimidas guardadas de los elementos.
  • 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 índices de compresión lleva más tiempo. Esto puede provocar un problema de rendimiento, ya que el usuario espera a que se compriman los elementos antes de que el servidor los envíe.

Compresión dinámica con Node y Express

El archivo server.js se encarga 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);
});

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

Para asegurarte de que todos los recursos se compriman cada vez que se soliciten, se puede usar la biblioteca de middleware de compresión. Para comenzar, agrégalo como devDependency en package.json:

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

Y, luego, impórtala en el archivo de servidor, server.js:

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

Además, agrégalo como middleware antes de activar 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 Red.

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

De 225 KB a 61.6 KB Ahora, en el Response Headers, un encabezado content-encoding muestra que el servidor está enviando 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 comprimir los recursos y ahorrarlos con anticipación.

Ventajas

  • La latencia debida a altos niveles de compresión ya no es una preocupación. No es necesario que ocurra nada sobre la marcha para comprimir archivos, ya que ahora se pueden recuperar directamente.

Desventajas

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

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

Dado que la compresión estática implica comprimir archivos con anticipación, se puede modificar la configuración del paquete web para comprimir elementos como parte del paso de compilación. Para esto, se puede usar CompressionPlugin.

Para comenzar, agrégalo como devDependency en package.json:

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

Como cualquier otro complemento de webpack, impórtalo en el archivo de configuración, webpack.config.js:.

const path = require("path");

//...

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

Además, inclúyela en el array plugins:

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

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

Cuando la app se vuelva a cargar y compilar, se creará una versión comprimida del paquete principal. Abre la consola de Glitch para ver el contenido del directorio final public/ 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 del paquete, main.bundle.js.gz, ahora 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 cada vez que se soliciten sus versiones de JS originales. Para ello, define una ruta nueva en server.js antes de que los archivos se entreguen 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 utiliza una función de devolución de llamada para definir cómo manejar esta solicitud. La ruta funciona de la siguiente manera:

  • Especificar '*.js' como primer argumento significa que esto funciona para cada extremo que se activa a fin de recuperar un archivo JS.
  • Dentro de la devolución de llamada, se adjunta .gz 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 hasta cualquier devolución de llamada que pueda ser la siguiente.

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

Reducción del tamaño de los paquetes con compresión estática

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

Conclusión

En este codelab, se abordó el proceso de reducción y compresión del código fuente. Ambas técnicas se están convirtiendo en opciones predeterminadas para muchas de las herramientas disponibles en la actualidad, por lo que es importante averiguar si tu cadena de herramientas ya las admite o si deberías comenzar a aplicar ambos procesos por tu cuenta.