Reduce y comprime las cargas útiles de red con gzip

En este codelab, se explora la manera en que la reducción y la compresión del paquete de JavaScript para la siguiente aplicación mejora el rendimiento de la página mediante la reducción del tamaño de la solicitud de la app.

Captura de pantalla de la app

Medir

Antes de comenzar para 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 incluyó 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 `Command + Option + 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 Red

Si bien se realizó un gran progreso en el codelab "Quita el código sin usar" para recortar este paquete, el tamaño de 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++;}}

Ahora, el tamaño del archivo será de alrededor de 83 B. Si se altera aún más cuando se reduce la longitud del nombre de la variable y se modifican algunas expresiones, es posible que el código final se vea de la siguiente manera:

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

El tamaño del archivo ahora alcanza 62 B.

Con cada paso, el código es cada vez más difícil de leer. Sin embargo, el motor de JavaScript del navegador los interpreta de la misma manera. El beneficio de ofuscar el código de esta manera puede ayudar a lograr archivos de menor tamaño. 112 B no era mucho al principio, pero aún así se redujo el tamaño en un 50%.

En esta aplicación, la versión 4 de webpack 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 sin salir del panel Red de Herramientas para desarrolladores. Ahora, haz clic en la pestaña Respuesta.

Respuesta reducida

El código en su forma final, reducida y moldeada, se muestra en el cuerpo de la respuesta. Para averiguar qué tan grande podría haber sido el paquete si no se recortó, 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 Red de Herramientas para desarrolladores.

El tamaño del paquete es de 767 KB.

¡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 uses:

  • Si se usa webpack v4 o superior, no es necesario realizar ningún trabajo adicional, 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 TerserWebpackPlugin y, luego, inclúyelo 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 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 hace referencia al código que se modificó con un algoritmo de compresión de datos. A diferencia de la reducción, que termina proporcionando 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 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 de red de Herramientas para desarrolladores, en el que se muestran tres tipos:

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

Observa el encabezado accept-encoding en el 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 son compatibles con 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 del servidor y del cliente. Se basa en el algoritmo para disminuir la cantidad de contenido y es compatible con todos los navegadores actuales.
  • Reducción (deflate): no se usa con frecuencia.
  • Brotli (br): Es un algoritmo de compresión más reciente que busca mejorar aún más las proporciones 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 app completada en el codelab "Remove unused code", excepto por el hecho de que Express ahora se usa como un framework de servidor. En las siguientes secciones, exploraremos tanto la compresión estática como la 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 y actualizar versiones comprimidas guardadas de los elementos.
  • La compresión sobre la marcha funciona especialmente bien para páginas web generadas de forma dinámica.

Desventajas

  • La compresión de archivos en niveles más altos para lograr mejores relaciones de compresión lleva más tiempo. Esto puede provocar un hit de rendimiento mientras 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 se encarga de configurar el servidor de nodo 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);
});

Por el momento, todo lo que hace 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 garantizar que todos los elementos se compriman cada vez que se solicitan, se puede usar la biblioteca de middleware de compression. Primero, agrégalo como devDependency en package.json:

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

Luego, impórtalo al archivo del servidor, server.js:

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

Y agrégalo como middleware antes de que se active 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. En el Response Headers ahora, 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 con anticipación y ahorrarse tiempo.

Ventajas

  • La latencia debido a los altos niveles de compresión ya no es un problema. No es necesario que ocurra nada 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 de manera significativa 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, la configuración del paquete web se puede modificar para comprimir los elementos como parte del paso de compilación. CompressionPlugin.

Primero, 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");

Inclúyela 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 y aprende a agregar opciones para 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 consultar el contenido del directorio final public/ que entrega el servidor de nodos.

  • 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 finales de salida 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 debes hacer es indicarle al servidor que envíe estos archivos comprimidos en formato gzip cada vez que se soliciten sus versiones originales de JS. 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 usa 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 todos los extremos que se activan con el 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 con cualquier devolución de llamada que pueda ocurrir a continuación.

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

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

Igual que antes, se redujo de forma significativa el tamaño del paquete.

Conclusión

En este codelab, se abarcó el proceso de reducir y comprimir el código fuente. Ambas técnicas se están convirtiendo en una opción predeterminada en muchas de las herramientas disponibles en la actualidad, 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.